diff options
Diffstat (limited to 'drivers/staging/hv')
39 files changed, 15501 insertions, 0 deletions
diff --git a/drivers/staging/hv/BlkVsc.c b/drivers/staging/hv/BlkVsc.c new file mode 100644 index 00000000000..51aa861292f --- /dev/null +++ b/drivers/staging/hv/BlkVsc.c @@ -0,0 +1,111 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include "osd.h" +#include "StorVsc.c" + +static const char *gBlkDriverName = "blkvsc"; + +/* {32412632-86cb-44a2-9b5c-50d1417354f5} */ +static const struct hv_guid gBlkVscDeviceType = { + .data = { + 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 + } +}; + +static int BlkVscOnDeviceAdd(struct hv_device *Device, void *AdditionalInfo) +{ + struct storvsc_device_info *deviceInfo; + int ret = 0; + + DPRINT_ENTER(BLKVSC); + + deviceInfo = (struct storvsc_device_info *)AdditionalInfo; + + ret = StorVscOnDeviceAdd(Device, AdditionalInfo); + if (ret != 0) { + DPRINT_EXIT(BLKVSC); + return ret; + } + + /* + * We need to use the device instance guid to set the path and target + * id. For IDE devices, the device instance id is formatted as + * <bus id> * - <device id> - 8899 - 000000000000. + */ + deviceInfo->PathId = Device->deviceInstance.data[3] << 24 | + Device->deviceInstance.data[2] << 16 | + Device->deviceInstance.data[1] << 8 | + Device->deviceInstance.data[0]; + + deviceInfo->TargetId = Device->deviceInstance.data[5] << 8 | + Device->deviceInstance.data[4]; + + DPRINT_EXIT(BLKVSC); + + return ret; +} + +int BlkVscInitialize(struct hv_driver *Driver) +{ + struct storvsc_driver_object *storDriver; + int ret = 0; + + DPRINT_ENTER(BLKVSC); + + storDriver = (struct storvsc_driver_object *)Driver; + + /* Make sure we are at least 2 pages since 1 page is used for control */ + ASSERT(storDriver->RingBufferSize >= (PAGE_SIZE << 1)); + + Driver->name = gBlkDriverName; + memcpy(&Driver->deviceType, &gBlkVscDeviceType, sizeof(struct hv_guid)); + + storDriver->RequestExtSize = sizeof(struct storvsc_request_extension); + + /* + * Divide the ring buffer data size (which is 1 page less than the ring + * buffer size since that page is reserved for the ring buffer indices) + * by the max request size (which is + * VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER + struct vstor_packet + u64) + */ + storDriver->MaxOutstandingRequestsPerChannel = + ((storDriver->RingBufferSize - PAGE_SIZE) / + ALIGN_UP(MAX_MULTIPAGE_BUFFER_PACKET + + sizeof(struct vstor_packet) + sizeof(u64), + sizeof(u64))); + + DPRINT_INFO(BLKVSC, "max io outstd %u", + storDriver->MaxOutstandingRequestsPerChannel); + + /* Setup the dispatch table */ + storDriver->Base.OnDeviceAdd = BlkVscOnDeviceAdd; + storDriver->Base.OnDeviceRemove = StorVscOnDeviceRemove; + storDriver->Base.OnCleanup = StorVscOnCleanup; + storDriver->OnIORequest = StorVscOnIORequest; + + DPRINT_EXIT(BLKVSC); + + return ret; +} diff --git a/drivers/staging/hv/Channel.c b/drivers/staging/hv/Channel.c new file mode 100644 index 00000000000..d649ee169d9 --- /dev/null +++ b/drivers/staging/hv/Channel.c @@ -0,0 +1,1015 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include "osd.h" +#include "logging.h" +#include "VmbusPrivate.h" + +/* Internal routines */ +static int VmbusChannelCreateGpadlHeader( + void *Kbuffer, /* must be phys and virt contiguous */ + u32 Size, /* page-size multiple */ + struct vmbus_channel_msginfo **msgInfo, + u32 *MessageCount); +static void DumpVmbusChannel(struct vmbus_channel *channel); +static void VmbusChannelSetEvent(struct vmbus_channel *channel); + + +#if 0 +static void DumpMonitorPage(struct hv_monitor_page *MonitorPage) +{ + int i = 0; + int j = 0; + + DPRINT_DBG(VMBUS, "monitorPage - %p, trigger state - %d", + MonitorPage, MonitorPage->TriggerState); + + for (i = 0; i < 4; i++) + DPRINT_DBG(VMBUS, "trigger group (%d) - %llx", i, + MonitorPage->TriggerGroup[i].AsUINT64); + + for (i = 0; i < 4; i++) { + for (j = 0; j < 32; j++) { + DPRINT_DBG(VMBUS, "latency (%d)(%d) - %llx", i, j, + MonitorPage->Latency[i][j]); + } + } + for (i = 0; i < 4; i++) { + for (j = 0; j < 32; j++) { + DPRINT_DBG(VMBUS, "param-conn id (%d)(%d) - %d", i, j, + MonitorPage->Parameter[i][j].ConnectionId.Asu32); + DPRINT_DBG(VMBUS, "param-flag (%d)(%d) - %d", i, j, + MonitorPage->Parameter[i][j].FlagNumber); + } + } +} +#endif + +/** + * VmbusChannelSetEvent - Trigger an event notification on the specified channel. + */ +static void VmbusChannelSetEvent(struct vmbus_channel *Channel) +{ + struct hv_monitor_page *monitorPage; + + DPRINT_ENTER(VMBUS); + + if (Channel->OfferMsg.MonitorAllocated) { + /* Each u32 represents 32 channels */ + set_bit(Channel->OfferMsg.ChildRelId & 31, + (unsigned long *) gVmbusConnection.SendInterruptPage + + (Channel->OfferMsg.ChildRelId >> 5)); + + monitorPage = gVmbusConnection.MonitorPages; + monitorPage++; /* Get the child to parent monitor page */ + + set_bit(Channel->MonitorBit, + (unsigned long *)&monitorPage->TriggerGroup + [Channel->MonitorGroup].Pending); + + } else { + VmbusSetEvent(Channel->OfferMsg.ChildRelId); + } + + DPRINT_EXIT(VMBUS); +} + +#if 0 +static void VmbusChannelClearEvent(struct vmbus_channel *channel) +{ + struct hv_monitor_page *monitorPage; + + DPRINT_ENTER(VMBUS); + + if (Channel->OfferMsg.MonitorAllocated) { + /* Each u32 represents 32 channels */ + clear_bit(Channel->OfferMsg.ChildRelId & 31, + (unsigned long *)gVmbusConnection.SendInterruptPage + + (Channel->OfferMsg.ChildRelId >> 5)); + + monitorPage = + (struct hv_monitor_page *)gVmbusConnection.MonitorPages; + monitorPage++; /* Get the child to parent monitor page */ + + clear_bit(Channel->MonitorBit, + (unsigned long *)&monitorPage->TriggerGroup + [Channel->MonitorGroup].Pending); + } + + DPRINT_EXIT(VMBUS); +} + +#endif +/** + * VmbusChannelGetDebugInfo -Retrieve various channel debug info + */ +void VmbusChannelGetDebugInfo(struct vmbus_channel *Channel, + struct vmbus_channel_debug_info *DebugInfo) +{ + struct hv_monitor_page *monitorPage; + u8 monitorGroup = (u8)Channel->OfferMsg.MonitorId / 32; + u8 monitorOffset = (u8)Channel->OfferMsg.MonitorId % 32; + /* u32 monitorBit = 1 << monitorOffset; */ + + DebugInfo->RelId = Channel->OfferMsg.ChildRelId; + DebugInfo->State = Channel->State; + memcpy(&DebugInfo->InterfaceType, + &Channel->OfferMsg.Offer.InterfaceType, sizeof(struct hv_guid)); + memcpy(&DebugInfo->InterfaceInstance, + &Channel->OfferMsg.Offer.InterfaceInstance, + sizeof(struct hv_guid)); + + monitorPage = (struct hv_monitor_page *)gVmbusConnection.MonitorPages; + + DebugInfo->MonitorId = Channel->OfferMsg.MonitorId; + + DebugInfo->ServerMonitorPending = + monitorPage->TriggerGroup[monitorGroup].Pending; + DebugInfo->ServerMonitorLatency = + monitorPage->Latency[monitorGroup][monitorOffset]; + DebugInfo->ServerMonitorConnectionId = + monitorPage->Parameter[monitorGroup] + [monitorOffset].ConnectionId.u.Id; + + monitorPage++; + + DebugInfo->ClientMonitorPending = + monitorPage->TriggerGroup[monitorGroup].Pending; + DebugInfo->ClientMonitorLatency = + monitorPage->Latency[monitorGroup][monitorOffset]; + DebugInfo->ClientMonitorConnectionId = + monitorPage->Parameter[monitorGroup] + [monitorOffset].ConnectionId.u.Id; + + RingBufferGetDebugInfo(&Channel->Inbound, &DebugInfo->Inbound); + RingBufferGetDebugInfo(&Channel->Outbound, &DebugInfo->Outbound); +} + +/** + * VmbusChannelOpen - Open the specified channel. + */ +int VmbusChannelOpen(struct vmbus_channel *NewChannel, u32 SendRingBufferSize, + u32 RecvRingBufferSize, void *UserData, u32 UserDataLen, + void (*OnChannelCallback)(void *context), void *Context) +{ + struct vmbus_channel_open_channel *openMsg; + struct vmbus_channel_msginfo *openInfo; + void *in, *out; + unsigned long flags; + int ret; + + DPRINT_ENTER(VMBUS); + + /* Aligned to page size */ + ASSERT(!(SendRingBufferSize & (PAGE_SIZE - 1))); + ASSERT(!(RecvRingBufferSize & (PAGE_SIZE - 1))); + + NewChannel->OnChannelCallback = OnChannelCallback; + NewChannel->ChannelCallbackContext = Context; + + /* Allocate the ring buffer */ + out = osd_PageAlloc((SendRingBufferSize + RecvRingBufferSize) + >> PAGE_SHIFT); + ASSERT(out); + ASSERT(((unsigned long)out & (PAGE_SIZE-1)) == 0); + + in = (void *)((unsigned long)out + SendRingBufferSize); + + NewChannel->RingBufferPages = out; + NewChannel->RingBufferPageCount = (SendRingBufferSize + + RecvRingBufferSize) >> PAGE_SHIFT; + + RingBufferInit(&NewChannel->Outbound, out, SendRingBufferSize); + + RingBufferInit(&NewChannel->Inbound, in, RecvRingBufferSize); + + /* Establish the gpadl for the ring buffer */ + DPRINT_DBG(VMBUS, "Establishing ring buffer's gpadl for channel %p...", + NewChannel); + + NewChannel->RingBufferGpadlHandle = 0; + + ret = VmbusChannelEstablishGpadl(NewChannel, + NewChannel->Outbound.RingBuffer, + SendRingBufferSize + + RecvRingBufferSize, + &NewChannel->RingBufferGpadlHandle); + + DPRINT_DBG(VMBUS, "channel %p <relid %d gpadl 0x%x send ring %p " + "size %d recv ring %p size %d, downstreamoffset %d>", + NewChannel, NewChannel->OfferMsg.ChildRelId, + NewChannel->RingBufferGpadlHandle, + NewChannel->Outbound.RingBuffer, + NewChannel->Outbound.RingSize, + NewChannel->Inbound.RingBuffer, + NewChannel->Inbound.RingSize, + SendRingBufferSize); + + /* Create and init the channel open message */ + openInfo = kmalloc(sizeof(*openInfo) + + sizeof(struct vmbus_channel_open_channel), + GFP_KERNEL); + ASSERT(openInfo != NULL); + + openInfo->WaitEvent = osd_WaitEventCreate(); + + openMsg = (struct vmbus_channel_open_channel *)openInfo->Msg; + openMsg->Header.MessageType = ChannelMessageOpenChannel; + openMsg->OpenId = NewChannel->OfferMsg.ChildRelId; /* FIXME */ + openMsg->ChildRelId = NewChannel->OfferMsg.ChildRelId; + openMsg->RingBufferGpadlHandle = NewChannel->RingBufferGpadlHandle; + ASSERT(openMsg->RingBufferGpadlHandle); + openMsg->DownstreamRingBufferPageOffset = SendRingBufferSize >> + PAGE_SHIFT; + openMsg->ServerContextAreaGpadlHandle = 0; /* TODO */ + + ASSERT(UserDataLen <= MAX_USER_DEFINED_BYTES); + if (UserDataLen) + memcpy(openMsg->UserData, UserData, UserDataLen); + + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_add_tail(&openInfo->MsgListEntry, + &gVmbusConnection.ChannelMsgList); + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_DBG(VMBUS, "Sending channel open msg..."); + + ret = VmbusPostMessage(openMsg, + sizeof(struct vmbus_channel_open_channel)); + if (ret != 0) { + DPRINT_ERR(VMBUS, "unable to open channel - %d", ret); + goto Cleanup; + } + + /* FIXME: Need to time-out here */ + osd_WaitEventWait(openInfo->WaitEvent); + + if (openInfo->Response.OpenResult.Status == 0) + DPRINT_INFO(VMBUS, "channel <%p> open success!!", NewChannel); + else + DPRINT_INFO(VMBUS, "channel <%p> open failed - %d!!", + NewChannel, openInfo->Response.OpenResult.Status); + +Cleanup: + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_del(&openInfo->MsgListEntry); + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + kfree(openInfo->WaitEvent); + kfree(openInfo); + + DPRINT_EXIT(VMBUS); + + return 0; +} + +/** + * DumpGpadlBody - Dump the gpadl body message to the console for debugging purposes. + */ +static void DumpGpadlBody(struct vmbus_channel_gpadl_body *Gpadl, u32 Len) +{ + int i; + int pfnCount; + + pfnCount = (Len - sizeof(struct vmbus_channel_gpadl_body)) / + sizeof(u64); + DPRINT_DBG(VMBUS, "gpadl body - len %d pfn count %d", Len, pfnCount); + + for (i = 0; i < pfnCount; i++) + DPRINT_DBG(VMBUS, "gpadl body - %d) pfn %llu", + i, Gpadl->Pfn[i]); +} + +/** + * DumpGpadlHeader - Dump the gpadl header message to the console for debugging purposes. + */ +static void DumpGpadlHeader(struct vmbus_channel_gpadl_header *Gpadl) +{ + int i, j; + int pageCount; + + DPRINT_DBG(VMBUS, + "gpadl header - relid %d, range count %d, range buflen %d", + Gpadl->ChildRelId, Gpadl->RangeCount, Gpadl->RangeBufLen); + for (i = 0; i < Gpadl->RangeCount; i++) { + pageCount = Gpadl->Range[i].ByteCount >> PAGE_SHIFT; + pageCount = (pageCount > 26) ? 26 : pageCount; + + DPRINT_DBG(VMBUS, "gpadl range %d - len %d offset %d " + "page count %d", i, Gpadl->Range[i].ByteCount, + Gpadl->Range[i].ByteOffset, pageCount); + + for (j = 0; j < pageCount; j++) + DPRINT_DBG(VMBUS, "%d) pfn %llu", j, + Gpadl->Range[i].PfnArray[j]); + } +} + +/** + * VmbusChannelCreateGpadlHeader - Creates a gpadl for the specified buffer + */ +static int VmbusChannelCreateGpadlHeader(void *Kbuffer, u32 Size, + struct vmbus_channel_msginfo **MsgInfo, + u32 *MessageCount) +{ + int i; + int pageCount; + unsigned long long pfn; + struct vmbus_channel_gpadl_header *gpaHeader; + struct vmbus_channel_gpadl_body *gpadlBody; + struct vmbus_channel_msginfo *msgHeader; + struct vmbus_channel_msginfo *msgBody; + u32 msgSize; + + int pfnSum, pfnCount, pfnLeft, pfnCurr, pfnSize; + + /* ASSERT((kbuffer & (PAGE_SIZE-1)) == 0); */ + ASSERT((Size & (PAGE_SIZE-1)) == 0); + + pageCount = Size >> PAGE_SHIFT; + pfn = virt_to_phys(Kbuffer) >> PAGE_SHIFT; + + /* do we need a gpadl body msg */ + pfnSize = MAX_SIZE_CHANNEL_MESSAGE - + sizeof(struct vmbus_channel_gpadl_header) - + sizeof(struct gpa_range); + pfnCount = pfnSize / sizeof(u64); + + if (pageCount > pfnCount) { + /* we need a gpadl body */ + /* fill in the header */ + msgSize = sizeof(struct vmbus_channel_msginfo) + + sizeof(struct vmbus_channel_gpadl_header) + + sizeof(struct gpa_range) + pfnCount * sizeof(u64); + msgHeader = kzalloc(msgSize, GFP_KERNEL); + + INIT_LIST_HEAD(&msgHeader->SubMsgList); + msgHeader->MessageSize = msgSize; + + gpaHeader = (struct vmbus_channel_gpadl_header *)msgHeader->Msg; + gpaHeader->RangeCount = 1; + gpaHeader->RangeBufLen = sizeof(struct gpa_range) + + pageCount * sizeof(u64); + gpaHeader->Range[0].ByteOffset = 0; + gpaHeader->Range[0].ByteCount = Size; + for (i = 0; i < pfnCount; i++) + gpaHeader->Range[0].PfnArray[i] = pfn+i; + *MsgInfo = msgHeader; + *MessageCount = 1; + + pfnSum = pfnCount; + pfnLeft = pageCount - pfnCount; + + /* how many pfns can we fit */ + pfnSize = MAX_SIZE_CHANNEL_MESSAGE - + sizeof(struct vmbus_channel_gpadl_body); + pfnCount = pfnSize / sizeof(u64); + + /* fill in the body */ + while (pfnLeft) { + if (pfnLeft > pfnCount) + pfnCurr = pfnCount; + else + pfnCurr = pfnLeft; + + msgSize = sizeof(struct vmbus_channel_msginfo) + + sizeof(struct vmbus_channel_gpadl_body) + + pfnCurr * sizeof(u64); + msgBody = kzalloc(msgSize, GFP_KERNEL); + ASSERT(msgBody); + msgBody->MessageSize = msgSize; + (*MessageCount)++; + gpadlBody = + (struct vmbus_channel_gpadl_body *)msgBody->Msg; + + /* + * FIXME: + * Gpadl is u32 and we are using a pointer which could + * be 64-bit + */ + /* gpadlBody->Gpadl = kbuffer; */ + for (i = 0; i < pfnCurr; i++) + gpadlBody->Pfn[i] = pfn + pfnSum + i; + + /* add to msg header */ + list_add_tail(&msgBody->MsgListEntry, + &msgHeader->SubMsgList); + pfnSum += pfnCurr; + pfnLeft -= pfnCurr; + } + } else { + /* everything fits in a header */ + msgSize = sizeof(struct vmbus_channel_msginfo) + + sizeof(struct vmbus_channel_gpadl_header) + + sizeof(struct gpa_range) + pageCount * sizeof(u64); + msgHeader = kzalloc(msgSize, GFP_KERNEL); + msgHeader->MessageSize = msgSize; + + gpaHeader = (struct vmbus_channel_gpadl_header *)msgHeader->Msg; + gpaHeader->RangeCount = 1; + gpaHeader->RangeBufLen = sizeof(struct gpa_range) + + pageCount * sizeof(u64); + gpaHeader->Range[0].ByteOffset = 0; + gpaHeader->Range[0].ByteCount = Size; + for (i = 0; i < pageCount; i++) + gpaHeader->Range[0].PfnArray[i] = pfn+i; + + *MsgInfo = msgHeader; + *MessageCount = 1; + } + + return 0; +} + +/** + * VmbusChannelEstablishGpadl - Estabish a GPADL for the specified buffer + * + * @Channel: a channel + * @Kbuffer: from kmalloc + * @Size: page-size multiple + * @GpadlHandle: some funky thing + */ +int VmbusChannelEstablishGpadl(struct vmbus_channel *Channel, void *Kbuffer, + u32 Size, u32 *GpadlHandle) +{ + struct vmbus_channel_gpadl_header *gpadlMsg; + struct vmbus_channel_gpadl_body *gpadlBody; + /* struct vmbus_channel_gpadl_created *gpadlCreated; */ + struct vmbus_channel_msginfo *msgInfo; + struct vmbus_channel_msginfo *subMsgInfo; + u32 msgCount; + struct list_head *curr; + u32 nextGpadlHandle; + unsigned long flags; + int ret; + + DPRINT_ENTER(VMBUS); + + nextGpadlHandle = atomic_read(&gVmbusConnection.NextGpadlHandle); + atomic_inc(&gVmbusConnection.NextGpadlHandle); + + VmbusChannelCreateGpadlHeader(Kbuffer, Size, &msgInfo, &msgCount); + ASSERT(msgInfo != NULL); + ASSERT(msgCount > 0); + + msgInfo->WaitEvent = osd_WaitEventCreate(); + gpadlMsg = (struct vmbus_channel_gpadl_header *)msgInfo->Msg; + gpadlMsg->Header.MessageType = ChannelMessageGpadlHeader; + gpadlMsg->ChildRelId = Channel->OfferMsg.ChildRelId; + gpadlMsg->Gpadl = nextGpadlHandle; + + DumpGpadlHeader(gpadlMsg); + + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_add_tail(&msgInfo->MsgListEntry, + &gVmbusConnection.ChannelMsgList); + + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + DPRINT_DBG(VMBUS, "buffer %p, size %d msg cnt %d", + Kbuffer, Size, msgCount); + + DPRINT_DBG(VMBUS, "Sending GPADL Header - len %zd", + msgInfo->MessageSize - sizeof(*msgInfo)); + + ret = VmbusPostMessage(gpadlMsg, msgInfo->MessageSize - + sizeof(*msgInfo)); + if (ret != 0) { + DPRINT_ERR(VMBUS, "Unable to open channel - %d", ret); + goto Cleanup; + } + + if (msgCount > 1) { + list_for_each(curr, &msgInfo->SubMsgList) { + + /* FIXME: should this use list_entry() instead ? */ + subMsgInfo = (struct vmbus_channel_msginfo *)curr; + gpadlBody = + (struct vmbus_channel_gpadl_body *)subMsgInfo->Msg; + + gpadlBody->Header.MessageType = ChannelMessageGpadlBody; + gpadlBody->Gpadl = nextGpadlHandle; + + DPRINT_DBG(VMBUS, "Sending GPADL Body - len %zd", + subMsgInfo->MessageSize - + sizeof(*subMsgInfo)); + + DumpGpadlBody(gpadlBody, subMsgInfo->MessageSize - + sizeof(*subMsgInfo)); + ret = VmbusPostMessage(gpadlBody, + subMsgInfo->MessageSize - + sizeof(*subMsgInfo)); + ASSERT(ret == 0); + } + } + osd_WaitEventWait(msgInfo->WaitEvent); + + /* At this point, we received the gpadl created msg */ + DPRINT_DBG(VMBUS, "Received GPADL created " + "(relid %d, status %d handle %x)", + Channel->OfferMsg.ChildRelId, + msgInfo->Response.GpadlCreated.CreationStatus, + gpadlMsg->Gpadl); + + *GpadlHandle = gpadlMsg->Gpadl; + +Cleanup: + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_del(&msgInfo->MsgListEntry); + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + kfree(msgInfo->WaitEvent); + kfree(msgInfo); + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusChannelTeardownGpadl -Teardown the specified GPADL handle + */ +int VmbusChannelTeardownGpadl(struct vmbus_channel *Channel, u32 GpadlHandle) +{ + struct vmbus_channel_gpadl_teardown *msg; + struct vmbus_channel_msginfo *info; + unsigned long flags; + int ret; + + DPRINT_ENTER(VMBUS); + + ASSERT(GpadlHandle != 0); + + info = kmalloc(sizeof(*info) + + sizeof(struct vmbus_channel_gpadl_teardown), GFP_KERNEL); + ASSERT(info != NULL); + + info->WaitEvent = osd_WaitEventCreate(); + + msg = (struct vmbus_channel_gpadl_teardown *)info->Msg; + + msg->Header.MessageType = ChannelMessageGpadlTeardown; + msg->ChildRelId = Channel->OfferMsg.ChildRelId; + msg->Gpadl = GpadlHandle; + + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_add_tail(&info->MsgListEntry, + &gVmbusConnection.ChannelMsgList); + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + ret = VmbusPostMessage(msg, + sizeof(struct vmbus_channel_gpadl_teardown)); + if (ret != 0) { + /* TODO: */ + /* something... */ + } + + osd_WaitEventWait(info->WaitEvent); + + /* Received a torndown response */ + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_del(&info->MsgListEntry); + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + kfree(info->WaitEvent); + kfree(info); + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusChannelClose - Close the specified channel + */ +void VmbusChannelClose(struct vmbus_channel *Channel) +{ + struct vmbus_channel_close_channel *msg; + struct vmbus_channel_msginfo *info; + unsigned long flags; + int ret; + + DPRINT_ENTER(VMBUS); + + /* Stop callback and cancel the timer asap */ + Channel->OnChannelCallback = NULL; + del_timer(&Channel->poll_timer); + + /* Send a closing message */ + info = kmalloc(sizeof(*info) + + sizeof(struct vmbus_channel_close_channel), GFP_KERNEL); + ASSERT(info != NULL); + + /* info->waitEvent = osd_WaitEventCreate(); */ + + msg = (struct vmbus_channel_close_channel *)info->Msg; + msg->Header.MessageType = ChannelMessageCloseChannel; + msg->ChildRelId = Channel->OfferMsg.ChildRelId; + + ret = VmbusPostMessage(msg, sizeof(struct vmbus_channel_close_channel)); + if (ret != 0) { + /* TODO: */ + /* something... */ + } + + /* Tear down the gpadl for the channel's ring buffer */ + if (Channel->RingBufferGpadlHandle) + VmbusChannelTeardownGpadl(Channel, + Channel->RingBufferGpadlHandle); + + /* TODO: Send a msg to release the childRelId */ + + /* Cleanup the ring buffers for this channel */ + RingBufferCleanup(&Channel->Outbound); + RingBufferCleanup(&Channel->Inbound); + + osd_PageFree(Channel->RingBufferPages, Channel->RingBufferPageCount); + + kfree(info); + + /* + * If we are closing the channel during an error path in + * opening the channel, don't free the channel since the + * caller will free the channel + */ + + if (Channel->State == CHANNEL_OPEN_STATE) { + spin_lock_irqsave(&gVmbusConnection.channel_lock, flags); + list_del(&Channel->ListEntry); + spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags); + + FreeVmbusChannel(Channel); + } + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelSendPacket - Send the specified buffer on the given channel + */ +int VmbusChannelSendPacket(struct vmbus_channel *Channel, const void *Buffer, + u32 BufferLen, u64 RequestId, + enum vmbus_packet_type Type, u32 Flags) +{ + struct vmpacket_descriptor desc; + u32 packetLen = sizeof(struct vmpacket_descriptor) + BufferLen; + u32 packetLenAligned = ALIGN_UP(packetLen, sizeof(u64)); + struct scatterlist bufferList[3]; + u64 alignedData = 0; + int ret; + + DPRINT_ENTER(VMBUS); + DPRINT_DBG(VMBUS, "channel %p buffer %p len %d", + Channel, Buffer, BufferLen); + + DumpVmbusChannel(Channel); + + ASSERT((packetLenAligned - packetLen) < sizeof(u64)); + + /* Setup the descriptor */ + desc.Type = Type; /* VmbusPacketTypeDataInBand; */ + desc.Flags = Flags; /* VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; */ + /* in 8-bytes granularity */ + desc.DataOffset8 = sizeof(struct vmpacket_descriptor) >> 3; + desc.Length8 = (u16)(packetLenAligned >> 3); + desc.TransactionId = RequestId; + + sg_init_table(bufferList, 3); + sg_set_buf(&bufferList[0], &desc, sizeof(struct vmpacket_descriptor)); + sg_set_buf(&bufferList[1], Buffer, BufferLen); + sg_set_buf(&bufferList[2], &alignedData, packetLenAligned - packetLen); + + ret = RingBufferWrite(&Channel->Outbound, bufferList, 3); + + /* TODO: We should determine if this is optional */ + if (ret == 0 && !GetRingBufferInterruptMask(&Channel->Outbound)) + VmbusChannelSetEvent(Channel); + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusChannelSendPacketPageBuffer - Send a range of single-page buffer packets using a GPADL Direct packet type. + */ +int VmbusChannelSendPacketPageBuffer(struct vmbus_channel *Channel, + struct hv_page_buffer PageBuffers[], + u32 PageCount, void *Buffer, u32 BufferLen, + u64 RequestId) +{ + int ret; + int i; + struct VMBUS_CHANNEL_PACKET_PAGE_BUFFER desc; + u32 descSize; + u32 packetLen; + u32 packetLenAligned; + struct scatterlist bufferList[3]; + u64 alignedData = 0; + + DPRINT_ENTER(VMBUS); + + ASSERT(PageCount <= MAX_PAGE_BUFFER_COUNT); + + DumpVmbusChannel(Channel); + + /* + * Adjust the size down since VMBUS_CHANNEL_PACKET_PAGE_BUFFER is the + * largest size we support + */ + descSize = sizeof(struct VMBUS_CHANNEL_PACKET_PAGE_BUFFER) - + ((MAX_PAGE_BUFFER_COUNT - PageCount) * + sizeof(struct hv_page_buffer)); + packetLen = descSize + BufferLen; + packetLenAligned = ALIGN_UP(packetLen, sizeof(u64)); + + ASSERT((packetLenAligned - packetLen) < sizeof(u64)); + + /* Setup the descriptor */ + desc.Type = VmbusPacketTypeDataUsingGpaDirect; + desc.Flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; + desc.DataOffset8 = descSize >> 3; /* in 8-bytes grandularity */ + desc.Length8 = (u16)(packetLenAligned >> 3); + desc.TransactionId = RequestId; + desc.RangeCount = PageCount; + + for (i = 0; i < PageCount; i++) { + desc.Range[i].Length = PageBuffers[i].Length; + desc.Range[i].Offset = PageBuffers[i].Offset; + desc.Range[i].Pfn = PageBuffers[i].Pfn; + } + + sg_init_table(bufferList, 3); + sg_set_buf(&bufferList[0], &desc, descSize); + sg_set_buf(&bufferList[1], Buffer, BufferLen); + sg_set_buf(&bufferList[2], &alignedData, packetLenAligned - packetLen); + + ret = RingBufferWrite(&Channel->Outbound, bufferList, 3); + + /* TODO: We should determine if this is optional */ + if (ret == 0 && !GetRingBufferInterruptMask(&Channel->Outbound)) + VmbusChannelSetEvent(Channel); + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusChannelSendPacketMultiPageBuffer - Send a multi-page buffer packet using a GPADL Direct packet type. + */ +int VmbusChannelSendPacketMultiPageBuffer(struct vmbus_channel *Channel, + struct hv_multipage_buffer *MultiPageBuffer, + void *Buffer, u32 BufferLen, u64 RequestId) +{ + int ret; + struct VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER desc; + u32 descSize; + u32 packetLen; + u32 packetLenAligned; + struct scatterlist bufferList[3]; + u64 alignedData = 0; + u32 PfnCount = NUM_PAGES_SPANNED(MultiPageBuffer->Offset, + MultiPageBuffer->Length); + + DPRINT_ENTER(VMBUS); + + DumpVmbusChannel(Channel); + + DPRINT_DBG(VMBUS, "data buffer - offset %u len %u pfn count %u", + MultiPageBuffer->Offset, MultiPageBuffer->Length, PfnCount); + + ASSERT(PfnCount > 0); + ASSERT(PfnCount <= MAX_MULTIPAGE_BUFFER_COUNT); + + /* + * Adjust the size down since VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER is + * the largest size we support + */ + descSize = sizeof(struct VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER) - + ((MAX_MULTIPAGE_BUFFER_COUNT - PfnCount) * + sizeof(u64)); + packetLen = descSize + BufferLen; + packetLenAligned = ALIGN_UP(packetLen, sizeof(u64)); + + ASSERT((packetLenAligned - packetLen) < sizeof(u64)); + + /* Setup the descriptor */ + desc.Type = VmbusPacketTypeDataUsingGpaDirect; + desc.Flags = VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED; + desc.DataOffset8 = descSize >> 3; /* in 8-bytes grandularity */ + desc.Length8 = (u16)(packetLenAligned >> 3); + desc.TransactionId = RequestId; + desc.RangeCount = 1; + + desc.Range.Length = MultiPageBuffer->Length; + desc.Range.Offset = MultiPageBuffer->Offset; + + memcpy(desc.Range.PfnArray, MultiPageBuffer->PfnArray, + PfnCount * sizeof(u64)); + + sg_init_table(bufferList, 3); + sg_set_buf(&bufferList[0], &desc, descSize); + sg_set_buf(&bufferList[1], Buffer, BufferLen); + sg_set_buf(&bufferList[2], &alignedData, packetLenAligned - packetLen); + + ret = RingBufferWrite(&Channel->Outbound, bufferList, 3); + + /* TODO: We should determine if this is optional */ + if (ret == 0 && !GetRingBufferInterruptMask(&Channel->Outbound)) + VmbusChannelSetEvent(Channel); + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusChannelRecvPacket - Retrieve the user packet on the specified channel + */ +/* TODO: Do we ever receive a gpa direct packet other than the ones we send ? */ +int VmbusChannelRecvPacket(struct vmbus_channel *Channel, void *Buffer, + u32 BufferLen, u32 *BufferActualLen, u64 *RequestId) +{ + struct vmpacket_descriptor desc; + u32 packetLen; + u32 userLen; + int ret; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + *BufferActualLen = 0; + *RequestId = 0; + + spin_lock_irqsave(&Channel->inbound_lock, flags); + + ret = RingBufferPeek(&Channel->Inbound, &desc, + sizeof(struct vmpacket_descriptor)); + if (ret != 0) { + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + /* DPRINT_DBG(VMBUS, "nothing to read!!"); */ + DPRINT_EXIT(VMBUS); + return 0; + } + + /* VmbusChannelClearEvent(Channel); */ + + packetLen = desc.Length8 << 3; + userLen = packetLen - (desc.DataOffset8 << 3); + /* ASSERT(userLen > 0); */ + + DPRINT_DBG(VMBUS, "packet received on channel %p relid %d <type %d " + "flag %d tid %llx pktlen %d datalen %d> ", + Channel, Channel->OfferMsg.ChildRelId, desc.Type, + desc.Flags, desc.TransactionId, packetLen, userLen); + + *BufferActualLen = userLen; + + if (userLen > BufferLen) { + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + DPRINT_ERR(VMBUS, "buffer too small - got %d needs %d", + BufferLen, userLen); + DPRINT_EXIT(VMBUS); + + return -1; + } + + *RequestId = desc.TransactionId; + + /* Copy over the packet to the user buffer */ + ret = RingBufferRead(&Channel->Inbound, Buffer, userLen, + (desc.DataOffset8 << 3)); + + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + DPRINT_EXIT(VMBUS); + + return 0; +} + +/** + * VmbusChannelRecvPacketRaw - Retrieve the raw packet on the specified channel + */ +int VmbusChannelRecvPacketRaw(struct vmbus_channel *Channel, void *Buffer, + u32 BufferLen, u32 *BufferActualLen, + u64 *RequestId) +{ + struct vmpacket_descriptor desc; + u32 packetLen; + u32 userLen; + int ret; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + *BufferActualLen = 0; + *RequestId = 0; + + spin_lock_irqsave(&Channel->inbound_lock, flags); + + ret = RingBufferPeek(&Channel->Inbound, &desc, + sizeof(struct vmpacket_descriptor)); + if (ret != 0) { + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + /* DPRINT_DBG(VMBUS, "nothing to read!!"); */ + DPRINT_EXIT(VMBUS); + return 0; + } + + /* VmbusChannelClearEvent(Channel); */ + + packetLen = desc.Length8 << 3; + userLen = packetLen - (desc.DataOffset8 << 3); + + DPRINT_DBG(VMBUS, "packet received on channel %p relid %d <type %d " + "flag %d tid %llx pktlen %d datalen %d> ", + Channel, Channel->OfferMsg.ChildRelId, desc.Type, + desc.Flags, desc.TransactionId, packetLen, userLen); + + *BufferActualLen = packetLen; + + if (packetLen > BufferLen) { + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + DPRINT_ERR(VMBUS, "buffer too small - needed %d bytes but " + "got space for only %d bytes", packetLen, BufferLen); + DPRINT_EXIT(VMBUS); + return -2; + } + + *RequestId = desc.TransactionId; + + /* Copy over the entire packet to the user buffer */ + ret = RingBufferRead(&Channel->Inbound, Buffer, packetLen, 0); + + spin_unlock_irqrestore(&Channel->inbound_lock, flags); + + DPRINT_EXIT(VMBUS); + + return 0; +} + +/** + * VmbusChannelOnChannelEvent - Channel event callback + */ +void VmbusChannelOnChannelEvent(struct vmbus_channel *Channel) +{ + DumpVmbusChannel(Channel); + ASSERT(Channel->OnChannelCallback); +#ifdef ENABLE_POLLING + del_timer(&Channel->poll_timer); + Channel->OnChannelCallback(Channel->ChannelCallbackContext); + channel->poll_timer.expires(jiffies + usecs_to_jiffies(100); + add_timer(&channel->poll_timer); +#else + Channel->OnChannelCallback(Channel->ChannelCallbackContext); +#endif +} + +/** + * VmbusChannelOnTimer - Timer event callback + */ +void VmbusChannelOnTimer(unsigned long data) +{ + struct vmbus_channel *channel = (struct vmbus_channel *)data; + + if (channel->OnChannelCallback) { + channel->OnChannelCallback(channel->ChannelCallbackContext); +#ifdef ENABLE_POLLING + channel->poll_timer.expires(jiffies + usecs_to_jiffies(100); + add_timer(&channel->poll_timer); +#endif + } +} + +/** + * DumpVmbusChannel - Dump vmbus channel info to the console + */ +static void DumpVmbusChannel(struct vmbus_channel *Channel) +{ + DPRINT_DBG(VMBUS, "Channel (%d)", Channel->OfferMsg.ChildRelId); + DumpRingInfo(&Channel->Outbound, "Outbound "); + DumpRingInfo(&Channel->Inbound, "Inbound "); +} diff --git a/drivers/staging/hv/Channel.h b/drivers/staging/hv/Channel.h new file mode 100644 index 00000000000..6b283edcae6 --- /dev/null +++ b/drivers/staging/hv/Channel.h @@ -0,0 +1,112 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _CHANNEL_H_ +#define _CHANNEL_H_ + +#include "ChannelMgmt.h" + +/* The format must be the same as struct vmdata_gpa_direct */ +struct VMBUS_CHANNEL_PACKET_PAGE_BUFFER { + u16 Type; + u16 DataOffset8; + u16 Length8; + u16 Flags; + u64 TransactionId; + u32 Reserved; + u32 RangeCount; + struct hv_page_buffer Range[MAX_PAGE_BUFFER_COUNT]; +} __attribute__((packed)); + +/* The format must be the same as struct vmdata_gpa_direct */ +struct VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER { + u16 Type; + u16 DataOffset8; + u16 Length8; + u16 Flags; + u64 TransactionId; + u32 Reserved; + u32 RangeCount; /* Always 1 in this case */ + struct hv_multipage_buffer Range; +} __attribute__((packed)); + + +extern int VmbusChannelOpen(struct vmbus_channel *channel, + u32 SendRingBufferSize, + u32 RecvRingBufferSize, + void *UserData, + u32 UserDataLen, + void(*OnChannelCallback)(void *context), + void *Context); + +extern void VmbusChannelClose(struct vmbus_channel *channel); + +extern int VmbusChannelSendPacket(struct vmbus_channel *channel, + const void *Buffer, + u32 BufferLen, + u64 RequestId, + enum vmbus_packet_type Type, + u32 Flags); + +extern int VmbusChannelSendPacketPageBuffer(struct vmbus_channel *channel, + struct hv_page_buffer PageBuffers[], + u32 PageCount, + void *Buffer, + u32 BufferLen, + u64 RequestId); + +extern int VmbusChannelSendPacketMultiPageBuffer(struct vmbus_channel *channel, + struct hv_multipage_buffer *mpb, + void *Buffer, + u32 BufferLen, + u64 RequestId); + +extern int VmbusChannelEstablishGpadl(struct vmbus_channel *channel, + void *Kbuffer, + u32 Size, + u32 *GpadlHandle); + +extern int VmbusChannelTeardownGpadl(struct vmbus_channel *channel, + u32 GpadlHandle); + +extern int VmbusChannelRecvPacket(struct vmbus_channel *channel, + void *Buffer, + u32 BufferLen, + u32 *BufferActualLen, + u64 *RequestId); + +extern int VmbusChannelRecvPacketRaw(struct vmbus_channel *channel, + void *Buffer, + u32 BufferLen, + u32 *BufferActualLen, + u64 *RequestId); + +extern void VmbusChannelOnChannelEvent(struct vmbus_channel *channel); + +extern void VmbusChannelGetDebugInfo(struct vmbus_channel *channel, + struct vmbus_channel_debug_info *debug); + +extern void VmbusChannelOnTimer(unsigned long data); + +#endif /* _CHANNEL_H_ */ diff --git a/drivers/staging/hv/ChannelInterface.c b/drivers/staging/hv/ChannelInterface.c new file mode 100644 index 00000000000..019b064f7cb --- /dev/null +++ b/drivers/staging/hv/ChannelInterface.c @@ -0,0 +1,152 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include "osd.h" +#include "VmbusPrivate.h" + +static int IVmbusChannelOpen(struct hv_device *device, u32 SendBufferSize, + u32 RecvRingBufferSize, void *UserData, + u32 UserDataLen, + void (*ChannelCallback)(void *context), + void *Context) +{ + return VmbusChannelOpen(device->context, SendBufferSize, + RecvRingBufferSize, UserData, UserDataLen, + ChannelCallback, Context); +} + +static void IVmbusChannelClose(struct hv_device *device) +{ + VmbusChannelClose(device->context); +} + +static int IVmbusChannelSendPacket(struct hv_device *device, const void *Buffer, + u32 BufferLen, u64 RequestId, u32 Type, + u32 Flags) +{ + return VmbusChannelSendPacket(device->context, Buffer, BufferLen, + RequestId, Type, Flags); +} + +static int IVmbusChannelSendPacketPageBuffer(struct hv_device *device, + struct hv_page_buffer PageBuffers[], + u32 PageCount, void *Buffer, + u32 BufferLen, u64 RequestId) +{ + return VmbusChannelSendPacketPageBuffer(device->context, PageBuffers, + PageCount, Buffer, BufferLen, + RequestId); +} + +static int IVmbusChannelSendPacketMultiPageBuffer(struct hv_device *device, + struct hv_multipage_buffer *MultiPageBuffer, + void *Buffer, u32 BufferLen, u64 RequestId) +{ + return VmbusChannelSendPacketMultiPageBuffer(device->context, + MultiPageBuffer, Buffer, + BufferLen, RequestId); +} + +static int IVmbusChannelRecvPacket(struct hv_device *device, void *Buffer, + u32 BufferLen, u32 *BufferActualLen, + u64 *RequestId) +{ + return VmbusChannelRecvPacket(device->context, Buffer, BufferLen, + BufferActualLen, RequestId); +} + +static int IVmbusChannelRecvPacketRaw(struct hv_device *device, void *Buffer, + u32 BufferLen, u32 *BufferActualLen, + u64 *RequestId) +{ + return VmbusChannelRecvPacketRaw(device->context, Buffer, BufferLen, + BufferActualLen, RequestId); +} + +static int IVmbusChannelEstablishGpadl(struct hv_device *device, void *Buffer, + u32 BufferLen, u32 *GpadlHandle) +{ + return VmbusChannelEstablishGpadl(device->context, Buffer, BufferLen, + GpadlHandle); +} + +static int IVmbusChannelTeardownGpadl(struct hv_device *device, u32 GpadlHandle) +{ + return VmbusChannelTeardownGpadl(device->context, GpadlHandle); + +} + +void GetChannelInterface(struct vmbus_channel_interface *iface) +{ + iface->Open = IVmbusChannelOpen; + iface->Close = IVmbusChannelClose; + iface->SendPacket = IVmbusChannelSendPacket; + iface->SendPacketPageBuffer = IVmbusChannelSendPacketPageBuffer; + iface->SendPacketMultiPageBuffer = + IVmbusChannelSendPacketMultiPageBuffer; + iface->RecvPacket = IVmbusChannelRecvPacket; + iface->RecvPacketRaw = IVmbusChannelRecvPacketRaw; + iface->EstablishGpadl = IVmbusChannelEstablishGpadl; + iface->TeardownGpadl = IVmbusChannelTeardownGpadl; + iface->GetInfo = GetChannelInfo; +} + +void GetChannelInfo(struct hv_device *device, struct hv_device_info *info) +{ + struct vmbus_channel_debug_info debugInfo; + + if (!device->context) + return; + + VmbusChannelGetDebugInfo(device->context, &debugInfo); + + info->ChannelId = debugInfo.RelId; + info->ChannelState = debugInfo.State; + memcpy(&info->ChannelType, &debugInfo.InterfaceType, + sizeof(struct hv_guid)); + memcpy(&info->ChannelInstance, &debugInfo.InterfaceInstance, + sizeof(struct hv_guid)); + + info->MonitorId = debugInfo.MonitorId; + + info->ServerMonitorPending = debugInfo.ServerMonitorPending; + info->ServerMonitorLatency = debugInfo.ServerMonitorLatency; + info->ServerMonitorConnectionId = debugInfo.ServerMonitorConnectionId; + + info->ClientMonitorPending = debugInfo.ClientMonitorPending; + info->ClientMonitorLatency = debugInfo.ClientMonitorLatency; + info->ClientMonitorConnectionId = debugInfo.ClientMonitorConnectionId; + + info->Inbound.InterruptMask = debugInfo.Inbound.CurrentInterruptMask; + info->Inbound.ReadIndex = debugInfo.Inbound.CurrentReadIndex; + info->Inbound.WriteIndex = debugInfo.Inbound.CurrentWriteIndex; + info->Inbound.BytesAvailToRead = debugInfo.Inbound.BytesAvailToRead; + info->Inbound.BytesAvailToWrite = debugInfo.Inbound.BytesAvailToWrite; + + info->Outbound.InterruptMask = debugInfo.Outbound.CurrentInterruptMask; + info->Outbound.ReadIndex = debugInfo.Outbound.CurrentReadIndex; + info->Outbound.WriteIndex = debugInfo.Outbound.CurrentWriteIndex; + info->Outbound.BytesAvailToRead = debugInfo.Outbound.BytesAvailToRead; + info->Outbound.BytesAvailToWrite = debugInfo.Outbound.BytesAvailToWrite; +} diff --git a/drivers/staging/hv/ChannelInterface.h b/drivers/staging/hv/ChannelInterface.h new file mode 100644 index 00000000000..27b7a253b71 --- /dev/null +++ b/drivers/staging/hv/ChannelInterface.h @@ -0,0 +1,35 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _CHANNEL_INTERFACE_H_ +#define _CHANNEL_INTERFACE_H_ + +#include "VmbusApi.h" + +void GetChannelInterface(struct vmbus_channel_interface *ChannelInterface); + +void GetChannelInfo(struct hv_device *Device, + struct hv_device_info *DeviceInfo); + +#endif /* _CHANNEL_INTERFACE_H_ */ diff --git a/drivers/staging/hv/ChannelMgmt.c b/drivers/staging/hv/ChannelMgmt.c new file mode 100644 index 00000000000..3db62caedcf --- /dev/null +++ b/drivers/staging/hv/ChannelMgmt.c @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/list.h> +#include "osd.h" +#include "logging.h" +#include "VmbusPrivate.h" + +struct vmbus_channel_message_table_entry { + enum vmbus_channel_message_type messageType; + void (*messageHandler)(struct vmbus_channel_message_header *msg); +}; + +#define MAX_NUM_DEVICE_CLASSES_SUPPORTED 4 +static const struct hv_guid + gSupportedDeviceClasses[MAX_NUM_DEVICE_CLASSES_SUPPORTED] = { + /* {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} */ + /* Storage - SCSI */ + { + .data = { + 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f + } + }, + + /* {F8615163-DF3E-46c5-913F-F2D2F965ED0E} */ + /* Network */ + { + .data = { + 0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, + 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E + } + }, + + /* {CFA8B69E-5B4A-4cc0-B98B-8BA1A1F3F95A} */ + /* Input */ + { + .data = { + 0x9E, 0xB6, 0xA8, 0xCF, 0x4A, 0x5B, 0xc0, 0x4c, + 0xB9, 0x8B, 0x8B, 0xA1, 0xA1, 0xF3, 0xF9, 0x5A + } + }, + + /* {32412632-86cb-44a2-9b5c-50d1417354f5} */ + /* IDE */ + { + .data = { + 0x32, 0x26, 0x41, 0x32, 0xcb, 0x86, 0xa2, 0x44, + 0x9b, 0x5c, 0x50, 0xd1, 0x41, 0x73, 0x54, 0xf5 + } + }, +}; + +/** + * AllocVmbusChannel - Allocate and initialize a vmbus channel object + */ +struct vmbus_channel *AllocVmbusChannel(void) +{ + struct vmbus_channel *channel; + + channel = kzalloc(sizeof(*channel), GFP_ATOMIC); + if (!channel) + return NULL; + + spin_lock_init(&channel->inbound_lock); + + init_timer(&channel->poll_timer); + channel->poll_timer.data = (unsigned long)channel; + channel->poll_timer.function = VmbusChannelOnTimer; + + channel->ControlWQ = create_workqueue("hv_vmbus_ctl"); + if (!channel->ControlWQ) { + kfree(channel); + return NULL; + } + + return channel; +} + +/** + * ReleaseVmbusChannel - Release the vmbus channel object itself + */ +static inline void ReleaseVmbusChannel(void *context) +{ + struct vmbus_channel *channel = context; + + DPRINT_ENTER(VMBUS); + + DPRINT_DBG(VMBUS, "releasing channel (%p)", channel); + destroy_workqueue(channel->ControlWQ); + DPRINT_DBG(VMBUS, "channel released (%p)", channel); + + kfree(channel); + + DPRINT_EXIT(VMBUS); +} + +/** + * FreeVmbusChannel - Release the resources used by the vmbus channel object + */ +void FreeVmbusChannel(struct vmbus_channel *Channel) +{ + del_timer(&Channel->poll_timer); + + /* + * We have to release the channel's workqueue/thread in the vmbus's + * workqueue/thread context + * ie we can't destroy ourselves. + */ + osd_schedule_callback(gVmbusConnection.WorkQueue, ReleaseVmbusChannel, + Channel); +} + +/** + * VmbusChannelProcessOffer - Process the offer by creating a channel/device associated with this offer + */ +static void VmbusChannelProcessOffer(void *context) +{ + struct vmbus_channel *newChannel = context; + struct vmbus_channel *channel; + bool fNew = true; + int ret; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + /* Make sure this is a new offer */ + spin_lock_irqsave(&gVmbusConnection.channel_lock, flags); + + list_for_each_entry(channel, &gVmbusConnection.ChannelList, ListEntry) { + if (!memcmp(&channel->OfferMsg.Offer.InterfaceType, + &newChannel->OfferMsg.Offer.InterfaceType, + sizeof(struct hv_guid)) && + !memcmp(&channel->OfferMsg.Offer.InterfaceInstance, + &newChannel->OfferMsg.Offer.InterfaceInstance, + sizeof(struct hv_guid))) { + fNew = false; + break; + } + } + + if (fNew) + list_add_tail(&newChannel->ListEntry, + &gVmbusConnection.ChannelList); + + spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags); + + if (!fNew) { + DPRINT_DBG(VMBUS, "Ignoring duplicate offer for relid (%d)", + newChannel->OfferMsg.ChildRelId); + FreeVmbusChannel(newChannel); + DPRINT_EXIT(VMBUS); + return; + } + + /* + * Start the process of binding this offer to the driver + * We need to set the DeviceObject field before calling + * VmbusChildDeviceAdd() + */ + newChannel->DeviceObject = VmbusChildDeviceCreate( + &newChannel->OfferMsg.Offer.InterfaceType, + &newChannel->OfferMsg.Offer.InterfaceInstance, + newChannel); + + DPRINT_DBG(VMBUS, "child device object allocated - %p", + newChannel->DeviceObject); + + /* + * Add the new device to the bus. This will kick off device-driver + * binding which eventually invokes the device driver's AddDevice() + * method. + */ + ret = VmbusChildDeviceAdd(newChannel->DeviceObject); + if (ret != 0) { + DPRINT_ERR(VMBUS, + "unable to add child device object (relid %d)", + newChannel->OfferMsg.ChildRelId); + + spin_lock_irqsave(&gVmbusConnection.channel_lock, flags); + list_del(&newChannel->ListEntry); + spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags); + + FreeVmbusChannel(newChannel); + } else { + /* + * This state is used to indicate a successful open + * so that when we do close the channel normally, we + * can cleanup properly + */ + newChannel->State = CHANNEL_OPEN_STATE; + } + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelProcessRescindOffer - Rescind the offer by initiating a device removal + */ +static void VmbusChannelProcessRescindOffer(void *context) +{ + struct vmbus_channel *channel = context; + + DPRINT_ENTER(VMBUS); + VmbusChildDeviceRemove(channel->DeviceObject); + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnOffer - Handler for channel offers from vmbus in parent partition. + * + * We ignore all offers except network and storage offers. For each network and + * storage offers, we create a channel object and queue a work item to the + * channel object to process the offer synchronously + */ +static void VmbusChannelOnOffer(struct vmbus_channel_message_header *hdr) +{ + struct vmbus_channel_offer_channel *offer; + struct vmbus_channel *newChannel; + struct hv_guid *guidType; + struct hv_guid *guidInstance; + int i; + int fSupported = 0; + + DPRINT_ENTER(VMBUS); + + offer = (struct vmbus_channel_offer_channel *)hdr; + for (i = 0; i < MAX_NUM_DEVICE_CLASSES_SUPPORTED; i++) { + if (memcmp(&offer->Offer.InterfaceType, + &gSupportedDeviceClasses[i], sizeof(struct hv_guid)) == 0) { + fSupported = 1; + break; + } + } + + if (!fSupported) { + DPRINT_DBG(VMBUS, "Ignoring channel offer notification for " + "child relid %d", offer->ChildRelId); + DPRINT_EXIT(VMBUS); + return; + } + + guidType = &offer->Offer.InterfaceType; + guidInstance = &offer->Offer.InterfaceInstance; + + DPRINT_INFO(VMBUS, "Channel offer notification - " + "child relid %d monitor id %d allocated %d, " + "type {%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x} " + "instance {%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}", + offer->ChildRelId, offer->MonitorId, + offer->MonitorAllocated, + guidType->data[3], guidType->data[2], + guidType->data[1], guidType->data[0], + guidType->data[5], guidType->data[4], + guidType->data[7], guidType->data[6], + guidType->data[8], guidType->data[9], + guidType->data[10], guidType->data[11], + guidType->data[12], guidType->data[13], + guidType->data[14], guidType->data[15], + guidInstance->data[3], guidInstance->data[2], + guidInstance->data[1], guidInstance->data[0], + guidInstance->data[5], guidInstance->data[4], + guidInstance->data[7], guidInstance->data[6], + guidInstance->data[8], guidInstance->data[9], + guidInstance->data[10], guidInstance->data[11], + guidInstance->data[12], guidInstance->data[13], + guidInstance->data[14], guidInstance->data[15]); + + /* Allocate the channel object and save this offer. */ + newChannel = AllocVmbusChannel(); + if (!newChannel) { + DPRINT_ERR(VMBUS, "unable to allocate channel object"); + return; + } + + DPRINT_DBG(VMBUS, "channel object allocated - %p", newChannel); + + memcpy(&newChannel->OfferMsg, offer, + sizeof(struct vmbus_channel_offer_channel)); + newChannel->MonitorGroup = (u8)offer->MonitorId / 32; + newChannel->MonitorBit = (u8)offer->MonitorId % 32; + + /* TODO: Make sure the offer comes from our parent partition */ + osd_schedule_callback(newChannel->ControlWQ, VmbusChannelProcessOffer, + newChannel); + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnOfferRescind - Rescind offer handler. + * + * We queue a work item to process this offer synchronously + */ +static void VmbusChannelOnOfferRescind(struct vmbus_channel_message_header *hdr) +{ + struct vmbus_channel_rescind_offer *rescind; + struct vmbus_channel *channel; + + DPRINT_ENTER(VMBUS); + + rescind = (struct vmbus_channel_rescind_offer *)hdr; + channel = GetChannelFromRelId(rescind->ChildRelId); + if (channel == NULL) { + DPRINT_DBG(VMBUS, "channel not found for relId %d", + rescind->ChildRelId); + return; + } + + osd_schedule_callback(channel->ControlWQ, + VmbusChannelProcessRescindOffer, + channel); + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnOffersDelivered - This is invoked when all offers have been delivered. + * + * Nothing to do here. + */ +static void VmbusChannelOnOffersDelivered( + struct vmbus_channel_message_header *hdr) +{ + DPRINT_ENTER(VMBUS); + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnOpenResult - Open result handler. + * + * This is invoked when we received a response to our channel open request. + * Find the matching request, copy the response and signal the requesting + * thread. + */ +static void VmbusChannelOnOpenResult(struct vmbus_channel_message_header *hdr) +{ + struct vmbus_channel_open_result *result; + struct list_head *curr; + struct vmbus_channel_msginfo *msgInfo; + struct vmbus_channel_message_header *requestHeader; + struct vmbus_channel_open_channel *openMsg; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + result = (struct vmbus_channel_open_result *)hdr; + DPRINT_DBG(VMBUS, "vmbus open result - %d", result->Status); + + /* + * Find the open msg, copy the result and signal/unblock the wait event + */ + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + + list_for_each(curr, &gVmbusConnection.ChannelMsgList) { +/* FIXME: this should probably use list_entry() instead */ + msgInfo = (struct vmbus_channel_msginfo *)curr; + requestHeader = (struct vmbus_channel_message_header *)msgInfo->Msg; + + if (requestHeader->MessageType == ChannelMessageOpenChannel) { + openMsg = (struct vmbus_channel_open_channel *)msgInfo->Msg; + if (openMsg->ChildRelId == result->ChildRelId && + openMsg->OpenId == result->OpenId) { + memcpy(&msgInfo->Response.OpenResult, + result, + sizeof(struct vmbus_channel_open_result)); + osd_WaitEventSet(msgInfo->WaitEvent); + break; + } + } + } + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnGpadlCreated - GPADL created handler. + * + * This is invoked when we received a response to our gpadl create request. + * Find the matching request, copy the response and signal the requesting + * thread. + */ +static void VmbusChannelOnGpadlCreated(struct vmbus_channel_message_header *hdr) +{ + struct vmbus_channel_gpadl_created *gpadlCreated; + struct list_head *curr; + struct vmbus_channel_msginfo *msgInfo; + struct vmbus_channel_message_header *requestHeader; + struct vmbus_channel_gpadl_header *gpadlHeader; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + gpadlCreated = (struct vmbus_channel_gpadl_created *)hdr; + DPRINT_DBG(VMBUS, "vmbus gpadl created result - %d", + gpadlCreated->CreationStatus); + + /* + * Find the establish msg, copy the result and signal/unblock the wait + * event + */ + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + + list_for_each(curr, &gVmbusConnection.ChannelMsgList) { +/* FIXME: this should probably use list_entry() instead */ + msgInfo = (struct vmbus_channel_msginfo *)curr; + requestHeader = (struct vmbus_channel_message_header *)msgInfo->Msg; + + if (requestHeader->MessageType == ChannelMessageGpadlHeader) { + gpadlHeader = (struct vmbus_channel_gpadl_header *)requestHeader; + + if ((gpadlCreated->ChildRelId == + gpadlHeader->ChildRelId) && + (gpadlCreated->Gpadl == gpadlHeader->Gpadl)) { + memcpy(&msgInfo->Response.GpadlCreated, + gpadlCreated, + sizeof(struct vmbus_channel_gpadl_created)); + osd_WaitEventSet(msgInfo->WaitEvent); + break; + } + } + } + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnGpadlTorndown - GPADL torndown handler. + * + * This is invoked when we received a response to our gpadl teardown request. + * Find the matching request, copy the response and signal the requesting + * thread. + */ +static void VmbusChannelOnGpadlTorndown( + struct vmbus_channel_message_header *hdr) +{ + struct vmbus_channel_gpadl_torndown *gpadlTorndown; + struct list_head *curr; + struct vmbus_channel_msginfo *msgInfo; + struct vmbus_channel_message_header *requestHeader; + struct vmbus_channel_gpadl_teardown *gpadlTeardown; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + gpadlTorndown = (struct vmbus_channel_gpadl_torndown *)hdr; + + /* + * Find the open msg, copy the result and signal/unblock the wait event + */ + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + + list_for_each(curr, &gVmbusConnection.ChannelMsgList) { +/* FIXME: this should probably use list_entry() instead */ + msgInfo = (struct vmbus_channel_msginfo *)curr; + requestHeader = (struct vmbus_channel_message_header *)msgInfo->Msg; + + if (requestHeader->MessageType == ChannelMessageGpadlTeardown) { + gpadlTeardown = (struct vmbus_channel_gpadl_teardown *)requestHeader; + + if (gpadlTorndown->Gpadl == gpadlTeardown->Gpadl) { + memcpy(&msgInfo->Response.GpadlTorndown, + gpadlTorndown, + sizeof(struct vmbus_channel_gpadl_torndown)); + osd_WaitEventSet(msgInfo->WaitEvent); + break; + } + } + } + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelOnVersionResponse - Version response handler + * + * This is invoked when we received a response to our initiate contact request. + * Find the matching request, copy the response and signal the requesting + * thread. + */ +static void VmbusChannelOnVersionResponse( + struct vmbus_channel_message_header *hdr) +{ + struct list_head *curr; + struct vmbus_channel_msginfo *msgInfo; + struct vmbus_channel_message_header *requestHeader; + struct vmbus_channel_initiate_contact *initiate; + struct vmbus_channel_version_response *versionResponse; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + versionResponse = (struct vmbus_channel_version_response *)hdr; + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + + list_for_each(curr, &gVmbusConnection.ChannelMsgList) { +/* FIXME: this should probably use list_entry() instead */ + msgInfo = (struct vmbus_channel_msginfo *)curr; + requestHeader = (struct vmbus_channel_message_header *)msgInfo->Msg; + + if (requestHeader->MessageType == + ChannelMessageInitiateContact) { + initiate = (struct vmbus_channel_initiate_contact *)requestHeader; + memcpy(&msgInfo->Response.VersionResponse, + versionResponse, + sizeof(struct vmbus_channel_version_response)); + osd_WaitEventSet(msgInfo->WaitEvent); + } + } + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_EXIT(VMBUS); +} + +/* Channel message dispatch table */ +static struct vmbus_channel_message_table_entry + gChannelMessageTable[ChannelMessageCount] = { + {ChannelMessageInvalid, NULL}, + {ChannelMessageOfferChannel, VmbusChannelOnOffer}, + {ChannelMessageRescindChannelOffer, VmbusChannelOnOfferRescind}, + {ChannelMessageRequestOffers, NULL}, + {ChannelMessageAllOffersDelivered, VmbusChannelOnOffersDelivered}, + {ChannelMessageOpenChannel, NULL}, + {ChannelMessageOpenChannelResult, VmbusChannelOnOpenResult}, + {ChannelMessageCloseChannel, NULL}, + {ChannelMessageGpadlHeader, NULL}, + {ChannelMessageGpadlBody, NULL}, + {ChannelMessageGpadlCreated, VmbusChannelOnGpadlCreated}, + {ChannelMessageGpadlTeardown, NULL}, + {ChannelMessageGpadlTorndown, VmbusChannelOnGpadlTorndown}, + {ChannelMessageRelIdReleased, NULL}, + {ChannelMessageInitiateContact, NULL}, + {ChannelMessageVersionResponse, VmbusChannelOnVersionResponse}, + {ChannelMessageUnload, NULL}, +}; + +/** + * VmbusOnChannelMessage - Handler for channel protocol messages. + * + * This is invoked in the vmbus worker thread context. + */ +void VmbusOnChannelMessage(void *Context) +{ + struct hv_message *msg = Context; + struct vmbus_channel_message_header *hdr; + int size; + + DPRINT_ENTER(VMBUS); + + hdr = (struct vmbus_channel_message_header *)msg->u.Payload; + size = msg->Header.PayloadSize; + + DPRINT_DBG(VMBUS, "message type %d size %d", hdr->MessageType, size); + + if (hdr->MessageType >= ChannelMessageCount) { + DPRINT_ERR(VMBUS, + "Received invalid channel message type %d size %d", + hdr->MessageType, size); + print_hex_dump_bytes("", DUMP_PREFIX_NONE, + (unsigned char *)msg->u.Payload, size); + kfree(msg); + return; + } + + if (gChannelMessageTable[hdr->MessageType].messageHandler) + gChannelMessageTable[hdr->MessageType].messageHandler(hdr); + else + DPRINT_ERR(VMBUS, "Unhandled channel message type %d", + hdr->MessageType); + + /* Free the msg that was allocated in VmbusOnMsgDPC() */ + kfree(msg); + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusChannelRequestOffers - Send a request to get all our pending offers. + */ +int VmbusChannelRequestOffers(void) +{ + struct vmbus_channel_message_header *msg; + struct vmbus_channel_msginfo *msgInfo; + int ret; + + DPRINT_ENTER(VMBUS); + + msgInfo = kmalloc(sizeof(*msgInfo) + + sizeof(struct vmbus_channel_message_header), + GFP_KERNEL); + ASSERT(msgInfo != NULL); + + msgInfo->WaitEvent = osd_WaitEventCreate(); + msg = (struct vmbus_channel_message_header *)msgInfo->Msg; + + msg->MessageType = ChannelMessageRequestOffers; + + /*SpinlockAcquire(gVmbusConnection.channelMsgLock); + INSERT_TAIL_LIST(&gVmbusConnection.channelMsgList, + &msgInfo->msgListEntry); + SpinlockRelease(gVmbusConnection.channelMsgLock);*/ + + ret = VmbusPostMessage(msg, + sizeof(struct vmbus_channel_message_header)); + if (ret != 0) { + DPRINT_ERR(VMBUS, "Unable to request offers - %d", ret); + + /*SpinlockAcquire(gVmbusConnection.channelMsgLock); + REMOVE_ENTRY_LIST(&msgInfo->msgListEntry); + SpinlockRelease(gVmbusConnection.channelMsgLock);*/ + + goto Cleanup; + } + /* osd_WaitEventWait(msgInfo->waitEvent); */ + + /*SpinlockAcquire(gVmbusConnection.channelMsgLock); + REMOVE_ENTRY_LIST(&msgInfo->msgListEntry); + SpinlockRelease(gVmbusConnection.channelMsgLock);*/ + + +Cleanup: + if (msgInfo) { + kfree(msgInfo->WaitEvent); + kfree(msgInfo); + } + + DPRINT_EXIT(VMBUS); + return ret; +} + +/** + * VmbusChannelReleaseUnattachedChannels - Release channels that are unattached/unconnected ie (no drivers associated) + */ +void VmbusChannelReleaseUnattachedChannels(void) +{ + struct vmbus_channel *channel, *pos; + struct vmbus_channel *start = NULL; + unsigned long flags; + + spin_lock_irqsave(&gVmbusConnection.channel_lock, flags); + + list_for_each_entry_safe(channel, pos, &gVmbusConnection.ChannelList, + ListEntry) { + if (channel == start) + break; + + if (!channel->DeviceObject->Driver) { + list_del(&channel->ListEntry); + DPRINT_INFO(VMBUS, + "Releasing unattached device object %p", + channel->DeviceObject); + + VmbusChildDeviceRemove(channel->DeviceObject); + FreeVmbusChannel(channel); + } else { + if (!start) + start = channel; + } + } + + spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags); +} + +/* eof */ diff --git a/drivers/staging/hv/ChannelMgmt.h b/drivers/staging/hv/ChannelMgmt.h new file mode 100644 index 00000000000..a839d8fe6ce --- /dev/null +++ b/drivers/staging/hv/ChannelMgmt.h @@ -0,0 +1,319 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _CHANNEL_MGMT_H_ +#define _CHANNEL_MGMT_H_ + +#include <linux/list.h> +#include "RingBuffer.h" +#include "VmbusChannelInterface.h" +#include "VmbusPacketFormat.h" + +/* Version 1 messages */ +enum vmbus_channel_message_type { + ChannelMessageInvalid = 0, + ChannelMessageOfferChannel = 1, + ChannelMessageRescindChannelOffer = 2, + ChannelMessageRequestOffers = 3, + ChannelMessageAllOffersDelivered = 4, + ChannelMessageOpenChannel = 5, + ChannelMessageOpenChannelResult = 6, + ChannelMessageCloseChannel = 7, + ChannelMessageGpadlHeader = 8, + ChannelMessageGpadlBody = 9, + ChannelMessageGpadlCreated = 10, + ChannelMessageGpadlTeardown = 11, + ChannelMessageGpadlTorndown = 12, + ChannelMessageRelIdReleased = 13, + ChannelMessageInitiateContact = 14, + ChannelMessageVersionResponse = 15, + ChannelMessageUnload = 16, +#ifdef VMBUS_FEATURE_PARENT_OR_PEER_MEMORY_MAPPED_INTO_A_CHILD + ChannelMessageViewRangeAdd = 17, + ChannelMessageViewRangeRemove = 18, +#endif + ChannelMessageCount +} __attribute__((packed)); + +struct vmbus_channel_message_header { + enum vmbus_channel_message_type MessageType; + u32 Padding; +} __attribute__((packed)); + +/* Query VMBus Version parameters */ +struct vmbus_channel_query_vmbus_version { + struct vmbus_channel_message_header Header; + u32 Version; +} __attribute__((packed)); + +/* VMBus Version Supported parameters */ +struct vmbus_channel_version_supported { + struct vmbus_channel_message_header Header; + bool VersionSupported; +} __attribute__((packed)); + +/* Offer Channel parameters */ +struct vmbus_channel_offer_channel { + struct vmbus_channel_message_header Header; + struct vmbus_channel_offer Offer; + u32 ChildRelId; + u8 MonitorId; + bool MonitorAllocated; +} __attribute__((packed)); + +/* Rescind Offer parameters */ +struct vmbus_channel_rescind_offer { + struct vmbus_channel_message_header Header; + u32 ChildRelId; +} __attribute__((packed)); + +/* + * Request Offer -- no parameters, SynIC message contains the partition ID + * Set Snoop -- no parameters, SynIC message contains the partition ID + * Clear Snoop -- no parameters, SynIC message contains the partition ID + * All Offers Delivered -- no parameters, SynIC message contains the partition + * ID + * Flush Client -- no parameters, SynIC message contains the partition ID + */ + +/* Open Channel parameters */ +struct vmbus_channel_open_channel { + struct vmbus_channel_message_header Header; + + /* Identifies the specific VMBus channel that is being opened. */ + u32 ChildRelId; + + /* ID making a particular open request at a channel offer unique. */ + u32 OpenId; + + /* GPADL for the channel's ring buffer. */ + u32 RingBufferGpadlHandle; + + /* GPADL for the channel's server context save area. */ + u32 ServerContextAreaGpadlHandle; + + /* + * The upstream ring buffer begins at offset zero in the memory + * described by RingBufferGpadlHandle. The downstream ring buffer + * follows it at this offset (in pages). + */ + u32 DownstreamRingBufferPageOffset; + + /* User-specific data to be passed along to the server endpoint. */ + unsigned char UserData[MAX_USER_DEFINED_BYTES]; +} __attribute__((packed)); + +/* Open Channel Result parameters */ +struct vmbus_channel_open_result { + struct vmbus_channel_message_header Header; + u32 ChildRelId; + u32 OpenId; + u32 Status; +} __attribute__((packed)); + +/* Close channel parameters; */ +struct vmbus_channel_close_channel { + struct vmbus_channel_message_header Header; + u32 ChildRelId; +} __attribute__((packed)); + +/* Channel Message GPADL */ +#define GPADL_TYPE_RING_BUFFER 1 +#define GPADL_TYPE_SERVER_SAVE_AREA 2 +#define GPADL_TYPE_TRANSACTION 8 + +/* + * The number of PFNs in a GPADL message is defined by the number of + * pages that would be spanned by ByteCount and ByteOffset. If the + * implied number of PFNs won't fit in this packet, there will be a + * follow-up packet that contains more. + */ +struct vmbus_channel_gpadl_header { + struct vmbus_channel_message_header Header; + u32 ChildRelId; + u32 Gpadl; + u16 RangeBufLen; + u16 RangeCount; + struct gpa_range Range[0]; +} __attribute__((packed)); + +/* This is the followup packet that contains more PFNs. */ +struct vmbus_channel_gpadl_body { + struct vmbus_channel_message_header Header; + u32 MessageNumber; + u32 Gpadl; + u64 Pfn[0]; +} __attribute__((packed)); + +struct vmbus_channel_gpadl_created { + struct vmbus_channel_message_header Header; + u32 ChildRelId; + u32 Gpadl; + u32 CreationStatus; +} __attribute__((packed)); + +struct vmbus_channel_gpadl_teardown { + struct vmbus_channel_message_header Header; + u32 ChildRelId; + u32 Gpadl; +} __attribute__((packed)); + +struct vmbus_channel_gpadl_torndown { + struct vmbus_channel_message_header Header; + u32 Gpadl; +} __attribute__((packed)); + +#ifdef VMBUS_FEATURE_PARENT_OR_PEER_MEMORY_MAPPED_INTO_A_CHILD +struct vmbus_channel_view_range_add { + struct vmbus_channel_message_header Header; + PHYSICAL_ADDRESS ViewRangeBase; + u64 ViewRangeLength; + u32 ChildRelId; +} __attribute__((packed)); + +struct vmbus_channel_view_range_remove { + struct vmbus_channel_message_header Header; + PHYSICAL_ADDRESS ViewRangeBase; + u32 ChildRelId; +} __attribute__((packed)); +#endif + +struct vmbus_channel_relid_released { + struct vmbus_channel_message_header Header; + u32 ChildRelId; +} __attribute__((packed)); + +struct vmbus_channel_initiate_contact { + struct vmbus_channel_message_header Header; + u32 VMBusVersionRequested; + u32 Padding2; + u64 InterruptPage; + u64 MonitorPage1; + u64 MonitorPage2; +} __attribute__((packed)); + +struct vmbus_channel_version_response { + struct vmbus_channel_message_header Header; + bool VersionSupported; +} __attribute__((packed)); + +enum vmbus_channel_state { + CHANNEL_OFFER_STATE, + CHANNEL_OPENING_STATE, + CHANNEL_OPEN_STATE, +}; + +struct vmbus_channel { + struct list_head ListEntry; + + struct hv_device *DeviceObject; + + struct timer_list poll_timer; /* SA-111 workaround */ + + enum vmbus_channel_state State; + + struct vmbus_channel_offer_channel OfferMsg; + /* + * These are based on the OfferMsg.MonitorId. + * Save it here for easy access. + */ + u8 MonitorGroup; + u8 MonitorBit; + + u32 RingBufferGpadlHandle; + + /* Allocated memory for ring buffer */ + void *RingBufferPages; + u32 RingBufferPageCount; + RING_BUFFER_INFO Outbound; /* send to parent */ + RING_BUFFER_INFO Inbound; /* receive from parent */ + spinlock_t inbound_lock; + struct workqueue_struct *ControlWQ; + + /* Channel callback are invoked in this workqueue context */ + /* HANDLE dataWorkQueue; */ + + void (*OnChannelCallback)(void *context); + void *ChannelCallbackContext; +}; + +struct vmbus_channel_debug_info { + u32 RelId; + enum vmbus_channel_state State; + struct hv_guid InterfaceType; + struct hv_guid InterfaceInstance; + u32 MonitorId; + u32 ServerMonitorPending; + u32 ServerMonitorLatency; + u32 ServerMonitorConnectionId; + u32 ClientMonitorPending; + u32 ClientMonitorLatency; + u32 ClientMonitorConnectionId; + + RING_BUFFER_DEBUG_INFO Inbound; + RING_BUFFER_DEBUG_INFO Outbound; +}; + +/* + * Represents each channel msg on the vmbus connection This is a + * variable-size data structure depending on the msg type itself + */ +struct vmbus_channel_msginfo { + /* Bookkeeping stuff */ + struct list_head MsgListEntry; + + /* So far, this is only used to handle gpadl body message */ + struct list_head SubMsgList; + + /* Synchronize the request/response if needed */ + struct osd_waitevent *WaitEvent; + + union { + struct vmbus_channel_version_supported VersionSupported; + struct vmbus_channel_open_result OpenResult; + struct vmbus_channel_gpadl_torndown GpadlTorndown; + struct vmbus_channel_gpadl_created GpadlCreated; + struct vmbus_channel_version_response VersionResponse; + } Response; + + u32 MessageSize; + /* + * The channel message that goes out on the "wire". + * It will contain at minimum the VMBUS_CHANNEL_MESSAGE_HEADER header + */ + unsigned char Msg[0]; +}; + + +struct vmbus_channel *AllocVmbusChannel(void); + +void FreeVmbusChannel(struct vmbus_channel *Channel); + +void VmbusOnChannelMessage(void *Context); + +int VmbusChannelRequestOffers(void); + +void VmbusChannelReleaseUnattachedChannels(void); + +#endif /* _CHANNEL_MGMT_H_ */ diff --git a/drivers/staging/hv/Connection.c b/drivers/staging/hv/Connection.c new file mode 100644 index 00000000000..43c2e685501 --- /dev/null +++ b/drivers/staging/hv/Connection.c @@ -0,0 +1,341 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include "osd.h" +#include "logging.h" +#include "VmbusPrivate.h" + + +struct VMBUS_CONNECTION gVmbusConnection = { + .ConnectState = Disconnected, + .NextGpadlHandle = ATOMIC_INIT(0xE1E10), +}; + +/** + * VmbusConnect - Sends a connect request on the partition service connection + */ +int VmbusConnect(void) +{ + int ret = 0; + struct vmbus_channel_msginfo *msgInfo = NULL; + struct vmbus_channel_initiate_contact *msg; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + /* Make sure we are not connecting or connected */ + if (gVmbusConnection.ConnectState != Disconnected) + return -1; + + /* Initialize the vmbus connection */ + gVmbusConnection.ConnectState = Connecting; + gVmbusConnection.WorkQueue = create_workqueue("hv_vmbus_con"); + if (!gVmbusConnection.WorkQueue) { + ret = -1; + goto Cleanup; + } + + INIT_LIST_HEAD(&gVmbusConnection.ChannelMsgList); + spin_lock_init(&gVmbusConnection.channelmsg_lock); + + INIT_LIST_HEAD(&gVmbusConnection.ChannelList); + spin_lock_init(&gVmbusConnection.channel_lock); + + /* + * Setup the vmbus event connection for channel interrupt + * abstraction stuff + */ + gVmbusConnection.InterruptPage = osd_PageAlloc(1); + if (gVmbusConnection.InterruptPage == NULL) { + ret = -1; + goto Cleanup; + } + + gVmbusConnection.RecvInterruptPage = gVmbusConnection.InterruptPage; + gVmbusConnection.SendInterruptPage = + (void *)((unsigned long)gVmbusConnection.InterruptPage + + (PAGE_SIZE >> 1)); + + /* + * Setup the monitor notification facility. The 1st page for + * parent->child and the 2nd page for child->parent + */ + gVmbusConnection.MonitorPages = osd_PageAlloc(2); + if (gVmbusConnection.MonitorPages == NULL) { + ret = -1; + goto Cleanup; + } + + msgInfo = kzalloc(sizeof(*msgInfo) + + sizeof(struct vmbus_channel_initiate_contact), + GFP_KERNEL); + if (msgInfo == NULL) { + ret = -1; + goto Cleanup; + } + + msgInfo->WaitEvent = osd_WaitEventCreate(); + msg = (struct vmbus_channel_initiate_contact *)msgInfo->Msg; + + msg->Header.MessageType = ChannelMessageInitiateContact; + msg->VMBusVersionRequested = VMBUS_REVISION_NUMBER; + msg->InterruptPage = virt_to_phys(gVmbusConnection.InterruptPage); + msg->MonitorPage1 = virt_to_phys(gVmbusConnection.MonitorPages); + msg->MonitorPage2 = virt_to_phys( + (void *)((unsigned long)gVmbusConnection.MonitorPages + + PAGE_SIZE)); + + /* + * Add to list before we send the request since we may + * receive the response before returning from this routine + */ + spin_lock_irqsave(&gVmbusConnection.channelmsg_lock, flags); + list_add_tail(&msgInfo->MsgListEntry, + &gVmbusConnection.ChannelMsgList); + + spin_unlock_irqrestore(&gVmbusConnection.channelmsg_lock, flags); + + DPRINT_DBG(VMBUS, "Vmbus connection - interrupt pfn %llx, " + "monitor1 pfn %llx,, monitor2 pfn %llx", + msg->InterruptPage, msg->MonitorPage1, msg->MonitorPage2); + + DPRINT_DBG(VMBUS, "Sending channel initiate msg..."); + ret = VmbusPostMessage(msg, + sizeof(struct vmbus_channel_initiate_contact)); + if (ret != 0) { + list_del(&msgInfo->MsgListEntry); + goto Cleanup; + } + + /* Wait for the connection response */ + osd_WaitEventWait(msgInfo->WaitEvent); + + list_del(&msgInfo->MsgListEntry); + + /* Check if successful */ + if (msgInfo->Response.VersionResponse.VersionSupported) { + DPRINT_INFO(VMBUS, "Vmbus connected!!"); + gVmbusConnection.ConnectState = Connected; + + } else { + DPRINT_ERR(VMBUS, "Vmbus connection failed!!..." + "current version (%d) not supported", + VMBUS_REVISION_NUMBER); + ret = -1; + goto Cleanup; + } + + kfree(msgInfo->WaitEvent); + kfree(msgInfo); + DPRINT_EXIT(VMBUS); + + return 0; + +Cleanup: + gVmbusConnection.ConnectState = Disconnected; + + if (gVmbusConnection.WorkQueue) + destroy_workqueue(gVmbusConnection.WorkQueue); + + if (gVmbusConnection.InterruptPage) { + osd_PageFree(gVmbusConnection.InterruptPage, 1); + gVmbusConnection.InterruptPage = NULL; + } + + if (gVmbusConnection.MonitorPages) { + osd_PageFree(gVmbusConnection.MonitorPages, 2); + gVmbusConnection.MonitorPages = NULL; + } + + if (msgInfo) { + kfree(msgInfo->WaitEvent); + kfree(msgInfo); + } + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusDisconnect - Sends a disconnect request on the partition service connection + */ +int VmbusDisconnect(void) +{ + int ret = 0; + struct vmbus_channel_message_header *msg; + + DPRINT_ENTER(VMBUS); + + /* Make sure we are connected */ + if (gVmbusConnection.ConnectState != Connected) + return -1; + + msg = kzalloc(sizeof(struct vmbus_channel_message_header), GFP_KERNEL); + + msg->MessageType = ChannelMessageUnload; + + ret = VmbusPostMessage(msg, + sizeof(struct vmbus_channel_message_header)); + if (ret != 0) + goto Cleanup; + + osd_PageFree(gVmbusConnection.InterruptPage, 1); + + /* TODO: iterate thru the msg list and free up */ + destroy_workqueue(gVmbusConnection.WorkQueue); + + gVmbusConnection.ConnectState = Disconnected; + + DPRINT_INFO(VMBUS, "Vmbus disconnected!!"); + +Cleanup: + kfree(msg); + DPRINT_EXIT(VMBUS); + return ret; +} + +/** + * GetChannelFromRelId - Get the channel object given its child relative id (ie channel id) + */ +struct vmbus_channel *GetChannelFromRelId(u32 relId) +{ + struct vmbus_channel *channel; + struct vmbus_channel *foundChannel = NULL; + unsigned long flags; + + spin_lock_irqsave(&gVmbusConnection.channel_lock, flags); + list_for_each_entry(channel, &gVmbusConnection.ChannelList, ListEntry) { + if (channel->OfferMsg.ChildRelId == relId) { + foundChannel = channel; + break; + } + } + spin_unlock_irqrestore(&gVmbusConnection.channel_lock, flags); + + return foundChannel; +} + +/** + * VmbusProcessChannelEvent - Process a channel event notification + */ +static void VmbusProcessChannelEvent(void *context) +{ + struct vmbus_channel *channel; + u32 relId = (u32)(unsigned long)context; + + ASSERT(relId > 0); + + /* + * Find the channel based on this relid and invokes the + * channel callback to process the event + */ + channel = GetChannelFromRelId(relId); + + if (channel) { + VmbusChannelOnChannelEvent(channel); + /* + * WorkQueueQueueWorkItem(channel->dataWorkQueue, + * VmbusChannelOnChannelEvent, + * (void*)channel); + */ + } else { + DPRINT_ERR(VMBUS, "channel not found for relid - %d.", relId); + } +} + +/** + * VmbusOnEvents - Handler for events + */ +void VmbusOnEvents(void) +{ + int dword; + int maxdword = MAX_NUM_CHANNELS_SUPPORTED >> 5; + int bit; + int relid; + u32 *recvInterruptPage = gVmbusConnection.RecvInterruptPage; + + DPRINT_ENTER(VMBUS); + + /* Check events */ + if (recvInterruptPage) { + for (dword = 0; dword < maxdword; dword++) { + if (recvInterruptPage[dword]) { + for (bit = 0; bit < 32; bit++) { + if (test_and_clear_bit(bit, (unsigned long *)&recvInterruptPage[dword])) { + relid = (dword << 5) + bit; + DPRINT_DBG(VMBUS, "event detected for relid - %d", relid); + + if (relid == 0) { + /* special case - vmbus channel protocol msg */ + DPRINT_DBG(VMBUS, "invalid relid - %d", relid); + continue; + } else { + /* QueueWorkItem(VmbusProcessEvent, (void*)relid); */ + /* ret = WorkQueueQueueWorkItem(gVmbusConnection.workQueue, VmbusProcessChannelEvent, (void*)relid); */ + VmbusProcessChannelEvent((void *)(unsigned long)relid); + } + } + } + } + } + } + DPRINT_EXIT(VMBUS); + + return; +} + +/** + * VmbusPostMessage - Send a msg on the vmbus's message connection + */ +int VmbusPostMessage(void *buffer, size_t bufferLen) +{ + union hv_connection_id connId; + + connId.Asu32 = 0; + connId.u.Id = VMBUS_MESSAGE_CONNECTION_ID; + return HvPostMessage(connId, 1, buffer, bufferLen); +} + +/** + * VmbusSetEvent - Send an event notification to the parent + */ +int VmbusSetEvent(u32 childRelId) +{ + int ret = 0; + + DPRINT_ENTER(VMBUS); + + /* Each u32 represents 32 channels */ + set_bit(childRelId & 31, + (unsigned long *)gVmbusConnection.SendInterruptPage + + (childRelId >> 5)); + + ret = HvSignalEvent(); + + DPRINT_EXIT(VMBUS); + + return ret; +} diff --git a/drivers/staging/hv/Hv.c b/drivers/staging/hv/Hv.c new file mode 100644 index 00000000000..c5b6613f2f2 --- /dev/null +++ b/drivers/staging/hv/Hv.c @@ -0,0 +1,568 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/vmalloc.h> +#include "osd.h" +#include "logging.h" +#include "VmbusPrivate.h" + +/* The one and only */ +struct hv_context gHvContext = { + .SynICInitialized = false, + .HypercallPage = NULL, + .SignalEventParam = NULL, + .SignalEventBuffer = NULL, +}; + +/** + * HvQueryHypervisorPresence - Query the cpuid for presense of windows hypervisor + */ +static int HvQueryHypervisorPresence(void) +{ + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int op; + + eax = 0; + ebx = 0; + ecx = 0; + edx = 0; + op = HvCpuIdFunctionVersionAndFeatures; + cpuid(op, &eax, &ebx, &ecx, &edx); + + return ecx & HV_PRESENT_BIT; +} + +/** + * HvQueryHypervisorInfo - Get version info of the windows hypervisor + */ +static int HvQueryHypervisorInfo(void) +{ + unsigned int eax; + unsigned int ebx; + unsigned int ecx; + unsigned int edx; + unsigned int maxLeaf; + unsigned int op; + + /* + * Its assumed that this is called after confirming that Viridian + * is present. Query id and revision. + */ + eax = 0; + ebx = 0; + ecx = 0; + edx = 0; + op = HvCpuIdFunctionHvVendorAndMaxFunction; + cpuid(op, &eax, &ebx, &ecx, &edx); + + DPRINT_INFO(VMBUS, "Vendor ID: %c%c%c%c%c%c%c%c%c%c%c%c", + (ebx & 0xFF), + ((ebx >> 8) & 0xFF), + ((ebx >> 16) & 0xFF), + ((ebx >> 24) & 0xFF), + (ecx & 0xFF), + ((ecx >> 8) & 0xFF), + ((ecx >> 16) & 0xFF), + ((ecx >> 24) & 0xFF), + (edx & 0xFF), + ((edx >> 8) & 0xFF), + ((edx >> 16) & 0xFF), + ((edx >> 24) & 0xFF)); + + maxLeaf = eax; + eax = 0; + ebx = 0; + ecx = 0; + edx = 0; + op = HvCpuIdFunctionHvInterface; + cpuid(op, &eax, &ebx, &ecx, &edx); + + DPRINT_INFO(VMBUS, "Interface ID: %c%c%c%c", + (eax & 0xFF), + ((eax >> 8) & 0xFF), + ((eax >> 16) & 0xFF), + ((eax >> 24) & 0xFF)); + + if (maxLeaf >= HvCpuIdFunctionMsHvVersion) { + eax = 0; + ebx = 0; + ecx = 0; + edx = 0; + op = HvCpuIdFunctionMsHvVersion; + cpuid(op, &eax, &ebx, &ecx, &edx); + DPRINT_INFO(VMBUS, "OS Build:%d-%d.%d-%d-%d.%d",\ + eax, + ebx >> 16, + ebx & 0xFFFF, + ecx, + edx >> 24, + edx & 0xFFFFFF); + } + return maxLeaf; +} + +/** + * HvDoHypercall - Invoke the specified hypercall + */ +static u64 HvDoHypercall(u64 Control, void *Input, void *Output) +{ +#ifdef CONFIG_X86_64 + u64 hvStatus = 0; + u64 inputAddress = (Input) ? virt_to_phys(Input) : 0; + u64 outputAddress = (Output) ? virt_to_phys(Output) : 0; + volatile void *hypercallPage = gHvContext.HypercallPage; + + DPRINT_DBG(VMBUS, "Hypercall <control %llx input phys %llx virt %p " + "output phys %llx virt %p hypercall %p>", + Control, inputAddress, Input, + outputAddress, Output, hypercallPage); + + __asm__ __volatile__("mov %0, %%r8" : : "r" (outputAddress) : "r8"); + __asm__ __volatile__("call *%3" : "=a" (hvStatus) : + "c" (Control), "d" (inputAddress), + "m" (hypercallPage)); + + DPRINT_DBG(VMBUS, "Hypercall <return %llx>", hvStatus); + + return hvStatus; + +#else + + u32 controlHi = Control >> 32; + u32 controlLo = Control & 0xFFFFFFFF; + u32 hvStatusHi = 1; + u32 hvStatusLo = 1; + u64 inputAddress = (Input) ? virt_to_phys(Input) : 0; + u32 inputAddressHi = inputAddress >> 32; + u32 inputAddressLo = inputAddress & 0xFFFFFFFF; + u64 outputAddress = (Output) ? virt_to_phys(Output) : 0; + u32 outputAddressHi = outputAddress >> 32; + u32 outputAddressLo = outputAddress & 0xFFFFFFFF; + volatile void *hypercallPage = gHvContext.HypercallPage; + + DPRINT_DBG(VMBUS, "Hypercall <control %llx input %p output %p>", + Control, Input, Output); + + __asm__ __volatile__ ("call *%8" : "=d"(hvStatusHi), + "=a"(hvStatusLo) : "d" (controlHi), + "a" (controlLo), "b" (inputAddressHi), + "c" (inputAddressLo), "D"(outputAddressHi), + "S"(outputAddressLo), "m" (hypercallPage)); + + DPRINT_DBG(VMBUS, "Hypercall <return %llx>", + hvStatusLo | ((u64)hvStatusHi << 32)); + + return hvStatusLo | ((u64)hvStatusHi << 32); +#endif /* !x86_64 */ +} + +/** + * HvInit - Main initialization routine. + * + * This routine must be called before any other routines in here are called + */ +int HvInit(void) +{ + int ret = 0; + int maxLeaf; + union hv_x64_msr_hypercall_contents hypercallMsr; + void *virtAddr = NULL; + + DPRINT_ENTER(VMBUS); + + memset(gHvContext.synICEventPage, 0, sizeof(void *) * MAX_NUM_CPUS); + memset(gHvContext.synICMessagePage, 0, sizeof(void *) * MAX_NUM_CPUS); + + if (!HvQueryHypervisorPresence()) { + DPRINT_ERR(VMBUS, "No Windows hypervisor detected!!"); + goto Cleanup; + } + + DPRINT_INFO(VMBUS, + "Windows hypervisor detected! Retrieving more info..."); + + maxLeaf = HvQueryHypervisorInfo(); + /* HvQueryHypervisorFeatures(maxLeaf); */ + + /* + * Determine if we are running on xenlinux (ie x2v shim) or native + * linux + */ + rdmsrl(HV_X64_MSR_GUEST_OS_ID, gHvContext.GuestId); + if (gHvContext.GuestId == 0) { + /* Write our OS info */ + wrmsrl(HV_X64_MSR_GUEST_OS_ID, HV_LINUX_GUEST_ID); + gHvContext.GuestId = HV_LINUX_GUEST_ID; + } + + /* See if the hypercall page is already set */ + rdmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64); + if (gHvContext.GuestId == HV_LINUX_GUEST_ID) { + /* Allocate the hypercall page memory */ + /* virtAddr = osd_PageAlloc(1); */ + virtAddr = osd_VirtualAllocExec(PAGE_SIZE); + + if (!virtAddr) { + DPRINT_ERR(VMBUS, + "unable to allocate hypercall page!!"); + goto Cleanup; + } + + hypercallMsr.Enable = 1; + /* hypercallMsr.GuestPhysicalAddress = + * virt_to_phys(virtAddr) >> PAGE_SHIFT; */ + hypercallMsr.GuestPhysicalAddress = vmalloc_to_pfn(virtAddr); + wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64); + + /* Confirm that hypercall page did get setup. */ + hypercallMsr.AsUINT64 = 0; + rdmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64); + if (!hypercallMsr.Enable) { + DPRINT_ERR(VMBUS, "unable to set hypercall page!!"); + goto Cleanup; + } + + gHvContext.HypercallPage = virtAddr; + } else { + DPRINT_ERR(VMBUS, "Unknown guest id (0x%llx)!!", + gHvContext.GuestId); + goto Cleanup; + } + + DPRINT_INFO(VMBUS, "Hypercall page VA=%p, PA=0x%0llx", + gHvContext.HypercallPage, + (u64)hypercallMsr.GuestPhysicalAddress << PAGE_SHIFT); + + /* Setup the global signal event param for the signal event hypercall */ + gHvContext.SignalEventBuffer = + kmalloc(sizeof(struct hv_input_signal_event_buffer), + GFP_KERNEL); + if (!gHvContext.SignalEventBuffer) + goto Cleanup; + + gHvContext.SignalEventParam = + (struct hv_input_signal_event *) + (ALIGN_UP((unsigned long)gHvContext.SignalEventBuffer, + HV_HYPERCALL_PARAM_ALIGN)); + gHvContext.SignalEventParam->ConnectionId.Asu32 = 0; + gHvContext.SignalEventParam->ConnectionId.u.Id = + VMBUS_EVENT_CONNECTION_ID; + gHvContext.SignalEventParam->FlagNumber = 0; + gHvContext.SignalEventParam->RsvdZ = 0; + + /* DPRINT_DBG(VMBUS, "My id %llu", HvGetCurrentPartitionId()); */ + + DPRINT_EXIT(VMBUS); + + return ret; + +Cleanup: + if (virtAddr) { + if (hypercallMsr.Enable) { + hypercallMsr.AsUINT64 = 0; + wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64); + } + + vfree(virtAddr); + } + ret = -1; + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * HvCleanup - Cleanup routine. + * + * This routine is called normally during driver unloading or exiting. + */ +void HvCleanup(void) +{ + union hv_x64_msr_hypercall_contents hypercallMsr; + + DPRINT_ENTER(VMBUS); + + if (gHvContext.SignalEventBuffer) { + gHvContext.SignalEventBuffer = NULL; + gHvContext.SignalEventParam = NULL; + kfree(gHvContext.SignalEventBuffer); + } + + if (gHvContext.GuestId == HV_LINUX_GUEST_ID) { + if (gHvContext.HypercallPage) { + hypercallMsr.AsUINT64 = 0; + wrmsrl(HV_X64_MSR_HYPERCALL, hypercallMsr.AsUINT64); + vfree(gHvContext.HypercallPage); + gHvContext.HypercallPage = NULL; + } + } + + DPRINT_EXIT(VMBUS); + +} + +/** + * HvPostMessage - Post a message using the hypervisor message IPC. + * + * This involves a hypercall. + */ +u16 HvPostMessage(union hv_connection_id connectionId, + enum hv_message_type messageType, + void *payload, size_t payloadSize) +{ + struct alignedInput { + u64 alignment8; + struct hv_input_post_message msg; + }; + + struct hv_input_post_message *alignedMsg; + u16 status; + unsigned long addr; + + if (payloadSize > HV_MESSAGE_PAYLOAD_BYTE_COUNT) + return -1; + + addr = (unsigned long)kmalloc(sizeof(struct alignedInput), GFP_ATOMIC); + if (!addr) + return -1; + + alignedMsg = (struct hv_input_post_message *) + (ALIGN_UP(addr, HV_HYPERCALL_PARAM_ALIGN)); + + alignedMsg->ConnectionId = connectionId; + alignedMsg->MessageType = messageType; + alignedMsg->PayloadSize = payloadSize; + memcpy((void *)alignedMsg->Payload, payload, payloadSize); + + status = HvDoHypercall(HvCallPostMessage, alignedMsg, NULL) & 0xFFFF; + + kfree((void *)addr); + + return status; +} + + +/** + * HvSignalEvent - Signal an event on the specified connection using the hypervisor event IPC. + * + * This involves a hypercall. + */ +u16 HvSignalEvent(void) +{ + u16 status; + + status = HvDoHypercall(HvCallSignalEvent, gHvContext.SignalEventParam, + NULL) & 0xFFFF; + return status; +} + +/** + * HvSynicInit - Initialize the Synthethic Interrupt Controller. + * + * If it is already initialized by another entity (ie x2v shim), we need to + * retrieve the initialized message and event pages. Otherwise, we create and + * initialize the message and event pages. + */ +int HvSynicInit(u32 irqVector) +{ + u64 version; + union hv_synic_simp simp; + union hv_synic_siefp siefp; + union hv_synic_sint sharedSint; + union hv_synic_scontrol sctrl; + u64 guestID; + int ret = 0; + + DPRINT_ENTER(VMBUS); + + if (!gHvContext.HypercallPage) { + DPRINT_EXIT(VMBUS); + return ret; + } + + /* Check the version */ + rdmsrl(HV_X64_MSR_SVERSION, version); + + DPRINT_INFO(VMBUS, "SynIC version: %llx", version); + + /* TODO: Handle SMP */ + if (gHvContext.GuestId == HV_XENLINUX_GUEST_ID) { + DPRINT_INFO(VMBUS, "Skipping SIMP and SIEFP setup since " + "it is already set."); + + rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64); + rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64); + + DPRINT_DBG(VMBUS, "Simp: %llx, Sifep: %llx", + simp.AsUINT64, siefp.AsUINT64); + + /* + * Determine if we are running on xenlinux (ie x2v shim) or + * native linux + */ + rdmsrl(HV_X64_MSR_GUEST_OS_ID, guestID); + if (guestID == HV_LINUX_GUEST_ID) { + gHvContext.synICMessagePage[0] = + phys_to_virt(simp.BaseSimpGpa << PAGE_SHIFT); + gHvContext.synICEventPage[0] = + phys_to_virt(siefp.BaseSiefpGpa << PAGE_SHIFT); + } else { + DPRINT_ERR(VMBUS, "unknown guest id!!"); + goto Cleanup; + } + DPRINT_DBG(VMBUS, "MAPPED: Simp: %p, Sifep: %p", + gHvContext.synICMessagePage[0], + gHvContext.synICEventPage[0]); + } else { + gHvContext.synICMessagePage[0] = osd_PageAlloc(1); + if (gHvContext.synICMessagePage[0] == NULL) { + DPRINT_ERR(VMBUS, + "unable to allocate SYNIC message page!!"); + goto Cleanup; + } + + gHvContext.synICEventPage[0] = osd_PageAlloc(1); + if (gHvContext.synICEventPage[0] == NULL) { + DPRINT_ERR(VMBUS, + "unable to allocate SYNIC event page!!"); + goto Cleanup; + } + + /* Setup the Synic's message page */ + rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64); + simp.SimpEnabled = 1; + simp.BaseSimpGpa = virt_to_phys(gHvContext.synICMessagePage[0]) + >> PAGE_SHIFT; + + DPRINT_DBG(VMBUS, "HV_X64_MSR_SIMP msr set to: %llx", + simp.AsUINT64); + + wrmsrl(HV_X64_MSR_SIMP, simp.AsUINT64); + + /* Setup the Synic's event page */ + rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64); + siefp.SiefpEnabled = 1; + siefp.BaseSiefpGpa = virt_to_phys(gHvContext.synICEventPage[0]) + >> PAGE_SHIFT; + + DPRINT_DBG(VMBUS, "HV_X64_MSR_SIEFP msr set to: %llx", + siefp.AsUINT64); + + wrmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64); + } + + /* Setup the interception SINT. */ + /* wrmsrl((HV_X64_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX), */ + /* interceptionSint.AsUINT64); */ + + /* Setup the shared SINT. */ + rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64); + + sharedSint.AsUINT64 = 0; + sharedSint.Vector = irqVector; /* HV_SHARED_SINT_IDT_VECTOR + 0x20; */ + sharedSint.Masked = false; + sharedSint.AutoEoi = true; + + DPRINT_DBG(VMBUS, "HV_X64_MSR_SINT1 msr set to: %llx", + sharedSint.AsUINT64); + + wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64); + + /* Enable the global synic bit */ + rdmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64); + sctrl.Enable = 1; + + wrmsrl(HV_X64_MSR_SCONTROL, sctrl.AsUINT64); + + gHvContext.SynICInitialized = true; + + DPRINT_EXIT(VMBUS); + + return ret; + +Cleanup: + ret = -1; + + if (gHvContext.GuestId == HV_LINUX_GUEST_ID) { + if (gHvContext.synICEventPage[0]) + osd_PageFree(gHvContext.synICEventPage[0], 1); + + if (gHvContext.synICMessagePage[0]) + osd_PageFree(gHvContext.synICMessagePage[0], 1); + } + + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * HvSynicCleanup - Cleanup routine for HvSynicInit(). + */ +void HvSynicCleanup(void) +{ + union hv_synic_sint sharedSint; + union hv_synic_simp simp; + union hv_synic_siefp siefp; + + DPRINT_ENTER(VMBUS); + + if (!gHvContext.SynICInitialized) { + DPRINT_EXIT(VMBUS); + return; + } + + rdmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64); + + sharedSint.Masked = 1; + + /* Disable the interrupt */ + wrmsrl(HV_X64_MSR_SINT0 + VMBUS_MESSAGE_SINT, sharedSint.AsUINT64); + + /* + * Disable and free the resources only if we are running as + * native linux since in xenlinux, we are sharing the + * resources with the x2v shim + */ + if (gHvContext.GuestId == HV_LINUX_GUEST_ID) { + rdmsrl(HV_X64_MSR_SIMP, simp.AsUINT64); + simp.SimpEnabled = 0; + simp.BaseSimpGpa = 0; + + wrmsrl(HV_X64_MSR_SIMP, simp.AsUINT64); + + rdmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64); + siefp.SiefpEnabled = 0; + siefp.BaseSiefpGpa = 0; + + wrmsrl(HV_X64_MSR_SIEFP, siefp.AsUINT64); + + osd_PageFree(gHvContext.synICMessagePage[0], 1); + osd_PageFree(gHvContext.synICEventPage[0], 1); + } + + DPRINT_EXIT(VMBUS); +} diff --git a/drivers/staging/hv/Hv.h b/drivers/staging/hv/Hv.h new file mode 100644 index 00000000000..5379e4bfc56 --- /dev/null +++ b/drivers/staging/hv/Hv.h @@ -0,0 +1,144 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef __HV_H__ +#define __HV_H__ + +#include "hv_api.h" + +enum { + VMBUS_MESSAGE_CONNECTION_ID = 1, + VMBUS_MESSAGE_PORT_ID = 1, + VMBUS_EVENT_CONNECTION_ID = 2, + VMBUS_EVENT_PORT_ID = 2, + VMBUS_MONITOR_CONNECTION_ID = 3, + VMBUS_MONITOR_PORT_ID = 3, + VMBUS_MESSAGE_SINT = 2, +}; + +/* #defines */ + +#define HV_PRESENT_BIT 0x80000000 + +#define HV_XENLINUX_GUEST_ID_LO 0x00000000 +#define HV_XENLINUX_GUEST_ID_HI 0x0B00B135 +#define HV_XENLINUX_GUEST_ID (((u64)HV_XENLINUX_GUEST_ID_HI << 32) \ + | HV_XENLINUX_GUEST_ID_LO) + +#define HV_LINUX_GUEST_ID_LO 0x00000000 +#define HV_LINUX_GUEST_ID_HI 0xB16B00B5 +#define HV_LINUX_GUEST_ID (((u64)HV_LINUX_GUEST_ID_HI << 32) | \ + HV_LINUX_GUEST_ID_LO) + +#define HV_CPU_POWER_MANAGEMENT (1 << 0) +#define HV_RECOMMENDATIONS_MAX 4 + +#define HV_X64_MAX 5 +#define HV_CAPS_MAX 8 + + +#define HV_HYPERCALL_PARAM_ALIGN sizeof(u64) + + +/* Service definitions */ + +#define HV_SERVICE_PARENT_PORT (0) +#define HV_SERVICE_PARENT_CONNECTION (0) + +#define HV_SERVICE_CONNECT_RESPONSE_SUCCESS (0) +#define HV_SERVICE_CONNECT_RESPONSE_INVALID_PARAMETER (1) +#define HV_SERVICE_CONNECT_RESPONSE_UNKNOWN_SERVICE (2) +#define HV_SERVICE_CONNECT_RESPONSE_CONNECTION_REJECTED (3) + +#define HV_SERVICE_CONNECT_REQUEST_MESSAGE_ID (1) +#define HV_SERVICE_CONNECT_RESPONSE_MESSAGE_ID (2) +#define HV_SERVICE_DISCONNECT_REQUEST_MESSAGE_ID (3) +#define HV_SERVICE_DISCONNECT_RESPONSE_MESSAGE_ID (4) +#define HV_SERVICE_MAX_MESSAGE_ID (4) + +#define HV_SERVICE_PROTOCOL_VERSION (0x0010) +#define HV_CONNECT_PAYLOAD_BYTE_COUNT 64 + +/* #define VMBUS_REVISION_NUMBER 6 */ + +/* Our local vmbus's port and connection id. Anything >0 is fine */ +/* #define VMBUS_PORT_ID 11 */ + +/* 628180B8-308D-4c5e-B7DB-1BEB62E62EF4 */ +static const struct hv_guid VMBUS_SERVICE_ID = { + .data = { + 0xb8, 0x80, 0x81, 0x62, 0x8d, 0x30, 0x5e, 0x4c, + 0xb7, 0xdb, 0x1b, 0xeb, 0x62, 0xe6, 0x2e, 0xf4 + }, +}; + +#define MAX_NUM_CPUS 1 + + +struct hv_input_signal_event_buffer { + u64 Align8; + struct hv_input_signal_event Event; +}; + +struct hv_context { + /* XenLinux or native Linux. If XenLinux, the hypercall and synic pages + * has already been initialized */ + u64 GuestId; + + void *HypercallPage; + + bool SynICInitialized; + + /* + * This is used as an input param to HvCallSignalEvent hypercall. The + * input param is immutable in our usage and must be dynamic mem (vs + * stack or global). */ + struct hv_input_signal_event_buffer *SignalEventBuffer; + /* 8-bytes aligned of the buffer above */ + struct hv_input_signal_event *SignalEventParam; + + void *synICMessagePage[MAX_NUM_CPUS]; + void *synICEventPage[MAX_NUM_CPUS]; +}; + +extern struct hv_context gHvContext; + + +/* Hv Interface */ + +extern int HvInit(void); + +extern void HvCleanup(void); + +extern u16 HvPostMessage(union hv_connection_id connectionId, + enum hv_message_type messageType, + void *payload, size_t payloadSize); + +extern u16 HvSignalEvent(void); + +extern int HvSynicInit(u32 irqVector); + +extern void HvSynicCleanup(void); + +#endif /* __HV_H__ */ diff --git a/drivers/staging/hv/Kconfig b/drivers/staging/hv/Kconfig new file mode 100644 index 00000000000..40447020a79 --- /dev/null +++ b/drivers/staging/hv/Kconfig @@ -0,0 +1,32 @@ +config HYPERV + tristate "Microsoft Hyper-V client drivers" + depends on X86 && m + default n + help + Select this option to run Linux as a Hyper-V client operating + system. + +if HYPERV + +config HYPERV_STORAGE + tristate "Microsoft Hyper-V virtual storage driver" + depends on SCSI + default HYPERV + help + Select this option to enable the Hyper-V virtual storage driver. + +config HYPERV_BLOCK + tristate "Microsoft Hyper-V virtual block driver" + depends on BLOCK && SCSI + default HYPERV + help + Select this option to enable the Hyper-V virtual block driver. + +config HYPERV_NET + tristate "Microsoft Hyper-V virtual network driver" + depends on NET + default HYPERV + help + Select this option to enable the Hyper-V virtual network driver. + +endif diff --git a/drivers/staging/hv/Makefile b/drivers/staging/hv/Makefile new file mode 100644 index 00000000000..27ebae8a918 --- /dev/null +++ b/drivers/staging/hv/Makefile @@ -0,0 +1,11 @@ +obj-$(CONFIG_HYPERV) += hv_vmbus.o +obj-$(CONFIG_HYPERV_STORAGE) += hv_storvsc.o +obj-$(CONFIG_HYPERV_BLOCK) += hv_blkvsc.o +obj-$(CONFIG_HYPERV_NET) += hv_netvsc.o + +hv_vmbus-objs := vmbus_drv.o osd.o \ + Vmbus.o Hv.o Connection.o Channel.o \ + ChannelMgmt.o ChannelInterface.o RingBuffer.o +hv_storvsc-objs := storvsc_drv.o StorVsc.o +hv_blkvsc-objs := blkvsc_drv.o BlkVsc.o +hv_netvsc-objs := netvsc_drv.o NetVsc.o RndisFilter.o diff --git a/drivers/staging/hv/NetVsc.c b/drivers/staging/hv/NetVsc.c new file mode 100644 index 00000000000..1610b845198 --- /dev/null +++ b/drivers/staging/hv/NetVsc.c @@ -0,0 +1,1379 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/io.h> +#include "osd.h" +#include "logging.h" +#include "NetVsc.h" +#include "RndisFilter.h" + + +/* Globals */ +static const char *gDriverName = "netvsc"; + +/* {F8615163-DF3E-46c5-913F-F2D2F965ED0E} */ +static const struct hv_guid gNetVscDeviceType = { + .data = { + 0x63, 0x51, 0x61, 0xF8, 0x3E, 0xDF, 0xc5, 0x46, + 0x91, 0x3F, 0xF2, 0xD2, 0xF9, 0x65, 0xED, 0x0E + } +}; + +static int NetVscOnDeviceAdd(struct hv_device *Device, void *AdditionalInfo); + +static int NetVscOnDeviceRemove(struct hv_device *Device); + +static void NetVscOnCleanup(struct hv_driver *Driver); + +static void NetVscOnChannelCallback(void *context); + +static int NetVscInitializeSendBufferWithNetVsp(struct hv_device *Device); + +static int NetVscInitializeReceiveBufferWithNetVsp(struct hv_device *Device); + +static int NetVscDestroySendBuffer(struct netvsc_device *NetDevice); + +static int NetVscDestroyReceiveBuffer(struct netvsc_device *NetDevice); + +static int NetVscConnectToVsp(struct hv_device *Device); + +static void NetVscOnSendCompletion(struct hv_device *Device, + struct vmpacket_descriptor *Packet); + +static int NetVscOnSend(struct hv_device *Device, + struct hv_netvsc_packet *Packet); + +static void NetVscOnReceive(struct hv_device *Device, + struct vmpacket_descriptor *Packet); + +static void NetVscOnReceiveCompletion(void *Context); + +static void NetVscSendReceiveCompletion(struct hv_device *Device, + u64 TransactionId); + + +static struct netvsc_device *AllocNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = kzalloc(sizeof(struct netvsc_device), GFP_KERNEL); + if (!netDevice) + return NULL; + + /* Set to 2 to allow both inbound and outbound traffic */ + atomic_cmpxchg(&netDevice->RefCount, 0, 2); + + netDevice->Device = Device; + Device->Extension = netDevice; + + return netDevice; +} + +static void FreeNetDevice(struct netvsc_device *Device) +{ + ASSERT(atomic_read(&Device->RefCount) == 0); + Device->Device->Extension = NULL; + kfree(Device); +} + + +/* Get the net device object iff exists and its refcount > 1 */ +static struct netvsc_device *GetOutboundNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = Device->Extension; + if (netDevice && atomic_read(&netDevice->RefCount) > 1) + atomic_inc(&netDevice->RefCount); + else + netDevice = NULL; + + return netDevice; +} + +/* Get the net device object iff exists and its refcount > 0 */ +static struct netvsc_device *GetInboundNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = Device->Extension; + if (netDevice && atomic_read(&netDevice->RefCount)) + atomic_inc(&netDevice->RefCount); + else + netDevice = NULL; + + return netDevice; +} + +static void PutNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = Device->Extension; + ASSERT(netDevice); + + atomic_dec(&netDevice->RefCount); +} + +static struct netvsc_device *ReleaseOutboundNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = Device->Extension; + if (netDevice == NULL) + return NULL; + + /* Busy wait until the ref drop to 2, then set it to 1 */ + while (atomic_cmpxchg(&netDevice->RefCount, 2, 1) != 2) + udelay(100); + + return netDevice; +} + +static struct netvsc_device *ReleaseInboundNetDevice(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + + netDevice = Device->Extension; + if (netDevice == NULL) + return NULL; + + /* Busy wait until the ref drop to 1, then set it to 0 */ + while (atomic_cmpxchg(&netDevice->RefCount, 1, 0) != 1) + udelay(100); + + Device->Extension = NULL; + return netDevice; +} + +/** + * NetVscInitialize - Main entry point + */ +int NetVscInitialize(struct hv_driver *drv) +{ + struct netvsc_driver *driver = (struct netvsc_driver *)drv; + + DPRINT_ENTER(NETVSC); + + DPRINT_DBG(NETVSC, "sizeof(struct hv_netvsc_packet)=%zd, " + "sizeof(struct nvsp_message)=%zd, " + "sizeof(struct vmtransfer_page_packet_header)=%zd", + sizeof(struct hv_netvsc_packet), + sizeof(struct nvsp_message), + sizeof(struct vmtransfer_page_packet_header)); + + /* Make sure we are at least 2 pages since 1 page is used for control */ + ASSERT(driver->RingBufferSize >= (PAGE_SIZE << 1)); + + drv->name = gDriverName; + memcpy(&drv->deviceType, &gNetVscDeviceType, sizeof(struct hv_guid)); + + /* Make sure it is set by the caller */ + ASSERT(driver->OnReceiveCallback); + ASSERT(driver->OnLinkStatusChanged); + + /* Setup the dispatch table */ + driver->Base.OnDeviceAdd = NetVscOnDeviceAdd; + driver->Base.OnDeviceRemove = NetVscOnDeviceRemove; + driver->Base.OnCleanup = NetVscOnCleanup; + + driver->OnSend = NetVscOnSend; + + RndisFilterInit(driver); + + DPRINT_EXIT(NETVSC); + + return 0; +} + +static int NetVscInitializeReceiveBufferWithNetVsp(struct hv_device *Device) +{ + int ret = 0; + struct netvsc_device *netDevice; + struct nvsp_message *initPacket; + + DPRINT_ENTER(NETVSC); + + netDevice = GetOutboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return -1; + } + ASSERT(netDevice->ReceiveBufferSize > 0); + /* page-size grandularity */ + ASSERT((netDevice->ReceiveBufferSize & (PAGE_SIZE - 1)) == 0); + + netDevice->ReceiveBuffer = + osd_PageAlloc(netDevice->ReceiveBufferSize >> PAGE_SHIFT); + if (!netDevice->ReceiveBuffer) { + DPRINT_ERR(NETVSC, + "unable to allocate receive buffer of size %d", + netDevice->ReceiveBufferSize); + ret = -1; + goto Cleanup; + } + /* page-aligned buffer */ + ASSERT(((unsigned long)netDevice->ReceiveBuffer & (PAGE_SIZE - 1)) == + 0); + + DPRINT_INFO(NETVSC, "Establishing receive buffer's GPADL..."); + + /* + * Establish the gpadl handle for this buffer on this + * channel. Note: This call uses the vmbus connection rather + * than the channel to establish the gpadl handle. + */ + ret = Device->Driver->VmbusChannelInterface.EstablishGpadl(Device, + netDevice->ReceiveBuffer, + netDevice->ReceiveBufferSize, + &netDevice->ReceiveBufferGpadlHandle); + if (ret != 0) { + DPRINT_ERR(NETVSC, + "unable to establish receive buffer's gpadl"); + goto Cleanup; + } + + /* osd_WaitEventWait(ext->ChannelInitEvent); */ + + /* Notify the NetVsp of the gpadl handle */ + DPRINT_INFO(NETVSC, "Sending NvspMessage1TypeSendReceiveBuffer..."); + + initPacket = &netDevice->ChannelInitPacket; + + memset(initPacket, 0, sizeof(struct nvsp_message)); + + initPacket->Header.MessageType = NvspMessage1TypeSendReceiveBuffer; + initPacket->Messages.Version1Messages.SendReceiveBuffer.GpadlHandle = netDevice->ReceiveBufferGpadlHandle; + initPacket->Messages.Version1Messages.SendReceiveBuffer.Id = NETVSC_RECEIVE_BUFFER_ID; + + /* Send the gpadl notification request */ + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + initPacket, + sizeof(struct nvsp_message), + (unsigned long)initPacket, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + DPRINT_ERR(NETVSC, + "unable to send receive buffer's gpadl to netvsp"); + goto Cleanup; + } + + osd_WaitEventWait(netDevice->ChannelInitEvent); + + /* Check the response */ + if (initPacket->Messages.Version1Messages.SendReceiveBufferComplete.Status != NvspStatusSuccess) { + DPRINT_ERR(NETVSC, "Unable to complete receive buffer " + "initialzation with NetVsp - status %d", + initPacket->Messages.Version1Messages.SendReceiveBufferComplete.Status); + ret = -1; + goto Cleanup; + } + + /* Parse the response */ + ASSERT(netDevice->ReceiveSectionCount == 0); + ASSERT(netDevice->ReceiveSections == NULL); + + netDevice->ReceiveSectionCount = initPacket->Messages.Version1Messages.SendReceiveBufferComplete.NumSections; + + netDevice->ReceiveSections = kmalloc(netDevice->ReceiveSectionCount * sizeof(struct nvsp_1_receive_buffer_section), GFP_KERNEL); + if (netDevice->ReceiveSections == NULL) { + ret = -1; + goto Cleanup; + } + + memcpy(netDevice->ReceiveSections, + initPacket->Messages.Version1Messages.SendReceiveBufferComplete.Sections, + netDevice->ReceiveSectionCount * sizeof(struct nvsp_1_receive_buffer_section)); + + DPRINT_INFO(NETVSC, "Receive sections info (count %d, offset %d, " + "endoffset %d, suballoc size %d, num suballocs %d)", + netDevice->ReceiveSectionCount, + netDevice->ReceiveSections[0].Offset, + netDevice->ReceiveSections[0].EndOffset, + netDevice->ReceiveSections[0].SubAllocationSize, + netDevice->ReceiveSections[0].NumSubAllocations); + + /* + * For 1st release, there should only be 1 section that represents the + * entire receive buffer + */ + if (netDevice->ReceiveSectionCount != 1 || + netDevice->ReceiveSections->Offset != 0) { + ret = -1; + goto Cleanup; + } + + goto Exit; + +Cleanup: + NetVscDestroyReceiveBuffer(netDevice); + +Exit: + PutNetDevice(Device); + DPRINT_EXIT(NETVSC); + return ret; +} + +static int NetVscInitializeSendBufferWithNetVsp(struct hv_device *Device) +{ + int ret = 0; + struct netvsc_device *netDevice; + struct nvsp_message *initPacket; + + DPRINT_ENTER(NETVSC); + + netDevice = GetOutboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return -1; + } + ASSERT(netDevice->SendBufferSize > 0); + /* page-size grandularity */ + ASSERT((netDevice->SendBufferSize & (PAGE_SIZE - 1)) == 0); + + netDevice->SendBuffer = + osd_PageAlloc(netDevice->SendBufferSize >> PAGE_SHIFT); + if (!netDevice->SendBuffer) { + DPRINT_ERR(NETVSC, "unable to allocate send buffer of size %d", + netDevice->SendBufferSize); + ret = -1; + goto Cleanup; + } + /* page-aligned buffer */ + ASSERT(((unsigned long)netDevice->SendBuffer & (PAGE_SIZE - 1)) == 0); + + DPRINT_INFO(NETVSC, "Establishing send buffer's GPADL..."); + + /* + * Establish the gpadl handle for this buffer on this + * channel. Note: This call uses the vmbus connection rather + * than the channel to establish the gpadl handle. + */ + ret = Device->Driver->VmbusChannelInterface.EstablishGpadl(Device, + netDevice->SendBuffer, + netDevice->SendBufferSize, + &netDevice->SendBufferGpadlHandle); + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to establish send buffer's gpadl"); + goto Cleanup; + } + + /* osd_WaitEventWait(ext->ChannelInitEvent); */ + + /* Notify the NetVsp of the gpadl handle */ + DPRINT_INFO(NETVSC, "Sending NvspMessage1TypeSendSendBuffer..."); + + initPacket = &netDevice->ChannelInitPacket; + + memset(initPacket, 0, sizeof(struct nvsp_message)); + + initPacket->Header.MessageType = NvspMessage1TypeSendSendBuffer; + initPacket->Messages.Version1Messages.SendReceiveBuffer.GpadlHandle = netDevice->SendBufferGpadlHandle; + initPacket->Messages.Version1Messages.SendReceiveBuffer.Id = NETVSC_SEND_BUFFER_ID; + + /* Send the gpadl notification request */ + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + initPacket, sizeof(struct nvsp_message), + (unsigned long)initPacket, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + DPRINT_ERR(NETVSC, + "unable to send receive buffer's gpadl to netvsp"); + goto Cleanup; + } + + osd_WaitEventWait(netDevice->ChannelInitEvent); + + /* Check the response */ + if (initPacket->Messages.Version1Messages.SendSendBufferComplete.Status != NvspStatusSuccess) { + DPRINT_ERR(NETVSC, "Unable to complete send buffer " + "initialzation with NetVsp - status %d", + initPacket->Messages.Version1Messages.SendSendBufferComplete.Status); + ret = -1; + goto Cleanup; + } + + netDevice->SendSectionSize = initPacket->Messages.Version1Messages.SendSendBufferComplete.SectionSize; + + goto Exit; + +Cleanup: + NetVscDestroySendBuffer(netDevice); + +Exit: + PutNetDevice(Device); + DPRINT_EXIT(NETVSC); + return ret; +} + +static int NetVscDestroyReceiveBuffer(struct netvsc_device *NetDevice) +{ + struct nvsp_message *revokePacket; + int ret = 0; + + DPRINT_ENTER(NETVSC); + + /* + * If we got a section count, it means we received a + * SendReceiveBufferComplete msg (ie sent + * NvspMessage1TypeSendReceiveBuffer msg) therefore, we need + * to send a revoke msg here + */ + if (NetDevice->ReceiveSectionCount) { + DPRINT_INFO(NETVSC, + "Sending NvspMessage1TypeRevokeReceiveBuffer..."); + + /* Send the revoke receive buffer */ + revokePacket = &NetDevice->RevokePacket; + memset(revokePacket, 0, sizeof(struct nvsp_message)); + + revokePacket->Header.MessageType = NvspMessage1TypeRevokeReceiveBuffer; + revokePacket->Messages.Version1Messages.RevokeReceiveBuffer.Id = NETVSC_RECEIVE_BUFFER_ID; + + ret = NetDevice->Device->Driver->VmbusChannelInterface.SendPacket( + NetDevice->Device, + revokePacket, + sizeof(struct nvsp_message), + (unsigned long)revokePacket, + VmbusPacketTypeDataInBand, 0); + /* + * If we failed here, we might as well return and + * have a leak rather than continue and a bugchk + */ + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to send revoke receive " + "buffer to netvsp"); + DPRINT_EXIT(NETVSC); + return -1; + } + } + + /* Teardown the gpadl on the vsp end */ + if (NetDevice->ReceiveBufferGpadlHandle) { + DPRINT_INFO(NETVSC, "Tearing down receive buffer's GPADL..."); + + ret = NetDevice->Device->Driver->VmbusChannelInterface.TeardownGpadl( + NetDevice->Device, + NetDevice->ReceiveBufferGpadlHandle); + + /* If we failed here, we might as well return and have a leak rather than continue and a bugchk */ + if (ret != 0) { + DPRINT_ERR(NETVSC, + "unable to teardown receive buffer's gpadl"); + DPRINT_EXIT(NETVSC); + return -1; + } + NetDevice->ReceiveBufferGpadlHandle = 0; + } + + if (NetDevice->ReceiveBuffer) { + DPRINT_INFO(NETVSC, "Freeing up receive buffer..."); + + /* Free up the receive buffer */ + osd_PageFree(NetDevice->ReceiveBuffer, + NetDevice->ReceiveBufferSize >> PAGE_SHIFT); + NetDevice->ReceiveBuffer = NULL; + } + + if (NetDevice->ReceiveSections) { + NetDevice->ReceiveSectionCount = 0; + kfree(NetDevice->ReceiveSections); + NetDevice->ReceiveSections = NULL; + } + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int NetVscDestroySendBuffer(struct netvsc_device *NetDevice) +{ + struct nvsp_message *revokePacket; + int ret = 0; + + DPRINT_ENTER(NETVSC); + + /* + * If we got a section count, it means we received a + * SendReceiveBufferComplete msg (ie sent + * NvspMessage1TypeSendReceiveBuffer msg) therefore, we need + * to send a revoke msg here + */ + if (NetDevice->SendSectionSize) { + DPRINT_INFO(NETVSC, + "Sending NvspMessage1TypeRevokeSendBuffer..."); + + /* Send the revoke send buffer */ + revokePacket = &NetDevice->RevokePacket; + memset(revokePacket, 0, sizeof(struct nvsp_message)); + + revokePacket->Header.MessageType = NvspMessage1TypeRevokeSendBuffer; + revokePacket->Messages.Version1Messages.RevokeSendBuffer.Id = NETVSC_SEND_BUFFER_ID; + + ret = NetDevice->Device->Driver->VmbusChannelInterface.SendPacket(NetDevice->Device, + revokePacket, + sizeof(struct nvsp_message), + (unsigned long)revokePacket, + VmbusPacketTypeDataInBand, 0); + /* + * If we failed here, we might as well return and have a leak + * rather than continue and a bugchk + */ + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to send revoke send buffer " + "to netvsp"); + DPRINT_EXIT(NETVSC); + return -1; + } + } + + /* Teardown the gpadl on the vsp end */ + if (NetDevice->SendBufferGpadlHandle) { + DPRINT_INFO(NETVSC, "Tearing down send buffer's GPADL..."); + + ret = NetDevice->Device->Driver->VmbusChannelInterface.TeardownGpadl(NetDevice->Device, NetDevice->SendBufferGpadlHandle); + + /* + * If we failed here, we might as well return and have a leak + * rather than continue and a bugchk + */ + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to teardown send buffer's " + "gpadl"); + DPRINT_EXIT(NETVSC); + return -1; + } + NetDevice->SendBufferGpadlHandle = 0; + } + + if (NetDevice->SendBuffer) { + DPRINT_INFO(NETVSC, "Freeing up send buffer..."); + + /* Free up the receive buffer */ + osd_PageFree(NetDevice->SendBuffer, + NetDevice->SendBufferSize >> PAGE_SHIFT); + NetDevice->SendBuffer = NULL; + } + + DPRINT_EXIT(NETVSC); + + return ret; +} + + +static int NetVscConnectToVsp(struct hv_device *Device) +{ + int ret; + struct netvsc_device *netDevice; + struct nvsp_message *initPacket; + int ndisVersion; + + DPRINT_ENTER(NETVSC); + + netDevice = GetOutboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return -1; + } + + initPacket = &netDevice->ChannelInitPacket; + + memset(initPacket, 0, sizeof(struct nvsp_message)); + initPacket->Header.MessageType = NvspMessageTypeInit; + initPacket->Messages.InitMessages.Init.MinProtocolVersion = NVSP_MIN_PROTOCOL_VERSION; + initPacket->Messages.InitMessages.Init.MaxProtocolVersion = NVSP_MAX_PROTOCOL_VERSION; + + DPRINT_INFO(NETVSC, "Sending NvspMessageTypeInit..."); + + /* Send the init request */ + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + initPacket, + sizeof(struct nvsp_message), + (unsigned long)initPacket, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to send NvspMessageTypeInit"); + goto Cleanup; + } + + osd_WaitEventWait(netDevice->ChannelInitEvent); + + /* Now, check the response */ + /* ASSERT(initPacket->Messages.InitMessages.InitComplete.MaximumMdlChainLength <= MAX_MULTIPAGE_BUFFER_COUNT); */ + DPRINT_INFO(NETVSC, "NvspMessageTypeInit status(%d) max mdl chain (%d)", + initPacket->Messages.InitMessages.InitComplete.Status, + initPacket->Messages.InitMessages.InitComplete.MaximumMdlChainLength); + + if (initPacket->Messages.InitMessages.InitComplete.Status != + NvspStatusSuccess) { + DPRINT_ERR(NETVSC, + "unable to initialize with netvsp (status 0x%x)", + initPacket->Messages.InitMessages.InitComplete.Status); + ret = -1; + goto Cleanup; + } + + if (initPacket->Messages.InitMessages.InitComplete.NegotiatedProtocolVersion != NVSP_PROTOCOL_VERSION_1) { + DPRINT_ERR(NETVSC, "unable to initialize with netvsp " + "(version expected 1 got %d)", + initPacket->Messages.InitMessages.InitComplete.NegotiatedProtocolVersion); + ret = -1; + goto Cleanup; + } + DPRINT_INFO(NETVSC, "Sending NvspMessage1TypeSendNdisVersion..."); + + /* Send the ndis version */ + memset(initPacket, 0, sizeof(struct nvsp_message)); + + ndisVersion = 0x00050000; + + initPacket->Header.MessageType = NvspMessage1TypeSendNdisVersion; + initPacket->Messages.Version1Messages.SendNdisVersion.NdisMajorVersion = + (ndisVersion & 0xFFFF0000) >> 16; + initPacket->Messages.Version1Messages.SendNdisVersion.NdisMinorVersion = + ndisVersion & 0xFFFF; + + /* Send the init request */ + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + initPacket, + sizeof(struct nvsp_message), + (unsigned long)initPacket, + VmbusPacketTypeDataInBand, 0); + if (ret != 0) { + DPRINT_ERR(NETVSC, + "unable to send NvspMessage1TypeSendNdisVersion"); + ret = -1; + goto Cleanup; + } + /* + * BUGBUG - We have to wait for the above msg since the + * netvsp uses KMCL which acknowledges packet (completion + * packet) since our Vmbus always set the + * VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED flag + */ + /* osd_WaitEventWait(NetVscChannel->ChannelInitEvent); */ + + /* Post the big receive buffer to NetVSP */ + ret = NetVscInitializeReceiveBufferWithNetVsp(Device); + if (ret == 0) + ret = NetVscInitializeSendBufferWithNetVsp(Device); + +Cleanup: + PutNetDevice(Device); + DPRINT_EXIT(NETVSC); + return ret; +} + +static void NetVscDisconnectFromVsp(struct netvsc_device *NetDevice) +{ + DPRINT_ENTER(NETVSC); + + NetVscDestroyReceiveBuffer(NetDevice); + NetVscDestroySendBuffer(NetDevice); + + DPRINT_EXIT(NETVSC); +} + +/** + * NetVscOnDeviceAdd - Callback when the device belonging to this driver is added + */ +static int NetVscOnDeviceAdd(struct hv_device *Device, void *AdditionalInfo) +{ + int ret = 0; + int i; + struct netvsc_device *netDevice; + struct hv_netvsc_packet *packet, *pos; + struct netvsc_driver *netDriver = + (struct netvsc_driver *)Device->Driver; + + DPRINT_ENTER(NETVSC); + + netDevice = AllocNetDevice(Device); + if (!netDevice) { + ret = -1; + goto Cleanup; + } + + DPRINT_DBG(NETVSC, "netvsc channel object allocated - %p", netDevice); + + /* Initialize the NetVSC channel extension */ + netDevice->ReceiveBufferSize = NETVSC_RECEIVE_BUFFER_SIZE; + spin_lock_init(&netDevice->receive_packet_list_lock); + + netDevice->SendBufferSize = NETVSC_SEND_BUFFER_SIZE; + + INIT_LIST_HEAD(&netDevice->ReceivePacketList); + + for (i = 0; i < NETVSC_RECEIVE_PACKETLIST_COUNT; i++) { + packet = kzalloc(sizeof(struct hv_netvsc_packet) + + (NETVSC_RECEIVE_SG_COUNT * + sizeof(struct hv_page_buffer)), GFP_KERNEL); + if (!packet) { + DPRINT_DBG(NETVSC, "unable to allocate netvsc pkts " + "for receive pool (wanted %d got %d)", + NETVSC_RECEIVE_PACKETLIST_COUNT, i); + break; + } + list_add_tail(&packet->ListEntry, + &netDevice->ReceivePacketList); + } + netDevice->ChannelInitEvent = osd_WaitEventCreate(); + + /* Open the channel */ + ret = Device->Driver->VmbusChannelInterface.Open(Device, + netDriver->RingBufferSize, + netDriver->RingBufferSize, + NULL, 0, + NetVscOnChannelCallback, + Device); + + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to open channel: %d", ret); + ret = -1; + goto Cleanup; + } + + /* Channel is opened */ + DPRINT_INFO(NETVSC, "*** NetVSC channel opened successfully! ***"); + + /* Connect with the NetVsp */ + ret = NetVscConnectToVsp(Device); + if (ret != 0) { + DPRINT_ERR(NETVSC, "unable to connect to NetVSP - %d", ret); + ret = -1; + goto Close; + } + + DPRINT_INFO(NETVSC, "*** NetVSC channel handshake result - %d ***", + ret); + + DPRINT_EXIT(NETVSC); + return ret; + +Close: + /* Now, we can close the channel safely */ + Device->Driver->VmbusChannelInterface.Close(Device); + +Cleanup: + + if (netDevice) { + kfree(netDevice->ChannelInitEvent); + + list_for_each_entry_safe(packet, pos, + &netDevice->ReceivePacketList, + ListEntry) { + list_del(&packet->ListEntry); + kfree(packet); + } + + ReleaseOutboundNetDevice(Device); + ReleaseInboundNetDevice(Device); + + FreeNetDevice(netDevice); + } + + DPRINT_EXIT(NETVSC); + return ret; +} + +/** + * NetVscOnDeviceRemove - Callback when the root bus device is removed + */ +static int NetVscOnDeviceRemove(struct hv_device *Device) +{ + struct netvsc_device *netDevice; + struct hv_netvsc_packet *netvscPacket, *pos; + + DPRINT_ENTER(NETVSC); + + DPRINT_INFO(NETVSC, "Disabling outbound traffic on net device (%p)...", + Device->Extension); + + /* Stop outbound traffic ie sends and receives completions */ + netDevice = ReleaseOutboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "No net device present!!"); + return -1; + } + + /* Wait for all send completions */ + while (atomic_read(&netDevice->NumOutstandingSends)) { + DPRINT_INFO(NETVSC, "waiting for %d requests to complete...", + atomic_read(&netDevice->NumOutstandingSends)); + udelay(100); + } + + DPRINT_INFO(NETVSC, "Disconnecting from netvsp..."); + + NetVscDisconnectFromVsp(netDevice); + + DPRINT_INFO(NETVSC, "Disabling inbound traffic on net device (%p)...", + Device->Extension); + + /* Stop inbound traffic ie receives and sends completions */ + netDevice = ReleaseInboundNetDevice(Device); + + /* At this point, no one should be accessing netDevice except in here */ + DPRINT_INFO(NETVSC, "net device (%p) safe to remove", netDevice); + + /* Now, we can close the channel safely */ + Device->Driver->VmbusChannelInterface.Close(Device); + + /* Release all resources */ + list_for_each_entry_safe(netvscPacket, pos, + &netDevice->ReceivePacketList, ListEntry) { + list_del(&netvscPacket->ListEntry); + kfree(netvscPacket); + } + + kfree(netDevice->ChannelInitEvent); + FreeNetDevice(netDevice); + + DPRINT_EXIT(NETVSC); + return 0; +} + +/** + * NetVscOnCleanup - Perform any cleanup when the driver is removed + */ +static void NetVscOnCleanup(struct hv_driver *drv) +{ + DPRINT_ENTER(NETVSC); + DPRINT_EXIT(NETVSC); +} + +static void NetVscOnSendCompletion(struct hv_device *Device, + struct vmpacket_descriptor *Packet) +{ + struct netvsc_device *netDevice; + struct nvsp_message *nvspPacket; + struct hv_netvsc_packet *nvscPacket; + + DPRINT_ENTER(NETVSC); + + netDevice = GetInboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return; + } + + nvspPacket = (struct nvsp_message *)((unsigned long)Packet + (Packet->DataOffset8 << 3)); + + DPRINT_DBG(NETVSC, "send completion packet - type %d", + nvspPacket->Header.MessageType); + + if ((nvspPacket->Header.MessageType == NvspMessageTypeInitComplete) || + (nvspPacket->Header.MessageType == + NvspMessage1TypeSendReceiveBufferComplete) || + (nvspPacket->Header.MessageType == + NvspMessage1TypeSendSendBufferComplete)) { + /* Copy the response back */ + memcpy(&netDevice->ChannelInitPacket, nvspPacket, + sizeof(struct nvsp_message)); + osd_WaitEventSet(netDevice->ChannelInitEvent); + } else if (nvspPacket->Header.MessageType == + NvspMessage1TypeSendRNDISPacketComplete) { + /* Get the send context */ + nvscPacket = (struct hv_netvsc_packet *)(unsigned long)Packet->TransactionId; + ASSERT(nvscPacket); + + /* Notify the layer above us */ + nvscPacket->Completion.Send.OnSendCompletion(nvscPacket->Completion.Send.SendCompletionContext); + + atomic_dec(&netDevice->NumOutstandingSends); + } else { + DPRINT_ERR(NETVSC, "Unknown send completion packet type - " + "%d received!!", nvspPacket->Header.MessageType); + } + + PutNetDevice(Device); + DPRINT_EXIT(NETVSC); +} + +static int NetVscOnSend(struct hv_device *Device, + struct hv_netvsc_packet *Packet) +{ + struct netvsc_device *netDevice; + int ret = 0; + + struct nvsp_message sendMessage; + + DPRINT_ENTER(NETVSC); + + netDevice = GetOutboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "net device (%p) shutting down..." + "ignoring outbound packets", netDevice); + DPRINT_EXIT(NETVSC); + return -2; + } + + sendMessage.Header.MessageType = NvspMessage1TypeSendRNDISPacket; + if (Packet->IsDataPacket) { + /* 0 is RMC_DATA; */ + sendMessage.Messages.Version1Messages.SendRNDISPacket.ChannelType = 0; + } else { + /* 1 is RMC_CONTROL; */ + sendMessage.Messages.Version1Messages.SendRNDISPacket.ChannelType = 1; + } + + /* Not using send buffer section */ + sendMessage.Messages.Version1Messages.SendRNDISPacket.SendBufferSectionIndex = 0xFFFFFFFF; + sendMessage.Messages.Version1Messages.SendRNDISPacket.SendBufferSectionSize = 0; + + if (Packet->PageBufferCount) { + ret = Device->Driver->VmbusChannelInterface.SendPacketPageBuffer( + Device, Packet->PageBuffers, + Packet->PageBufferCount, + &sendMessage, + sizeof(struct nvsp_message), + (unsigned long)Packet); + } else { + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + &sendMessage, + sizeof(struct nvsp_message), + (unsigned long)Packet, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + } + + if (ret != 0) + DPRINT_ERR(NETVSC, "Unable to send packet %p ret %d", + Packet, ret); + + atomic_inc(&netDevice->NumOutstandingSends); + PutNetDevice(Device); + + DPRINT_EXIT(NETVSC); + return ret; +} + +static void NetVscOnReceive(struct hv_device *Device, + struct vmpacket_descriptor *Packet) +{ + struct netvsc_device *netDevice; + struct vmtransfer_page_packet_header *vmxferpagePacket; + struct nvsp_message *nvspPacket; + struct hv_netvsc_packet *netvscPacket = NULL; + unsigned long start; + unsigned long end, endVirtual; + /* struct netvsc_driver *netvscDriver; */ + struct xferpage_packet *xferpagePacket = NULL; + int i, j; + int count = 0, bytesRemain = 0; + unsigned long flags; + LIST_HEAD(listHead); + + DPRINT_ENTER(NETVSC); + + netDevice = GetInboundNetDevice(Device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return; + } + + /* + * All inbound packets other than send completion should be xfer page + * packet + */ + if (Packet->Type != VmbusPacketTypeDataUsingTransferPages) { + DPRINT_ERR(NETVSC, "Unknown packet type received - %d", + Packet->Type); + PutNetDevice(Device); + return; + } + + nvspPacket = (struct nvsp_message *)((unsigned long)Packet + + (Packet->DataOffset8 << 3)); + + /* Make sure this is a valid nvsp packet */ + if (nvspPacket->Header.MessageType != NvspMessage1TypeSendRNDISPacket) { + DPRINT_ERR(NETVSC, "Unknown nvsp packet type received - %d", + nvspPacket->Header.MessageType); + PutNetDevice(Device); + return; + } + + DPRINT_DBG(NETVSC, "NVSP packet received - type %d", + nvspPacket->Header.MessageType); + + vmxferpagePacket = (struct vmtransfer_page_packet_header *)Packet; + + if (vmxferpagePacket->TransferPageSetId != NETVSC_RECEIVE_BUFFER_ID) { + DPRINT_ERR(NETVSC, "Invalid xfer page set id - " + "expecting %x got %x", NETVSC_RECEIVE_BUFFER_ID, + vmxferpagePacket->TransferPageSetId); + PutNetDevice(Device); + return; + } + + DPRINT_DBG(NETVSC, "xfer page - range count %d", + vmxferpagePacket->RangeCount); + + /* + * Grab free packets (range count + 1) to represent this xfer + * page packet. +1 to represent the xfer page packet itself. + * We grab it here so that we know exactly how many we can + * fulfil + */ + spin_lock_irqsave(&netDevice->receive_packet_list_lock, flags); + while (!list_empty(&netDevice->ReceivePacketList)) { + list_move_tail(&netDevice->ReceivePacketList, &listHead); + if (++count == vmxferpagePacket->RangeCount + 1) + break; + } + spin_unlock_irqrestore(&netDevice->receive_packet_list_lock, flags); + + /* + * We need at least 2 netvsc pkts (1 to represent the xfer + * page and at least 1 for the range) i.e. we can handled + * some of the xfer page packet ranges... + */ + if (count < 2) { + DPRINT_ERR(NETVSC, "Got only %d netvsc pkt...needed %d pkts. " + "Dropping this xfer page packet completely!", + count, vmxferpagePacket->RangeCount + 1); + + /* Return it to the freelist */ + spin_lock_irqsave(&netDevice->receive_packet_list_lock, flags); + for (i = count; i != 0; i--) { + list_move_tail(&listHead, + &netDevice->ReceivePacketList); + } + spin_unlock_irqrestore(&netDevice->receive_packet_list_lock, + flags); + + NetVscSendReceiveCompletion(Device, + vmxferpagePacket->d.TransactionId); + + PutNetDevice(Device); + return; + } + + /* Remove the 1st packet to represent the xfer page packet itself */ + xferpagePacket = list_entry(&listHead, struct xferpage_packet, + ListEntry); + list_del(&xferpagePacket->ListEntry); + + /* This is how much we can satisfy */ + xferpagePacket->Count = count - 1; + ASSERT(xferpagePacket->Count > 0 && xferpagePacket->Count <= + vmxferpagePacket->RangeCount); + + if (xferpagePacket->Count != vmxferpagePacket->RangeCount) { + DPRINT_INFO(NETVSC, "Needed %d netvsc pkts to satisy this xfer " + "page...got %d", vmxferpagePacket->RangeCount, + xferpagePacket->Count); + } + + /* Each range represents 1 RNDIS pkt that contains 1 ethernet frame */ + for (i = 0; i < (count - 1); i++) { + netvscPacket = list_entry(&listHead, struct hv_netvsc_packet, + ListEntry); + list_del(&netvscPacket->ListEntry); + + /* Initialize the netvsc packet */ + netvscPacket->XferPagePacket = xferpagePacket; + netvscPacket->Completion.Recv.OnReceiveCompletion = + NetVscOnReceiveCompletion; + netvscPacket->Completion.Recv.ReceiveCompletionContext = + netvscPacket; + netvscPacket->Device = Device; + /* Save this so that we can send it back */ + netvscPacket->Completion.Recv.ReceiveCompletionTid = + vmxferpagePacket->d.TransactionId; + + netvscPacket->TotalDataBufferLength = + vmxferpagePacket->Ranges[i].ByteCount; + netvscPacket->PageBufferCount = 1; + + ASSERT(vmxferpagePacket->Ranges[i].ByteOffset + + vmxferpagePacket->Ranges[i].ByteCount < + netDevice->ReceiveBufferSize); + + netvscPacket->PageBuffers[0].Length = + vmxferpagePacket->Ranges[i].ByteCount; + + start = virt_to_phys((void *)((unsigned long)netDevice->ReceiveBuffer + vmxferpagePacket->Ranges[i].ByteOffset)); + + netvscPacket->PageBuffers[0].Pfn = start >> PAGE_SHIFT; + endVirtual = (unsigned long)netDevice->ReceiveBuffer + + vmxferpagePacket->Ranges[i].ByteOffset + + vmxferpagePacket->Ranges[i].ByteCount - 1; + end = virt_to_phys((void *)endVirtual); + + /* Calculate the page relative offset */ + netvscPacket->PageBuffers[0].Offset = + vmxferpagePacket->Ranges[i].ByteOffset & (PAGE_SIZE - 1); + if ((end >> PAGE_SHIFT) != (start >> PAGE_SHIFT)) { + /* Handle frame across multiple pages: */ + netvscPacket->PageBuffers[0].Length = + (netvscPacket->PageBuffers[0].Pfn << PAGE_SHIFT) + + PAGE_SIZE - start; + bytesRemain = netvscPacket->TotalDataBufferLength - + netvscPacket->PageBuffers[0].Length; + for (j = 1; j < NETVSC_PACKET_MAXPAGE; j++) { + netvscPacket->PageBuffers[j].Offset = 0; + if (bytesRemain <= PAGE_SIZE) { + netvscPacket->PageBuffers[j].Length = bytesRemain; + bytesRemain = 0; + } else { + netvscPacket->PageBuffers[j].Length = PAGE_SIZE; + bytesRemain -= PAGE_SIZE; + } + netvscPacket->PageBuffers[j].Pfn = + virt_to_phys((void *)(endVirtual - bytesRemain)) >> PAGE_SHIFT; + netvscPacket->PageBufferCount++; + if (bytesRemain == 0) + break; + } + ASSERT(bytesRemain == 0); + } + DPRINT_DBG(NETVSC, "[%d] - (abs offset %u len %u) => " + "(pfn %llx, offset %u, len %u)", i, + vmxferpagePacket->Ranges[i].ByteOffset, + vmxferpagePacket->Ranges[i].ByteCount, + netvscPacket->PageBuffers[0].Pfn, + netvscPacket->PageBuffers[0].Offset, + netvscPacket->PageBuffers[0].Length); + + /* Pass it to the upper layer */ + ((struct netvsc_driver *)Device->Driver)->OnReceiveCallback(Device, netvscPacket); + + NetVscOnReceiveCompletion(netvscPacket->Completion.Recv.ReceiveCompletionContext); + } + + ASSERT(list_empty(&listHead)); + + PutNetDevice(Device); + DPRINT_EXIT(NETVSC); +} + +static void NetVscSendReceiveCompletion(struct hv_device *Device, + u64 TransactionId) +{ + struct nvsp_message recvcompMessage; + int retries = 0; + int ret; + + DPRINT_DBG(NETVSC, "Sending receive completion pkt - %llx", + TransactionId); + + recvcompMessage.Header.MessageType = + NvspMessage1TypeSendRNDISPacketComplete; + + /* FIXME: Pass in the status */ + recvcompMessage.Messages.Version1Messages.SendRNDISPacketComplete.Status = NvspStatusSuccess; + +retry_send_cmplt: + /* Send the completion */ + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + &recvcompMessage, + sizeof(struct nvsp_message), + TransactionId, + VmbusPacketTypeCompletion, 0); + if (ret == 0) { + /* success */ + /* no-op */ + } else if (ret == -1) { + /* no more room...wait a bit and attempt to retry 3 times */ + retries++; + DPRINT_ERR(NETVSC, "unable to send receive completion pkt " + "(tid %llx)...retrying %d", TransactionId, retries); + + if (retries < 4) { + udelay(100); + goto retry_send_cmplt; + } else { + DPRINT_ERR(NETVSC, "unable to send receive completion " + "pkt (tid %llx)...give up retrying", + TransactionId); + } + } else { + DPRINT_ERR(NETVSC, "unable to send receive completion pkt - " + "%llx", TransactionId); + } +} + +/* Send a receive completion packet to RNDIS device (ie NetVsp) */ +static void NetVscOnReceiveCompletion(void *Context) +{ + struct hv_netvsc_packet *packet = Context; + struct hv_device *device = (struct hv_device *)packet->Device; + struct netvsc_device *netDevice; + u64 transactionId = 0; + bool fSendReceiveComp = false; + unsigned long flags; + + DPRINT_ENTER(NETVSC); + + ASSERT(packet->XferPagePacket); + + /* + * Even though it seems logical to do a GetOutboundNetDevice() here to + * send out receive completion, we are using GetInboundNetDevice() + * since we may have disable outbound traffic already. + */ + netDevice = GetInboundNetDevice(device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "unable to get net device..." + "device being destroyed?"); + DPRINT_EXIT(NETVSC); + return; + } + + /* Overloading use of the lock. */ + spin_lock_irqsave(&netDevice->receive_packet_list_lock, flags); + + ASSERT(packet->XferPagePacket->Count > 0); + packet->XferPagePacket->Count--; + + /* + * Last one in the line that represent 1 xfer page packet. + * Return the xfer page packet itself to the freelist + */ + if (packet->XferPagePacket->Count == 0) { + fSendReceiveComp = true; + transactionId = packet->Completion.Recv.ReceiveCompletionTid; + list_add_tail(&packet->XferPagePacket->ListEntry, + &netDevice->ReceivePacketList); + + } + + /* Put the packet back */ + list_add_tail(&packet->ListEntry, &netDevice->ReceivePacketList); + spin_unlock_irqrestore(&netDevice->receive_packet_list_lock, flags); + + /* Send a receive completion for the xfer page packet */ + if (fSendReceiveComp) + NetVscSendReceiveCompletion(device, transactionId); + + PutNetDevice(device); + DPRINT_EXIT(NETVSC); +} + +void NetVscOnChannelCallback(void *Context) +{ + const int netPacketSize = 2048; + int ret; + struct hv_device *device = Context; + struct netvsc_device *netDevice; + u32 bytesRecvd; + u64 requestId; + unsigned char packet[netPacketSize]; + struct vmpacket_descriptor *desc; + unsigned char *buffer = packet; + int bufferlen = netPacketSize; + + + DPRINT_ENTER(NETVSC); + + ASSERT(device); + + netDevice = GetInboundNetDevice(device); + if (!netDevice) { + DPRINT_ERR(NETVSC, "net device (%p) shutting down..." + "ignoring inbound packets", netDevice); + DPRINT_EXIT(NETVSC); + return; + } + + do { + ret = device->Driver->VmbusChannelInterface.RecvPacketRaw( + device, buffer, bufferlen, + &bytesRecvd, &requestId); + if (ret == 0) { + if (bytesRecvd > 0) { + DPRINT_DBG(NETVSC, "receive %d bytes, tid %llx", + bytesRecvd, requestId); + + desc = (struct vmpacket_descriptor *)buffer; + switch (desc->Type) { + case VmbusPacketTypeCompletion: + NetVscOnSendCompletion(device, desc); + break; + + case VmbusPacketTypeDataUsingTransferPages: + NetVscOnReceive(device, desc); + break; + + default: + DPRINT_ERR(NETVSC, + "unhandled packet type %d, " + "tid %llx len %d\n", + desc->Type, requestId, + bytesRecvd); + break; + } + + /* reset */ + if (bufferlen > netPacketSize) { + kfree(buffer); + buffer = packet; + bufferlen = netPacketSize; + } + } else { + /* reset */ + if (bufferlen > netPacketSize) { + kfree(buffer); + buffer = packet; + bufferlen = netPacketSize; + } + + break; + } + } else if (ret == -2) { + /* Handle large packet */ + buffer = kmalloc(bytesRecvd, GFP_ATOMIC); + if (buffer == NULL) { + /* Try again next time around */ + DPRINT_ERR(NETVSC, + "unable to allocate buffer of size " + "(%d)!!", bytesRecvd); + break; + } + + bufferlen = bytesRecvd; + } else { + ASSERT(0); + } + } while (1); + + PutNetDevice(device); + DPRINT_EXIT(NETVSC); + return; +} diff --git a/drivers/staging/hv/NetVsc.h b/drivers/staging/hv/NetVsc.h new file mode 100644 index 00000000000..3e7112f7c75 --- /dev/null +++ b/drivers/staging/hv/NetVsc.h @@ -0,0 +1,329 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _NETVSC_H_ +#define _NETVSC_H_ + +#include <linux/list.h> +#include "VmbusPacketFormat.h" +#include "VmbusChannelInterface.h" +#include "NetVscApi.h" + + +#define NVSP_INVALID_PROTOCOL_VERSION ((u32)0xFFFFFFFF) + +#define NVSP_PROTOCOL_VERSION_1 2 +#define NVSP_MIN_PROTOCOL_VERSION NVSP_PROTOCOL_VERSION_1 +#define NVSP_MAX_PROTOCOL_VERSION NVSP_PROTOCOL_VERSION_1 + +enum { + NvspMessageTypeNone = 0, + + /* Init Messages */ + NvspMessageTypeInit = 1, + NvspMessageTypeInitComplete = 2, + + NvspVersionMessageStart = 100, + + /* Version 1 Messages */ + NvspMessage1TypeSendNdisVersion = NvspVersionMessageStart, + + NvspMessage1TypeSendReceiveBuffer, + NvspMessage1TypeSendReceiveBufferComplete, + NvspMessage1TypeRevokeReceiveBuffer, + + NvspMessage1TypeSendSendBuffer, + NvspMessage1TypeSendSendBufferComplete, + NvspMessage1TypeRevokeSendBuffer, + + NvspMessage1TypeSendRNDISPacket, + NvspMessage1TypeSendRNDISPacketComplete, + + /* + * This should be set to the number of messages for the version with + * the maximum number of messages. + */ + NvspNumMessagePerVersion = 9, +}; + +enum { + NvspStatusNone = 0, + NvspStatusSuccess, + NvspStatusFailure, + NvspStatusProtocolVersionRangeTooNew, + NvspStatusProtocolVersionRangeTooOld, + NvspStatusInvalidRndisPacket, + NvspStatusBusy, + NvspStatusMax, +}; + +struct nvsp_message_header { + u32 MessageType; +}; + +/* Init Messages */ + +/* + * This message is used by the VSC to initialize the channel after the channels + * has been opened. This message should never include anything other then + * versioning (i.e. this message will be the same for ever). + */ +struct nvsp_message_init { + u32 MinProtocolVersion; + u32 MaxProtocolVersion; +} __attribute__((packed)); + +/* + * This message is used by the VSP to complete the initialization of the + * channel. This message should never include anything other then versioning + * (i.e. this message will be the same for ever). + */ +struct nvsp_message_init_complete { + u32 NegotiatedProtocolVersion; + u32 MaximumMdlChainLength; + u32 Status; +} __attribute__((packed)); + +union nvsp_message_init_uber { + struct nvsp_message_init Init; + struct nvsp_message_init_complete InitComplete; +} __attribute__((packed)); + +/* Version 1 Messages */ + +/* + * This message is used by the VSC to send the NDIS version to the VSP. The VSP + * can use this information when handling OIDs sent by the VSC. + */ +struct nvsp_1_message_send_ndis_version { + u32 NdisMajorVersion; + u32 NdisMinorVersion; +} __attribute__((packed)); + +/* + * This message is used by the VSC to send a receive buffer to the VSP. The VSP + * can then use the receive buffer to send data to the VSC. + */ +struct nvsp_1_message_send_receive_buffer { + u32 GpadlHandle; + u16 Id; +} __attribute__((packed)); + +struct nvsp_1_receive_buffer_section { + u32 Offset; + u32 SubAllocationSize; + u32 NumSubAllocations; + u32 EndOffset; +} __attribute__((packed)); + +/* + * This message is used by the VSP to acknowledge a receive buffer send by the + * VSC. This message must be sent by the VSP before the VSP uses the receive + * buffer. + */ +struct nvsp_1_message_send_receive_buffer_complete { + u32 Status; + u32 NumSections; + + /* + * The receive buffer is split into two parts, a large suballocation + * section and a small suballocation section. These sections are then + * suballocated by a certain size. + */ + + /* + * For example, the following break up of the receive buffer has 6 + * large suballocations and 10 small suballocations. + */ + + /* + * | Large Section | | Small Section | + * ------------------------------------------------------------ + * | | | | | | | | | | | | | | | | | | + * | | + * LargeOffset SmallOffset + */ + + struct nvsp_1_receive_buffer_section Sections[1]; +} __attribute__((packed)); + +/* + * This message is sent by the VSC to revoke the receive buffer. After the VSP + * completes this transaction, the vsp should never use the receive buffer + * again. + */ +struct nvsp_1_message_revoke_receive_buffer { + u16 Id; +}; + +/* + * This message is used by the VSC to send a send buffer to the VSP. The VSC + * can then use the send buffer to send data to the VSP. + */ +struct nvsp_1_message_send_send_buffer { + u32 GpadlHandle; + u16 Id; +} __attribute__((packed)); + +/* + * This message is used by the VSP to acknowledge a send buffer sent by the + * VSC. This message must be sent by the VSP before the VSP uses the sent + * buffer. + */ +struct nvsp_1_message_send_send_buffer_complete { + u32 Status; + + /* + * The VSC gets to choose the size of the send buffer and the VSP gets + * to choose the sections size of the buffer. This was done to enable + * dynamic reconfigurations when the cost of GPA-direct buffers + * decreases. + */ + u32 SectionSize; +} __attribute__((packed)); + +/* + * This message is sent by the VSC to revoke the send buffer. After the VSP + * completes this transaction, the vsp should never use the send buffer again. + */ +struct nvsp_1_message_revoke_send_buffer { + u16 Id; +}; + +/* + * This message is used by both the VSP and the VSC to send a RNDIS message to + * the opposite channel endpoint. + */ +struct nvsp_1_message_send_rndis_packet { + /* + * This field is specified by RNIDS. They assume there's two different + * channels of communication. However, the Network VSP only has one. + * Therefore, the channel travels with the RNDIS packet. + */ + u32 ChannelType; + + /* + * This field is used to send part or all of the data through a send + * buffer. This values specifies an index into the send buffer. If the + * index is 0xFFFFFFFF, then the send buffer is not being used and all + * of the data was sent through other VMBus mechanisms. + */ + u32 SendBufferSectionIndex; + u32 SendBufferSectionSize; +} __attribute__((packed)); + +/* + * This message is used by both the VSP and the VSC to complete a RNDIS message + * to the opposite channel endpoint. At this point, the initiator of this + * message cannot use any resources associated with the original RNDIS packet. + */ +struct nvsp_1_message_send_rndis_packet_complete { + u32 Status; +}; + +union nvsp_1_message_uber { + struct nvsp_1_message_send_ndis_version SendNdisVersion; + + struct nvsp_1_message_send_receive_buffer SendReceiveBuffer; + struct nvsp_1_message_send_receive_buffer_complete + SendReceiveBufferComplete; + struct nvsp_1_message_revoke_receive_buffer RevokeReceiveBuffer; + + struct nvsp_1_message_send_send_buffer SendSendBuffer; + struct nvsp_1_message_send_send_buffer_complete SendSendBufferComplete; + struct nvsp_1_message_revoke_send_buffer RevokeSendBuffer; + + struct nvsp_1_message_send_rndis_packet SendRNDISPacket; + struct nvsp_1_message_send_rndis_packet_complete + SendRNDISPacketComplete; +} __attribute__((packed)); + +union nvsp_all_messages { + union nvsp_message_init_uber InitMessages; + union nvsp_1_message_uber Version1Messages; +} __attribute__((packed)); + +/* ALL Messages */ +struct nvsp_message { + struct nvsp_message_header Header; + union nvsp_all_messages Messages; +} __attribute__((packed)); + + + + +/* #define NVSC_MIN_PROTOCOL_VERSION 1 */ +/* #define NVSC_MAX_PROTOCOL_VERSION 1 */ + +#define NETVSC_SEND_BUFFER_SIZE (64*1024) /* 64K */ +#define NETVSC_SEND_BUFFER_ID 0xface + + +#define NETVSC_RECEIVE_BUFFER_SIZE (1024*1024) /* 1MB */ + +#define NETVSC_RECEIVE_BUFFER_ID 0xcafe + +#define NETVSC_RECEIVE_SG_COUNT 1 + +/* Preallocated receive packets */ +#define NETVSC_RECEIVE_PACKETLIST_COUNT 256 + + +/* Per netvsc channel-specific */ +struct netvsc_device { + struct hv_device *Device; + + atomic_t RefCount; + atomic_t NumOutstandingSends; + /* + * List of free preallocated hv_netvsc_packet to represent receive + * packet + */ + struct list_head ReceivePacketList; + spinlock_t receive_packet_list_lock; + + /* Send buffer allocated by us but manages by NetVSP */ + void *SendBuffer; + u32 SendBufferSize; + u32 SendBufferGpadlHandle; + u32 SendSectionSize; + + /* Receive buffer allocated by us but manages by NetVSP */ + void *ReceiveBuffer; + u32 ReceiveBufferSize; + u32 ReceiveBufferGpadlHandle; + u32 ReceiveSectionCount; + struct nvsp_1_receive_buffer_section *ReceiveSections; + + /* Used for NetVSP initialization protocol */ + struct osd_waitevent *ChannelInitEvent; + struct nvsp_message ChannelInitPacket; + + struct nvsp_message RevokePacket; + /* unsigned char HwMacAddr[HW_MACADDR_LEN]; */ + + /* Holds rndis device info */ + void *Extension; +}; + +#endif /* _NETVSC_H_ */ diff --git a/drivers/staging/hv/NetVscApi.h b/drivers/staging/hv/NetVscApi.h new file mode 100644 index 00000000000..1ce2b74a34a --- /dev/null +++ b/drivers/staging/hv/NetVscApi.h @@ -0,0 +1,123 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _NETVSC_API_H_ +#define _NETVSC_API_H_ + +#include "VmbusApi.h" + +/* Defines */ +#define NETVSC_DEVICE_RING_BUFFER_SIZE (64*PAGE_SIZE) +#define HW_MACADDR_LEN 6 + +/* Fwd declaration */ +struct hv_netvsc_packet; + +/* Represent the xfer page packet which contains 1 or more netvsc packet */ +struct xferpage_packet { + struct list_head ListEntry; + + /* # of netvsc packets this xfer packet contains */ + u32 Count; +}; + +/* The number of pages which are enough to cover jumbo frame buffer. */ +#define NETVSC_PACKET_MAXPAGE 4 + +/* + * Represent netvsc packet which contains 1 RNDIS and 1 ethernet frame + * within the RNDIS + */ +struct hv_netvsc_packet { + /* Bookkeeping stuff */ + struct list_head ListEntry; + + struct hv_device *Device; + bool IsDataPacket; + + /* + * Valid only for receives when we break a xfer page packet + * into multiple netvsc packets + */ + struct xferpage_packet *XferPagePacket; + + union { + struct{ + u64 ReceiveCompletionTid; + void *ReceiveCompletionContext; + void (*OnReceiveCompletion)(void *context); + } Recv; + struct{ + u64 SendCompletionTid; + void *SendCompletionContext; + void (*OnSendCompletion)(void *context); + } Send; + } Completion; + + /* This points to the memory after PageBuffers */ + void *Extension; + + u32 TotalDataBufferLength; + /* Points to the send/receive buffer where the ethernet frame is */ + u32 PageBufferCount; + struct hv_page_buffer PageBuffers[NETVSC_PACKET_MAXPAGE]; +}; + +/* Represents the net vsc driver */ +struct netvsc_driver { + /* Must be the first field */ + /* Which is a bug FIXME! */ + struct hv_driver Base; + + u32 RingBufferSize; + u32 RequestExtSize; + + /* Additional num of page buffers to allocate */ + u32 AdditionalRequestPageBufferCount; + + /* + * This is set by the caller to allow us to callback when we + * receive a packet from the "wire" + */ + int (*OnReceiveCallback)(struct hv_device *dev, + struct hv_netvsc_packet *packet); + void (*OnLinkStatusChanged)(struct hv_device *dev, u32 Status); + + /* Specific to this driver */ + int (*OnOpen)(struct hv_device *dev); + int (*OnClose)(struct hv_device *dev); + int (*OnSend)(struct hv_device *dev, struct hv_netvsc_packet *packet); + + void *Context; +}; + +struct netvsc_device_info { + unsigned char MacAddr[6]; + bool LinkState; /* 0 - link up, 1 - link down */ +}; + +/* Interface */ +int NetVscInitialize(struct hv_driver *drv); + +#endif /* _NETVSC_API_H_ */ diff --git a/drivers/staging/hv/RingBuffer.c b/drivers/staging/hv/RingBuffer.c new file mode 100644 index 00000000000..f69ae33a91e --- /dev/null +++ b/drivers/staging/hv/RingBuffer.c @@ -0,0 +1,606 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#include <linux/kernel.h> +#include <linux/mm.h> +#include "osd.h" +#include "logging.h" +#include "RingBuffer.h" + + +/* #defines */ + + +/* Amount of space to write to */ +#define BYTES_AVAIL_TO_WRITE(r, w, z) ((w) >= (r))?((z) - ((w) - (r))):((r) - (w)) + + +/*++ + +Name: + GetRingBufferAvailBytes() + +Description: + Get number of bytes available to read and to write to + for the specified ring buffer + +--*/ +static inline void +GetRingBufferAvailBytes(RING_BUFFER_INFO *rbi, u32 *read, u32 *write) +{ + u32 read_loc,write_loc; + + /* Capture the read/write indices before they changed */ + read_loc = rbi->RingBuffer->ReadIndex; + write_loc = rbi->RingBuffer->WriteIndex; + + *write = BYTES_AVAIL_TO_WRITE(read_loc, write_loc, rbi->RingDataSize); + *read = rbi->RingDataSize - *write; +} + +/*++ + +Name: + GetNextWriteLocation() + +Description: + Get the next write location for the specified ring buffer + +--*/ +static inline u32 +GetNextWriteLocation(RING_BUFFER_INFO* RingInfo) +{ + u32 next = RingInfo->RingBuffer->WriteIndex; + + ASSERT(next < RingInfo->RingDataSize); + + return next; +} + +/*++ + +Name: + SetNextWriteLocation() + +Description: + Set the next write location for the specified ring buffer + +--*/ +static inline void +SetNextWriteLocation(RING_BUFFER_INFO* RingInfo, u32 NextWriteLocation) +{ + RingInfo->RingBuffer->WriteIndex = NextWriteLocation; +} + +/*++ + +Name: + GetNextReadLocation() + +Description: + Get the next read location for the specified ring buffer + +--*/ +static inline u32 +GetNextReadLocation(RING_BUFFER_INFO* RingInfo) +{ + u32 next = RingInfo->RingBuffer->ReadIndex; + + ASSERT(next < RingInfo->RingDataSize); + + return next; +} + +/*++ + +Name: + GetNextReadLocationWithOffset() + +Description: + Get the next read location + offset for the specified ring buffer. + This allows the caller to skip + +--*/ +static inline u32 +GetNextReadLocationWithOffset(RING_BUFFER_INFO* RingInfo, u32 Offset) +{ + u32 next = RingInfo->RingBuffer->ReadIndex; + + ASSERT(next < RingInfo->RingDataSize); + next += Offset; + next %= RingInfo->RingDataSize; + + return next; +} + +/*++ + +Name: + SetNextReadLocation() + +Description: + Set the next read location for the specified ring buffer + +--*/ +static inline void +SetNextReadLocation(RING_BUFFER_INFO* RingInfo, u32 NextReadLocation) +{ + RingInfo->RingBuffer->ReadIndex = NextReadLocation; +} + + +/*++ + +Name: + GetRingBuffer() + +Description: + Get the start of the ring buffer + +--*/ +static inline void * +GetRingBuffer(RING_BUFFER_INFO* RingInfo) +{ + return (void *)RingInfo->RingBuffer->Buffer; +} + + +/*++ + +Name: + GetRingBufferSize() + +Description: + Get the size of the ring buffer + +--*/ +static inline u32 +GetRingBufferSize(RING_BUFFER_INFO* RingInfo) +{ + return RingInfo->RingDataSize; +} + +/*++ + +Name: + GetRingBufferIndices() + +Description: + Get the read and write indices as u64 of the specified ring buffer + +--*/ +static inline u64 +GetRingBufferIndices(RING_BUFFER_INFO* RingInfo) +{ + return ((u64)RingInfo->RingBuffer->WriteIndex << 32) || RingInfo->RingBuffer->ReadIndex; +} + + +/*++ + +Name: + DumpRingInfo() + +Description: + Dump out to console the ring buffer info + +--*/ +void DumpRingInfo(RING_BUFFER_INFO *RingInfo, char *Prefix) +{ + u32 bytesAvailToWrite; + u32 bytesAvailToRead; + + GetRingBufferAvailBytes(RingInfo, &bytesAvailToRead, &bytesAvailToWrite); + + DPRINT(VMBUS, DEBUG_RING_LVL, "%s <<ringinfo %p buffer %p avail write %u avail read %u read idx %u write idx %u>>", + Prefix, + RingInfo, + RingInfo->RingBuffer->Buffer, + bytesAvailToWrite, + bytesAvailToRead, + RingInfo->RingBuffer->ReadIndex, + RingInfo->RingBuffer->WriteIndex); +} + + +/* Internal routines */ + +static u32 +CopyToRingBuffer( + RING_BUFFER_INFO *RingInfo, + u32 StartWriteOffset, + void * Src, + u32 SrcLen); + +static u32 +CopyFromRingBuffer( + RING_BUFFER_INFO *RingInfo, + void * Dest, + u32 DestLen, + u32 StartReadOffset); + + + +/*++ + +Name: + RingBufferGetDebugInfo() + +Description: + Get various debug metrics for the specified ring buffer + +--*/ +void RingBufferGetDebugInfo(RING_BUFFER_INFO *RingInfo, + RING_BUFFER_DEBUG_INFO *DebugInfo) +{ + u32 bytesAvailToWrite; + u32 bytesAvailToRead; + + if (RingInfo->RingBuffer) + { + GetRingBufferAvailBytes(RingInfo, &bytesAvailToRead, &bytesAvailToWrite); + + DebugInfo->BytesAvailToRead = bytesAvailToRead; + DebugInfo->BytesAvailToWrite = bytesAvailToWrite; + DebugInfo->CurrentReadIndex = RingInfo->RingBuffer->ReadIndex; + DebugInfo->CurrentWriteIndex = RingInfo->RingBuffer->WriteIndex; + + DebugInfo->CurrentInterruptMask = RingInfo->RingBuffer->InterruptMask; + } +} + + +/*++ + +Name: + GetRingBufferInterruptMask() + +Description: + Get the interrupt mask for the specified ring buffer + +--*/ +u32 GetRingBufferInterruptMask(RING_BUFFER_INFO *rbi) +{ + return rbi->RingBuffer->InterruptMask; +} + +/*++ + +Name: + RingBufferInit() + +Description: + Initialize the ring buffer + +--*/ +int RingBufferInit(RING_BUFFER_INFO *RingInfo, void *Buffer, u32 BufferLen) +{ + ASSERT(sizeof(RING_BUFFER) == PAGE_SIZE); + + memset(RingInfo, 0, sizeof(RING_BUFFER_INFO)); + + RingInfo->RingBuffer = (RING_BUFFER*)Buffer; + RingInfo->RingBuffer->ReadIndex = RingInfo->RingBuffer->WriteIndex = 0; + + RingInfo->RingSize = BufferLen; + RingInfo->RingDataSize = BufferLen - sizeof(RING_BUFFER); + + spin_lock_init(&RingInfo->ring_lock); + + return 0; +} + +/*++ + +Name: + RingBufferCleanup() + +Description: + Cleanup the ring buffer + +--*/ +void RingBufferCleanup(RING_BUFFER_INFO* RingInfo) +{ +} + +/*++ + +Name: + RingBufferWrite() + +Description: + Write to the ring buffer + +--*/ +int RingBufferWrite(RING_BUFFER_INFO *OutRingInfo, + struct scatterlist *sglist, u32 sgcount) +{ + int i=0; + u32 byteAvailToWrite; + u32 byteAvailToRead; + u32 totalBytesToWrite=0; + + struct scatterlist *sg; + volatile u32 nextWriteLocation; + u64 prevIndices=0; + unsigned long flags; + + DPRINT_ENTER(VMBUS); + + for_each_sg(sglist, sg, sgcount, i) + { + totalBytesToWrite += sg->length; + } + + totalBytesToWrite += sizeof(u64); + + spin_lock_irqsave(&OutRingInfo->ring_lock, flags); + + GetRingBufferAvailBytes(OutRingInfo, &byteAvailToRead, &byteAvailToWrite); + + DPRINT_DBG(VMBUS, "Writing %u bytes...", totalBytesToWrite); + + /* DumpRingInfo(OutRingInfo, "BEFORE "); */ + + /* If there is only room for the packet, assume it is full. Otherwise, the next time around, we think the ring buffer */ + /* is empty since the read index == write index */ + if (byteAvailToWrite <= totalBytesToWrite) + { + DPRINT_DBG(VMBUS, "No more space left on outbound ring buffer (needed %u, avail %u)", totalBytesToWrite, byteAvailToWrite); + + spin_unlock_irqrestore(&OutRingInfo->ring_lock, flags); + + DPRINT_EXIT(VMBUS); + + return -1; + } + + /* Write to the ring buffer */ + nextWriteLocation = GetNextWriteLocation(OutRingInfo); + + for_each_sg(sglist, sg, sgcount, i) + { + nextWriteLocation = CopyToRingBuffer(OutRingInfo, + nextWriteLocation, + sg_virt(sg), + sg->length); + } + + /* Set previous packet start */ + prevIndices = GetRingBufferIndices(OutRingInfo); + + nextWriteLocation = CopyToRingBuffer(OutRingInfo, + nextWriteLocation, + &prevIndices, + sizeof(u64)); + + /* Make sure we flush all writes before updating the writeIndex */ + mb(); + + /* Now, update the write location */ + SetNextWriteLocation(OutRingInfo, nextWriteLocation); + + /* DumpRingInfo(OutRingInfo, "AFTER "); */ + + spin_unlock_irqrestore(&OutRingInfo->ring_lock, flags); + + DPRINT_EXIT(VMBUS); + + return 0; +} + + +/*++ + +Name: + RingBufferPeek() + +Description: + Read without advancing the read index + +--*/ +int RingBufferPeek(RING_BUFFER_INFO *InRingInfo, void *Buffer, u32 BufferLen) +{ + u32 bytesAvailToWrite; + u32 bytesAvailToRead; + u32 nextReadLocation=0; + unsigned long flags; + + spin_lock_irqsave(&InRingInfo->ring_lock, flags); + + GetRingBufferAvailBytes(InRingInfo, &bytesAvailToRead, &bytesAvailToWrite); + + /* Make sure there is something to read */ + if (bytesAvailToRead < BufferLen ) + { + /* DPRINT_DBG(VMBUS, "got callback but not enough to read <avail to read %d read size %d>!!", bytesAvailToRead, BufferLen); */ + + spin_unlock_irqrestore(&InRingInfo->ring_lock, flags); + + return -1; + } + + /* Convert to byte offset */ + nextReadLocation = GetNextReadLocation(InRingInfo); + + nextReadLocation = CopyFromRingBuffer(InRingInfo, + Buffer, + BufferLen, + nextReadLocation); + + spin_unlock_irqrestore(&InRingInfo->ring_lock, flags); + + return 0; +} + + +/*++ + +Name: + RingBufferRead() + +Description: + Read and advance the read index + +--*/ +int RingBufferRead(RING_BUFFER_INFO *InRingInfo, void *Buffer, + u32 BufferLen, u32 Offset) +{ + u32 bytesAvailToWrite; + u32 bytesAvailToRead; + u32 nextReadLocation=0; + u64 prevIndices=0; + unsigned long flags; + + ASSERT(BufferLen > 0); + + spin_lock_irqsave(&InRingInfo->ring_lock, flags); + + GetRingBufferAvailBytes(InRingInfo, &bytesAvailToRead, &bytesAvailToWrite); + + DPRINT_DBG(VMBUS, "Reading %u bytes...", BufferLen); + + /* DumpRingInfo(InRingInfo, "BEFORE "); */ + + /* Make sure there is something to read */ + if (bytesAvailToRead < BufferLen ) + { + DPRINT_DBG(VMBUS, "got callback but not enough to read <avail to read %d read size %d>!!", bytesAvailToRead, BufferLen); + + spin_unlock_irqrestore(&InRingInfo->ring_lock, flags); + + return -1; + } + + nextReadLocation = GetNextReadLocationWithOffset(InRingInfo, Offset); + + nextReadLocation = CopyFromRingBuffer(InRingInfo, + Buffer, + BufferLen, + nextReadLocation); + + nextReadLocation = CopyFromRingBuffer(InRingInfo, + &prevIndices, + sizeof(u64), + nextReadLocation); + + /* Make sure all reads are done before we update the read index since */ + /* the writer may start writing to the read area once the read index is updated */ + mb(); + + /* Update the read index */ + SetNextReadLocation(InRingInfo, nextReadLocation); + + /* DumpRingInfo(InRingInfo, "AFTER "); */ + + spin_unlock_irqrestore(&InRingInfo->ring_lock, flags); + + return 0; +} + + +/*++ + +Name: + CopyToRingBuffer() + +Description: + Helper routine to copy from source to ring buffer. + Assume there is enough room. Handles wrap-around in dest case only!! + +--*/ +static u32 +CopyToRingBuffer( + RING_BUFFER_INFO *RingInfo, + u32 StartWriteOffset, + void * Src, + u32 SrcLen) +{ + void * ringBuffer=GetRingBuffer(RingInfo); + u32 ringBufferSize=GetRingBufferSize(RingInfo); + u32 fragLen; + + if (SrcLen > ringBufferSize - StartWriteOffset) /* wrap-around detected! */ + { + DPRINT_DBG(VMBUS, "wrap-around detected!"); + + fragLen = ringBufferSize - StartWriteOffset; + memcpy(ringBuffer + StartWriteOffset, Src, fragLen); + memcpy(ringBuffer, Src + fragLen, SrcLen - fragLen); + } + else + { + memcpy(ringBuffer + StartWriteOffset, Src, SrcLen); + } + + StartWriteOffset += SrcLen; + StartWriteOffset %= ringBufferSize; + + return StartWriteOffset; +} + + +/*++ + +Name: + CopyFromRingBuffer() + +Description: + Helper routine to copy to source from ring buffer. + Assume there is enough room. Handles wrap-around in src case only!! + +--*/ +static u32 +CopyFromRingBuffer( + RING_BUFFER_INFO *RingInfo, + void * Dest, + u32 DestLen, + u32 StartReadOffset) +{ + void * ringBuffer=GetRingBuffer(RingInfo); + u32 ringBufferSize=GetRingBufferSize(RingInfo); + + u32 fragLen; + + if (DestLen > ringBufferSize - StartReadOffset) /* wrap-around detected at the src */ + { + DPRINT_DBG(VMBUS, "src wrap-around detected!"); + + fragLen = ringBufferSize - StartReadOffset; + + memcpy(Dest, ringBuffer + StartReadOffset, fragLen); + memcpy(Dest + fragLen, ringBuffer, DestLen - fragLen); + } + else + { + memcpy(Dest, ringBuffer + StartReadOffset, DestLen); + } + + StartReadOffset += DestLen; + StartReadOffset %= ringBufferSize; + + return StartReadOffset; +} + + +/* eof */ diff --git a/drivers/staging/hv/RingBuffer.h b/drivers/staging/hv/RingBuffer.h new file mode 100644 index 00000000000..6202157e145 --- /dev/null +++ b/drivers/staging/hv/RingBuffer.h @@ -0,0 +1,101 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _RING_BUFFER_H_ +#define _RING_BUFFER_H_ + +#include <linux/scatterlist.h> + +typedef struct _RING_BUFFER { + /* Offset in bytes from the start of ring data below */ + volatile u32 WriteIndex; + + /* Offset in bytes from the start of ring data below */ + volatile u32 ReadIndex; + + volatile u32 InterruptMask; + + /* Pad it to PAGE_SIZE so that data starts on page boundary */ + u8 Reserved[4084]; + + /* NOTE: + * The InterruptMask field is used only for channels but since our + * vmbus connection also uses this data structure and its data starts + * here, we commented out this field. + */ + /* volatile u32 InterruptMask; */ + + /* + * Ring data starts here + RingDataStartOffset + * !!! DO NOT place any fields below this !!! + */ + u8 Buffer[0]; +} __attribute__((packed)) RING_BUFFER; + +typedef struct _RING_BUFFER_INFO { + RING_BUFFER *RingBuffer; + u32 RingSize; /* Include the shared header */ + spinlock_t ring_lock; + + u32 RingDataSize; /* < ringSize */ + u32 RingDataStartOffset; + +} RING_BUFFER_INFO; + +typedef struct _RING_BUFFER_DEBUG_INFO { + u32 CurrentInterruptMask; + u32 CurrentReadIndex; + u32 CurrentWriteIndex; + u32 BytesAvailToRead; + u32 BytesAvailToWrite; +} RING_BUFFER_DEBUG_INFO; + + + +/* Interface */ + + +int RingBufferInit(RING_BUFFER_INFO *RingInfo, void *Buffer, u32 BufferLen); + +void RingBufferCleanup(RING_BUFFER_INFO *RingInfo); + +int RingBufferWrite(RING_BUFFER_INFO *RingInfo, + struct scatterlist *sglist, + u32 sgcount); + +int RingBufferPeek(RING_BUFFER_INFO *RingInfo, void *Buffer, u32 BufferLen); + +int RingBufferRead(RING_BUFFER_INFO *RingInfo, + void *Buffer, + u32 BufferLen, + u32 Offset); + +u32 GetRingBufferInterruptMask(RING_BUFFER_INFO *RingInfo); + +void DumpRingInfo(RING_BUFFER_INFO *RingInfo, char *Prefix); + +void RingBufferGetDebugInfo(RING_BUFFER_INFO *RingInfo, + RING_BUFFER_DEBUG_INFO *DebugInfo); + +#endif /* _RING_BUFFER_H_ */ diff --git a/drivers/staging/hv/RndisFilter.c b/drivers/staging/hv/RndisFilter.c new file mode 100644 index 00000000000..26d79975387 --- /dev/null +++ b/drivers/staging/hv/RndisFilter.c @@ -0,0 +1,1000 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/kernel.h> +#include <linux/highmem.h> +#include <linux/io.h> +#include "osd.h" +#include "logging.h" +#include "NetVscApi.h" +#include "RndisFilter.h" + +/* Data types */ +struct rndis_filter_driver_object { + /* The original driver */ + struct netvsc_driver InnerDriver; +}; + +enum rndis_device_state { + RNDIS_DEV_UNINITIALIZED = 0, + RNDIS_DEV_INITIALIZING, + RNDIS_DEV_INITIALIZED, + RNDIS_DEV_DATAINITIALIZED, +}; + +struct rndis_device { + struct netvsc_device *NetDevice; + + enum rndis_device_state State; + u32 LinkStatus; + atomic_t NewRequestId; + + spinlock_t request_lock; + struct list_head RequestList; + + unsigned char HwMacAddr[HW_MACADDR_LEN]; +}; + +struct rndis_request { + struct list_head ListEntry; + struct osd_waitevent *WaitEvent; + + /* + * FIXME: We assumed a fixed size response here. If we do ever need to + * handle a bigger response, we can either define a max response + * message or add a response buffer variable above this field + */ + struct rndis_message ResponseMessage; + + /* Simplify allocation by having a netvsc packet inline */ + struct hv_netvsc_packet Packet; + struct hv_page_buffer Buffer; + /* FIXME: We assumed a fixed size request here. */ + struct rndis_message RequestMessage; +}; + + +struct rndis_filter_packet { + void *CompletionContext; + void (*OnCompletion)(void *context); + struct rndis_message Message; +}; + + +static int RndisFilterOnDeviceAdd(struct hv_device *Device, + void *AdditionalInfo); + +static int RndisFilterOnDeviceRemove(struct hv_device *Device); + +static void RndisFilterOnCleanup(struct hv_driver *Driver); + +static int RndisFilterOnOpen(struct hv_device *Device); + +static int RndisFilterOnClose(struct hv_device *Device); + +static int RndisFilterOnSend(struct hv_device *Device, + struct hv_netvsc_packet *Packet); + +static void RndisFilterOnSendCompletion(void *Context); + +static void RndisFilterOnSendRequestCompletion(void *Context); + + +/* The one and only */ +static struct rndis_filter_driver_object gRndisFilter; + +static struct rndis_device *GetRndisDevice(void) +{ + struct rndis_device *device; + + device = kzalloc(sizeof(struct rndis_device), GFP_KERNEL); + if (!device) + return NULL; + + spin_lock_init(&device->request_lock); + + INIT_LIST_HEAD(&device->RequestList); + + device->State = RNDIS_DEV_UNINITIALIZED; + + return device; +} + +static struct rndis_request *GetRndisRequest(struct rndis_device *Device, + u32 MessageType, + u32 MessageLength) +{ + struct rndis_request *request; + struct rndis_message *rndisMessage; + struct rndis_set_request *set; + unsigned long flags; + + request = kzalloc(sizeof(struct rndis_request), GFP_KERNEL); + if (!request) + return NULL; + + request->WaitEvent = osd_WaitEventCreate(); + if (!request->WaitEvent) { + kfree(request); + return NULL; + } + + rndisMessage = &request->RequestMessage; + rndisMessage->NdisMessageType = MessageType; + rndisMessage->MessageLength = MessageLength; + + /* + * Set the request id. This field is always after the rndis header for + * request/response packet types so we just used the SetRequest as a + * template + */ + set = &rndisMessage->Message.SetRequest; + set->RequestId = atomic_inc_return(&Device->NewRequestId); + + /* Add to the request list */ + spin_lock_irqsave(&Device->request_lock, flags); + list_add_tail(&request->ListEntry, &Device->RequestList); + spin_unlock_irqrestore(&Device->request_lock, flags); + + return request; +} + +static void PutRndisRequest(struct rndis_device *Device, + struct rndis_request *Request) +{ + unsigned long flags; + + spin_lock_irqsave(&Device->request_lock, flags); + list_del(&Request->ListEntry); + spin_unlock_irqrestore(&Device->request_lock, flags); + + kfree(Request->WaitEvent); + kfree(Request); +} + +static void DumpRndisMessage(struct rndis_message *RndisMessage) +{ + switch (RndisMessage->NdisMessageType) { + case REMOTE_NDIS_PACKET_MSG: + DPRINT_DBG(NETVSC, "REMOTE_NDIS_PACKET_MSG (len %u, " + "data offset %u data len %u, # oob %u, " + "oob offset %u, oob len %u, pkt offset %u, " + "pkt len %u", + RndisMessage->MessageLength, + RndisMessage->Message.Packet.DataOffset, + RndisMessage->Message.Packet.DataLength, + RndisMessage->Message.Packet.NumOOBDataElements, + RndisMessage->Message.Packet.OOBDataOffset, + RndisMessage->Message.Packet.OOBDataLength, + RndisMessage->Message.Packet.PerPacketInfoOffset, + RndisMessage->Message.Packet.PerPacketInfoLength); + break; + + case REMOTE_NDIS_INITIALIZE_CMPLT: + DPRINT_DBG(NETVSC, "REMOTE_NDIS_INITIALIZE_CMPLT " + "(len %u, id 0x%x, status 0x%x, major %d, minor %d, " + "device flags %d, max xfer size 0x%x, max pkts %u, " + "pkt aligned %u)", + RndisMessage->MessageLength, + RndisMessage->Message.InitializeComplete.RequestId, + RndisMessage->Message.InitializeComplete.Status, + RndisMessage->Message.InitializeComplete.MajorVersion, + RndisMessage->Message.InitializeComplete.MinorVersion, + RndisMessage->Message.InitializeComplete.DeviceFlags, + RndisMessage->Message.InitializeComplete.MaxTransferSize, + RndisMessage->Message.InitializeComplete.MaxPacketsPerMessage, + RndisMessage->Message.InitializeComplete.PacketAlignmentFactor); + break; + + case REMOTE_NDIS_QUERY_CMPLT: + DPRINT_DBG(NETVSC, "REMOTE_NDIS_QUERY_CMPLT " + "(len %u, id 0x%x, status 0x%x, buf len %u, " + "buf offset %u)", + RndisMessage->MessageLength, + RndisMessage->Message.QueryComplete.RequestId, + RndisMessage->Message.QueryComplete.Status, + RndisMessage->Message.QueryComplete.InformationBufferLength, + RndisMessage->Message.QueryComplete.InformationBufferOffset); + break; + + case REMOTE_NDIS_SET_CMPLT: + DPRINT_DBG(NETVSC, + "REMOTE_NDIS_SET_CMPLT (len %u, id 0x%x, status 0x%x)", + RndisMessage->MessageLength, + RndisMessage->Message.SetComplete.RequestId, + RndisMessage->Message.SetComplete.Status); + break; + + case REMOTE_NDIS_INDICATE_STATUS_MSG: + DPRINT_DBG(NETVSC, "REMOTE_NDIS_INDICATE_STATUS_MSG " + "(len %u, status 0x%x, buf len %u, buf offset %u)", + RndisMessage->MessageLength, + RndisMessage->Message.IndicateStatus.Status, + RndisMessage->Message.IndicateStatus.StatusBufferLength, + RndisMessage->Message.IndicateStatus.StatusBufferOffset); + break; + + default: + DPRINT_DBG(NETVSC, "0x%x (len %u)", + RndisMessage->NdisMessageType, + RndisMessage->MessageLength); + break; + } +} + +static int RndisFilterSendRequest(struct rndis_device *Device, + struct rndis_request *Request) +{ + int ret; + struct hv_netvsc_packet *packet; + + DPRINT_ENTER(NETVSC); + + /* Setup the packet to send it */ + packet = &Request->Packet; + + packet->IsDataPacket = false; + packet->TotalDataBufferLength = Request->RequestMessage.MessageLength; + packet->PageBufferCount = 1; + + packet->PageBuffers[0].Pfn = virt_to_phys(&Request->RequestMessage) >> + PAGE_SHIFT; + packet->PageBuffers[0].Length = Request->RequestMessage.MessageLength; + packet->PageBuffers[0].Offset = + (unsigned long)&Request->RequestMessage & (PAGE_SIZE - 1); + + packet->Completion.Send.SendCompletionContext = Request;/* packet; */ + packet->Completion.Send.OnSendCompletion = + RndisFilterOnSendRequestCompletion; + packet->Completion.Send.SendCompletionTid = (unsigned long)Device; + + ret = gRndisFilter.InnerDriver.OnSend(Device->NetDevice->Device, packet); + DPRINT_EXIT(NETVSC); + return ret; +} + +static void RndisFilterReceiveResponse(struct rndis_device *Device, + struct rndis_message *Response) +{ + struct rndis_request *request = NULL; + bool found = false; + unsigned long flags; + + DPRINT_ENTER(NETVSC); + + spin_lock_irqsave(&Device->request_lock, flags); + list_for_each_entry(request, &Device->RequestList, ListEntry) { + /* + * All request/response message contains RequestId as the 1st + * field + */ + if (request->RequestMessage.Message.InitializeRequest.RequestId + == Response->Message.InitializeComplete.RequestId) { + DPRINT_DBG(NETVSC, "found rndis request for " + "this response (id 0x%x req type 0x%x res " + "type 0x%x)", + request->RequestMessage.Message.InitializeRequest.RequestId, + request->RequestMessage.NdisMessageType, + Response->NdisMessageType); + + found = true; + break; + } + } + spin_unlock_irqrestore(&Device->request_lock, flags); + + if (found) { + if (Response->MessageLength <= sizeof(struct rndis_message)) { + memcpy(&request->ResponseMessage, Response, + Response->MessageLength); + } else { + DPRINT_ERR(NETVSC, "rndis response buffer overflow " + "detected (size %u max %zu)", + Response->MessageLength, + sizeof(struct rndis_filter_packet)); + + if (Response->NdisMessageType == + REMOTE_NDIS_RESET_CMPLT) { + /* does not have a request id field */ + request->ResponseMessage.Message.ResetComplete.Status = STATUS_BUFFER_OVERFLOW; + } else { + request->ResponseMessage.Message.InitializeComplete.Status = STATUS_BUFFER_OVERFLOW; + } + } + + osd_WaitEventSet(request->WaitEvent); + } else { + DPRINT_ERR(NETVSC, "no rndis request found for this response " + "(id 0x%x res type 0x%x)", + Response->Message.InitializeComplete.RequestId, + Response->NdisMessageType); + } + + DPRINT_EXIT(NETVSC); +} + +static void RndisFilterReceiveIndicateStatus(struct rndis_device *Device, + struct rndis_message *Response) +{ + struct rndis_indicate_status *indicate = + &Response->Message.IndicateStatus; + + if (indicate->Status == RNDIS_STATUS_MEDIA_CONNECT) { + gRndisFilter.InnerDriver.OnLinkStatusChanged(Device->NetDevice->Device, 1); + } else if (indicate->Status == RNDIS_STATUS_MEDIA_DISCONNECT) { + gRndisFilter.InnerDriver.OnLinkStatusChanged(Device->NetDevice->Device, 0); + } else { + /* + * TODO: + */ + } +} + +static void RndisFilterReceiveData(struct rndis_device *Device, + struct rndis_message *Message, + struct hv_netvsc_packet *Packet) +{ + struct rndis_packet *rndisPacket; + u32 dataOffset; + + DPRINT_ENTER(NETVSC); + + /* empty ethernet frame ?? */ + ASSERT(Packet->PageBuffers[0].Length > + RNDIS_MESSAGE_SIZE(struct rndis_packet)); + + rndisPacket = &Message->Message.Packet; + + /* + * FIXME: Handle multiple rndis pkt msgs that maybe enclosed in this + * netvsc packet (ie TotalDataBufferLength != MessageLength) + */ + + /* Remove the rndis header and pass it back up the stack */ + dataOffset = RNDIS_HEADER_SIZE + rndisPacket->DataOffset; + + Packet->TotalDataBufferLength -= dataOffset; + Packet->PageBuffers[0].Offset += dataOffset; + Packet->PageBuffers[0].Length -= dataOffset; + + Packet->IsDataPacket = true; + + gRndisFilter.InnerDriver.OnReceiveCallback(Device->NetDevice->Device, + Packet); + + DPRINT_EXIT(NETVSC); +} + +static int RndisFilterOnReceive(struct hv_device *Device, + struct hv_netvsc_packet *Packet) +{ + struct netvsc_device *netDevice = Device->Extension; + struct rndis_device *rndisDevice; + struct rndis_message rndisMessage; + struct rndis_message *rndisHeader; + + DPRINT_ENTER(NETVSC); + + ASSERT(netDevice); + /* Make sure the rndis device state is initialized */ + if (!netDevice->Extension) { + DPRINT_ERR(NETVSC, "got rndis message but no rndis device..." + "dropping this message!"); + DPRINT_EXIT(NETVSC); + return -1; + } + + rndisDevice = (struct rndis_device *)netDevice->Extension; + if (rndisDevice->State == RNDIS_DEV_UNINITIALIZED) { + DPRINT_ERR(NETVSC, "got rndis message but rndis device " + "uninitialized...dropping this message!"); + DPRINT_EXIT(NETVSC); + return -1; + } + + rndisHeader = (struct rndis_message *)kmap_atomic( + pfn_to_page(Packet->PageBuffers[0].Pfn), KM_IRQ0); + + rndisHeader = (void *)((unsigned long)rndisHeader + + Packet->PageBuffers[0].Offset); + + /* Make sure we got a valid rndis message */ + /* + * FIXME: There seems to be a bug in set completion msg where its + * MessageLength is 16 bytes but the ByteCount field in the xfer page + * range shows 52 bytes + * */ +#if 0 + if (Packet->TotalDataBufferLength != rndisHeader->MessageLength) { + kunmap_atomic(rndisHeader - Packet->PageBuffers[0].Offset, + KM_IRQ0); + + DPRINT_ERR(NETVSC, "invalid rndis message? (expected %u " + "bytes got %u)...dropping this message!", + rndisHeader->MessageLength, + Packet->TotalDataBufferLength); + DPRINT_EXIT(NETVSC); + return -1; + } +#endif + + if ((rndisHeader->NdisMessageType != REMOTE_NDIS_PACKET_MSG) && + (rndisHeader->MessageLength > sizeof(struct rndis_message))) { + DPRINT_ERR(NETVSC, "incoming rndis message buffer overflow " + "detected (got %u, max %zu)...marking it an error!", + rndisHeader->MessageLength, + sizeof(struct rndis_message)); + } + + memcpy(&rndisMessage, rndisHeader, + (rndisHeader->MessageLength > sizeof(struct rndis_message)) ? + sizeof(struct rndis_message) : + rndisHeader->MessageLength); + + kunmap_atomic(rndisHeader - Packet->PageBuffers[0].Offset, KM_IRQ0); + + DumpRndisMessage(&rndisMessage); + + switch (rndisMessage.NdisMessageType) { + case REMOTE_NDIS_PACKET_MSG: + /* data msg */ + RndisFilterReceiveData(rndisDevice, &rndisMessage, Packet); + break; + + case REMOTE_NDIS_INITIALIZE_CMPLT: + case REMOTE_NDIS_QUERY_CMPLT: + case REMOTE_NDIS_SET_CMPLT: + /* case REMOTE_NDIS_RESET_CMPLT: */ + /* case REMOTE_NDIS_KEEPALIVE_CMPLT: */ + /* completion msgs */ + RndisFilterReceiveResponse(rndisDevice, &rndisMessage); + break; + + case REMOTE_NDIS_INDICATE_STATUS_MSG: + /* notification msgs */ + RndisFilterReceiveIndicateStatus(rndisDevice, &rndisMessage); + break; + default: + DPRINT_ERR(NETVSC, "unhandled rndis message (type %u len %u)", + rndisMessage.NdisMessageType, + rndisMessage.MessageLength); + break; + } + + DPRINT_EXIT(NETVSC); + return 0; +} + +static int RndisFilterQueryDevice(struct rndis_device *Device, u32 Oid, + void *Result, u32 *ResultSize) +{ + struct rndis_request *request; + u32 inresultSize = *ResultSize; + struct rndis_query_request *query; + struct rndis_query_complete *queryComplete; + int ret = 0; + + DPRINT_ENTER(NETVSC); + + ASSERT(Result); + + *ResultSize = 0; + request = GetRndisRequest(Device, REMOTE_NDIS_QUERY_MSG, + RNDIS_MESSAGE_SIZE(struct rndis_query_request)); + if (!request) { + ret = -1; + goto Cleanup; + } + + /* Setup the rndis query */ + query = &request->RequestMessage.Message.QueryRequest; + query->Oid = Oid; + query->InformationBufferOffset = sizeof(struct rndis_query_request); + query->InformationBufferLength = 0; + query->DeviceVcHandle = 0; + + ret = RndisFilterSendRequest(Device, request); + if (ret != 0) + goto Cleanup; + + osd_WaitEventWait(request->WaitEvent); + + /* Copy the response back */ + queryComplete = &request->ResponseMessage.Message.QueryComplete; + + if (queryComplete->InformationBufferLength > inresultSize) { + ret = -1; + goto Cleanup; + } + + memcpy(Result, + (void *)((unsigned long)queryComplete + + queryComplete->InformationBufferOffset), + queryComplete->InformationBufferLength); + + *ResultSize = queryComplete->InformationBufferLength; + +Cleanup: + if (request) + PutRndisRequest(Device, request); + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int RndisFilterQueryDeviceMac(struct rndis_device *Device) +{ + u32 size = HW_MACADDR_LEN; + + return RndisFilterQueryDevice(Device, + RNDIS_OID_802_3_PERMANENT_ADDRESS, + Device->HwMacAddr, &size); +} + +static int RndisFilterQueryDeviceLinkStatus(struct rndis_device *Device) +{ + u32 size = sizeof(u32); + + return RndisFilterQueryDevice(Device, + RNDIS_OID_GEN_MEDIA_CONNECT_STATUS, + &Device->LinkStatus, &size); +} + +static int RndisFilterSetPacketFilter(struct rndis_device *Device, + u32 NewFilter) +{ + struct rndis_request *request; + struct rndis_set_request *set; + struct rndis_set_complete *setComplete; + u32 status; + int ret; + + DPRINT_ENTER(NETVSC); + + ASSERT(RNDIS_MESSAGE_SIZE(struct rndis_set_request) + sizeof(u32) <= + sizeof(struct rndis_message)); + + request = GetRndisRequest(Device, REMOTE_NDIS_SET_MSG, + RNDIS_MESSAGE_SIZE(struct rndis_set_request) + + sizeof(u32)); + if (!request) { + ret = -1; + goto Cleanup; + } + + /* Setup the rndis set */ + set = &request->RequestMessage.Message.SetRequest; + set->Oid = RNDIS_OID_GEN_CURRENT_PACKET_FILTER; + set->InformationBufferLength = sizeof(u32); + set->InformationBufferOffset = sizeof(struct rndis_set_request); + + memcpy((void *)(unsigned long)set + sizeof(struct rndis_set_request), + &NewFilter, sizeof(u32)); + + ret = RndisFilterSendRequest(Device, request); + if (ret != 0) + goto Cleanup; + + ret = osd_WaitEventWaitEx(request->WaitEvent, 2000/*2sec*/); + if (!ret) { + ret = -1; + DPRINT_ERR(NETVSC, "timeout before we got a set response..."); + /* + * We cant deallocate the request since we may still receive a + * send completion for it. + */ + goto Exit; + } else { + if (ret > 0) + ret = 0; + setComplete = &request->ResponseMessage.Message.SetComplete; + status = setComplete->Status; + } + +Cleanup: + if (request) + PutRndisRequest(Device, request); +Exit: + DPRINT_EXIT(NETVSC); + + return ret; +} + +int RndisFilterInit(struct netvsc_driver *Driver) +{ + DPRINT_ENTER(NETVSC); + + DPRINT_DBG(NETVSC, "sizeof(struct rndis_filter_packet) == %zd", + sizeof(struct rndis_filter_packet)); + + Driver->RequestExtSize = sizeof(struct rndis_filter_packet); + Driver->AdditionalRequestPageBufferCount = 1; /* For rndis header */ + + /* Driver->Context = rndisDriver; */ + + memset(&gRndisFilter, 0, sizeof(struct rndis_filter_driver_object)); + + /*rndisDriver->Driver = Driver; + + ASSERT(Driver->OnLinkStatusChanged); + rndisDriver->OnLinkStatusChanged = Driver->OnLinkStatusChanged;*/ + + /* Save the original dispatch handlers before we override it */ + gRndisFilter.InnerDriver.Base.OnDeviceAdd = Driver->Base.OnDeviceAdd; + gRndisFilter.InnerDriver.Base.OnDeviceRemove = + Driver->Base.OnDeviceRemove; + gRndisFilter.InnerDriver.Base.OnCleanup = Driver->Base.OnCleanup; + + ASSERT(Driver->OnSend); + ASSERT(Driver->OnReceiveCallback); + gRndisFilter.InnerDriver.OnSend = Driver->OnSend; + gRndisFilter.InnerDriver.OnReceiveCallback = Driver->OnReceiveCallback; + gRndisFilter.InnerDriver.OnLinkStatusChanged = + Driver->OnLinkStatusChanged; + + /* Override */ + Driver->Base.OnDeviceAdd = RndisFilterOnDeviceAdd; + Driver->Base.OnDeviceRemove = RndisFilterOnDeviceRemove; + Driver->Base.OnCleanup = RndisFilterOnCleanup; + Driver->OnSend = RndisFilterOnSend; + Driver->OnOpen = RndisFilterOnOpen; + Driver->OnClose = RndisFilterOnClose; + /* Driver->QueryLinkStatus = RndisFilterQueryDeviceLinkStatus; */ + Driver->OnReceiveCallback = RndisFilterOnReceive; + + DPRINT_EXIT(NETVSC); + + return 0; +} + +static int RndisFilterInitDevice(struct rndis_device *Device) +{ + struct rndis_request *request; + struct rndis_initialize_request *init; + struct rndis_initialize_complete *initComplete; + u32 status; + int ret; + + DPRINT_ENTER(NETVSC); + + request = GetRndisRequest(Device, REMOTE_NDIS_INITIALIZE_MSG, + RNDIS_MESSAGE_SIZE(struct rndis_initialize_request)); + if (!request) { + ret = -1; + goto Cleanup; + } + + /* Setup the rndis set */ + init = &request->RequestMessage.Message.InitializeRequest; + init->MajorVersion = RNDIS_MAJOR_VERSION; + init->MinorVersion = RNDIS_MINOR_VERSION; + /* FIXME: Use 1536 - rounded ethernet frame size */ + init->MaxTransferSize = 2048; + + Device->State = RNDIS_DEV_INITIALIZING; + + ret = RndisFilterSendRequest(Device, request); + if (ret != 0) { + Device->State = RNDIS_DEV_UNINITIALIZED; + goto Cleanup; + } + + osd_WaitEventWait(request->WaitEvent); + + initComplete = &request->ResponseMessage.Message.InitializeComplete; + status = initComplete->Status; + if (status == RNDIS_STATUS_SUCCESS) { + Device->State = RNDIS_DEV_INITIALIZED; + ret = 0; + } else { + Device->State = RNDIS_DEV_UNINITIALIZED; + ret = -1; + } + +Cleanup: + if (request) + PutRndisRequest(Device, request); + DPRINT_EXIT(NETVSC); + + return ret; +} + +static void RndisFilterHaltDevice(struct rndis_device *Device) +{ + struct rndis_request *request; + struct rndis_halt_request *halt; + + DPRINT_ENTER(NETVSC); + + /* Attempt to do a rndis device halt */ + request = GetRndisRequest(Device, REMOTE_NDIS_HALT_MSG, + RNDIS_MESSAGE_SIZE(struct rndis_halt_request)); + if (!request) + goto Cleanup; + + /* Setup the rndis set */ + halt = &request->RequestMessage.Message.HaltRequest; + halt->RequestId = atomic_inc_return(&Device->NewRequestId); + + /* Ignore return since this msg is optional. */ + RndisFilterSendRequest(Device, request); + + Device->State = RNDIS_DEV_UNINITIALIZED; + +Cleanup: + if (request) + PutRndisRequest(Device, request); + DPRINT_EXIT(NETVSC); + return; +} + +static int RndisFilterOpenDevice(struct rndis_device *Device) +{ + int ret; + + DPRINT_ENTER(NETVSC); + + if (Device->State != RNDIS_DEV_INITIALIZED) + return 0; + + ret = RndisFilterSetPacketFilter(Device, + NDIS_PACKET_TYPE_BROADCAST | + NDIS_PACKET_TYPE_DIRECTED); + if (ret == 0) + Device->State = RNDIS_DEV_DATAINITIALIZED; + + DPRINT_EXIT(NETVSC); + return ret; +} + +static int RndisFilterCloseDevice(struct rndis_device *Device) +{ + int ret; + + DPRINT_ENTER(NETVSC); + + if (Device->State != RNDIS_DEV_DATAINITIALIZED) + return 0; + + ret = RndisFilterSetPacketFilter(Device, 0); + if (ret == 0) + Device->State = RNDIS_DEV_INITIALIZED; + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int RndisFilterOnDeviceAdd(struct hv_device *Device, + void *AdditionalInfo) +{ + int ret; + struct netvsc_device *netDevice; + struct rndis_device *rndisDevice; + struct netvsc_device_info *deviceInfo = AdditionalInfo; + + DPRINT_ENTER(NETVSC); + + rndisDevice = GetRndisDevice(); + if (!rndisDevice) { + DPRINT_EXIT(NETVSC); + return -1; + } + + DPRINT_DBG(NETVSC, "rndis device object allocated - %p", rndisDevice); + + /* + * Let the inner driver handle this first to create the netvsc channel + * NOTE! Once the channel is created, we may get a receive callback + * (RndisFilterOnReceive()) before this call is completed + */ + ret = gRndisFilter.InnerDriver.Base.OnDeviceAdd(Device, AdditionalInfo); + if (ret != 0) { + kfree(rndisDevice); + DPRINT_EXIT(NETVSC); + return ret; + } + + + /* Initialize the rndis device */ + netDevice = Device->Extension; + ASSERT(netDevice); + ASSERT(netDevice->Device); + + netDevice->Extension = rndisDevice; + rndisDevice->NetDevice = netDevice; + + /* Send the rndis initialization message */ + ret = RndisFilterInitDevice(rndisDevice); + if (ret != 0) { + /* + * TODO: If rndis init failed, we will need to shut down the + * channel + */ + } + + /* Get the mac address */ + ret = RndisFilterQueryDeviceMac(rndisDevice); + if (ret != 0) { + /* + * TODO: shutdown rndis device and the channel + */ + } + + DPRINT_INFO(NETVSC, "Device 0x%p mac addr %02x%02x%02x%02x%02x%02x", + rndisDevice, + rndisDevice->HwMacAddr[0], + rndisDevice->HwMacAddr[1], + rndisDevice->HwMacAddr[2], + rndisDevice->HwMacAddr[3], + rndisDevice->HwMacAddr[4], + rndisDevice->HwMacAddr[5]); + + memcpy(deviceInfo->MacAddr, rndisDevice->HwMacAddr, HW_MACADDR_LEN); + + RndisFilterQueryDeviceLinkStatus(rndisDevice); + + deviceInfo->LinkState = rndisDevice->LinkStatus; + DPRINT_INFO(NETVSC, "Device 0x%p link state %s", rndisDevice, + ((deviceInfo->LinkState) ? ("down") : ("up"))); + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int RndisFilterOnDeviceRemove(struct hv_device *Device) +{ + struct netvsc_device *netDevice = Device->Extension; + struct rndis_device *rndisDevice = netDevice->Extension; + + DPRINT_ENTER(NETVSC); + + /* Halt and release the rndis device */ + RndisFilterHaltDevice(rndisDevice); + + kfree(rndisDevice); + netDevice->Extension = NULL; + + /* Pass control to inner driver to remove the device */ + gRndisFilter.InnerDriver.Base.OnDeviceRemove(Device); + + DPRINT_EXIT(NETVSC); + + return 0; +} + +static void RndisFilterOnCleanup(struct hv_driver *Driver) +{ + DPRINT_ENTER(NETVSC); + + DPRINT_EXIT(NETVSC); +} + +static int RndisFilterOnOpen(struct hv_device *Device) +{ + int ret; + struct netvsc_device *netDevice = Device->Extension; + + DPRINT_ENTER(NETVSC); + + ASSERT(netDevice); + ret = RndisFilterOpenDevice(netDevice->Extension); + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int RndisFilterOnClose(struct hv_device *Device) +{ + int ret; + struct netvsc_device *netDevice = Device->Extension; + + DPRINT_ENTER(NETVSC); + + ASSERT(netDevice); + ret = RndisFilterCloseDevice(netDevice->Extension); + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static int RndisFilterOnSend(struct hv_device *Device, + struct hv_netvsc_packet *Packet) +{ + int ret; + struct rndis_filter_packet *filterPacket; + struct rndis_message *rndisMessage; + struct rndis_packet *rndisPacket; + u32 rndisMessageSize; + + DPRINT_ENTER(NETVSC); + + /* Add the rndis header */ + filterPacket = (struct rndis_filter_packet *)Packet->Extension; + ASSERT(filterPacket); + + memset(filterPacket, 0, sizeof(struct rndis_filter_packet)); + + rndisMessage = &filterPacket->Message; + rndisMessageSize = RNDIS_MESSAGE_SIZE(struct rndis_packet); + + rndisMessage->NdisMessageType = REMOTE_NDIS_PACKET_MSG; + rndisMessage->MessageLength = Packet->TotalDataBufferLength + + rndisMessageSize; + + rndisPacket = &rndisMessage->Message.Packet; + rndisPacket->DataOffset = sizeof(struct rndis_packet); + rndisPacket->DataLength = Packet->TotalDataBufferLength; + + Packet->IsDataPacket = true; + Packet->PageBuffers[0].Pfn = virt_to_phys(rndisMessage) >> PAGE_SHIFT; + Packet->PageBuffers[0].Offset = + (unsigned long)rndisMessage & (PAGE_SIZE-1); + Packet->PageBuffers[0].Length = rndisMessageSize; + + /* Save the packet send completion and context */ + filterPacket->OnCompletion = Packet->Completion.Send.OnSendCompletion; + filterPacket->CompletionContext = + Packet->Completion.Send.SendCompletionContext; + + /* Use ours */ + Packet->Completion.Send.OnSendCompletion = RndisFilterOnSendCompletion; + Packet->Completion.Send.SendCompletionContext = filterPacket; + + ret = gRndisFilter.InnerDriver.OnSend(Device, Packet); + if (ret != 0) { + /* + * Reset the completion to originals to allow retries from + * above + */ + Packet->Completion.Send.OnSendCompletion = + filterPacket->OnCompletion; + Packet->Completion.Send.SendCompletionContext = + filterPacket->CompletionContext; + } + + DPRINT_EXIT(NETVSC); + + return ret; +} + +static void RndisFilterOnSendCompletion(void *Context) +{ + struct rndis_filter_packet *filterPacket = Context; + + DPRINT_ENTER(NETVSC); + + /* Pass it back to the original handler */ + filterPacket->OnCompletion(filterPacket->CompletionContext); + + DPRINT_EXIT(NETVSC); +} + + +static void RndisFilterOnSendRequestCompletion(void *Context) +{ + DPRINT_ENTER(NETVSC); + + /* Noop */ + DPRINT_EXIT(NETVSC); +} diff --git a/drivers/staging/hv/RndisFilter.h b/drivers/staging/hv/RndisFilter.h new file mode 100644 index 00000000000..fa7dd79ddeb --- /dev/null +++ b/drivers/staging/hv/RndisFilter.h @@ -0,0 +1,55 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _RNDISFILTER_H_ +#define _RNDISFILTER_H_ + +#define __struct_bcount(x) + +#include "NetVsc.h" + +#include "rndis.h" + +#define RNDIS_HEADER_SIZE (sizeof(struct rndis_message) - \ + sizeof(union rndis_message_container)) + +#define NDIS_PACKET_TYPE_DIRECTED 0x00000001 +#define NDIS_PACKET_TYPE_MULTICAST 0x00000002 +#define NDIS_PACKET_TYPE_ALL_MULTICAST 0x00000004 +#define NDIS_PACKET_TYPE_BROADCAST 0x00000008 +#define NDIS_PACKET_TYPE_SOURCE_ROUTING 0x00000010 +#define NDIS_PACKET_TYPE_PROMISCUOUS 0x00000020 +#define NDIS_PACKET_TYPE_SMT 0x00000040 +#define NDIS_PACKET_TYPE_ALL_LOCAL 0x00000080 +#define NDIS_PACKET_TYPE_GROUP 0x00000100 +#define NDIS_PACKET_TYPE_ALL_FUNCTIONAL 0x00000200 +#define NDIS_PACKET_TYPE_FUNCTIONAL 0x00000400 +#define NDIS_PACKET_TYPE_MAC_FRAME 0x00000800 + + +/* Interface */ + +extern int RndisFilterInit(struct netvsc_driver *driver); + +#endif /* _RNDISFILTER_H_ */ diff --git a/drivers/staging/hv/StorVsc.c b/drivers/staging/hv/StorVsc.c new file mode 100644 index 00000000000..14015c92794 --- /dev/null +++ b/drivers/staging/hv/StorVsc.c @@ -0,0 +1,850 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include "osd.h" +#include "logging.h" +#include "StorVscApi.h" +#include "VmbusPacketFormat.h" +#include "vstorage.h" + + +struct storvsc_request_extension { + /* LIST_ENTRY ListEntry; */ + + struct hv_storvsc_request *Request; + struct hv_device *Device; + + /* Synchronize the request/response if needed */ + struct osd_waitevent *WaitEvent; + + struct vstor_packet VStorPacket; +}; + +/* A storvsc device is a device object that contains a vmbus channel */ +struct storvsc_device { + struct hv_device *Device; + + /* 0 indicates the device is being destroyed */ + atomic_t RefCount; + + atomic_t NumOutstandingRequests; + + /* + * Each unique Port/Path/Target represents 1 channel ie scsi + * controller. In reality, the pathid, targetid is always 0 + * and the port is set by us + */ + unsigned int PortNumber; + unsigned char PathId; + unsigned char TargetId; + + /* LIST_ENTRY OutstandingRequestList; */ + /* HANDLE OutstandingRequestLock; */ + + /* Used for vsc/vsp channel reset process */ + struct storvsc_request_extension InitRequest; + struct storvsc_request_extension ResetRequest; +}; + + +static const char *gDriverName = "storvsc"; + +/* {ba6163d9-04a1-4d29-b605-72e2ffb1dc7f} */ +static const struct hv_guid gStorVscDeviceType = { + .data = { + 0xd9, 0x63, 0x61, 0xba, 0xa1, 0x04, 0x29, 0x4d, + 0xb6, 0x05, 0x72, 0xe2, 0xff, 0xb1, 0xdc, 0x7f + } +}; + + +static inline struct storvsc_device *AllocStorDevice(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = kzalloc(sizeof(struct storvsc_device), GFP_KERNEL); + if (!storDevice) + return NULL; + + /* Set to 2 to allow both inbound and outbound traffics */ + /* (ie GetStorDevice() and MustGetStorDevice()) to proceed. */ + atomic_cmpxchg(&storDevice->RefCount, 0, 2); + + storDevice->Device = Device; + Device->Extension = storDevice; + + return storDevice; +} + +static inline void FreeStorDevice(struct storvsc_device *Device) +{ + ASSERT(atomic_read(&Device->RefCount) == 0); + kfree(Device); +} + +/* Get the stordevice object iff exists and its refcount > 1 */ +static inline struct storvsc_device *GetStorDevice(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = (struct storvsc_device *)Device->Extension; + if (storDevice && atomic_read(&storDevice->RefCount) > 1) + atomic_inc(&storDevice->RefCount); + else + storDevice = NULL; + + return storDevice; +} + +/* Get the stordevice object iff exists and its refcount > 0 */ +static inline struct storvsc_device *MustGetStorDevice(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = (struct storvsc_device *)Device->Extension; + if (storDevice && atomic_read(&storDevice->RefCount)) + atomic_inc(&storDevice->RefCount); + else + storDevice = NULL; + + return storDevice; +} + +static inline void PutStorDevice(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = (struct storvsc_device *)Device->Extension; + ASSERT(storDevice); + + atomic_dec(&storDevice->RefCount); + ASSERT(atomic_read(&storDevice->RefCount)); +} + +/* Drop ref count to 1 to effectively disable GetStorDevice() */ +static inline struct storvsc_device *ReleaseStorDevice(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = (struct storvsc_device *)Device->Extension; + ASSERT(storDevice); + + /* Busy wait until the ref drop to 2, then set it to 1 */ + while (atomic_cmpxchg(&storDevice->RefCount, 2, 1) != 2) + udelay(100); + + return storDevice; +} + +/* Drop ref count to 0. No one can use StorDevice object. */ +static inline struct storvsc_device *FinalReleaseStorDevice( + struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + storDevice = (struct storvsc_device *)Device->Extension; + ASSERT(storDevice); + + /* Busy wait until the ref drop to 1, then set it to 0 */ + while (atomic_cmpxchg(&storDevice->RefCount, 1, 0) != 1) + udelay(100); + + Device->Extension = NULL; + return storDevice; +} + +static int StorVscChannelInit(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + struct storvsc_request_extension *request; + struct vstor_packet *vstorPacket; + int ret; + + storDevice = GetStorDevice(Device); + if (!storDevice) { + DPRINT_ERR(STORVSC, "unable to get stor device..." + "device being destroyed?"); + DPRINT_EXIT(STORVSC); + return -1; + } + + request = &storDevice->InitRequest; + vstorPacket = &request->VStorPacket; + + /* + * Now, initiate the vsc/vsp initialization protocol on the open + * channel + */ + memset(request, sizeof(struct storvsc_request_extension), 0); + request->WaitEvent = osd_WaitEventCreate(); + + vstorPacket->Operation = VStorOperationBeginInitialization; + vstorPacket->Flags = REQUEST_COMPLETION_FLAG; + + /*SpinlockAcquire(gDriverExt.packetListLock); + INSERT_TAIL_LIST(&gDriverExt.packetList, &packet->listEntry.entry); + SpinlockRelease(gDriverExt.packetListLock);*/ + + DPRINT_INFO(STORVSC, "BEGIN_INITIALIZATION_OPERATION..."); + + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)request, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + DPRINT_ERR(STORVSC, + "unable to send BEGIN_INITIALIZATION_OPERATION"); + goto Cleanup; + } + + osd_WaitEventWait(request->WaitEvent); + + if (vstorPacket->Operation != VStorOperationCompleteIo || + vstorPacket->Status != 0) { + DPRINT_ERR(STORVSC, "BEGIN_INITIALIZATION_OPERATION failed " + "(op %d status 0x%x)", + vstorPacket->Operation, vstorPacket->Status); + goto Cleanup; + } + + DPRINT_INFO(STORVSC, "QUERY_PROTOCOL_VERSION_OPERATION..."); + + /* reuse the packet for version range supported */ + memset(vstorPacket, sizeof(struct vstor_packet), 0); + vstorPacket->Operation = VStorOperationQueryProtocolVersion; + vstorPacket->Flags = REQUEST_COMPLETION_FLAG; + + vstorPacket->Version.MajorMinor = VMSTOR_PROTOCOL_VERSION_CURRENT; + FILL_VMSTOR_REVISION(vstorPacket->Version.Revision); + + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)request, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + DPRINT_ERR(STORVSC, + "unable to send BEGIN_INITIALIZATION_OPERATION"); + goto Cleanup; + } + + osd_WaitEventWait(request->WaitEvent); + + /* TODO: Check returned version */ + if (vstorPacket->Operation != VStorOperationCompleteIo || + vstorPacket->Status != 0) { + DPRINT_ERR(STORVSC, "QUERY_PROTOCOL_VERSION_OPERATION failed " + "(op %d status 0x%x)", + vstorPacket->Operation, vstorPacket->Status); + goto Cleanup; + } + + /* Query channel properties */ + DPRINT_INFO(STORVSC, "QUERY_PROPERTIES_OPERATION..."); + + memset(vstorPacket, sizeof(struct vstor_packet), 0); + vstorPacket->Operation = VStorOperationQueryProperties; + vstorPacket->Flags = REQUEST_COMPLETION_FLAG; + vstorPacket->StorageChannelProperties.PortNumber = + storDevice->PortNumber; + + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)request, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + if (ret != 0) { + DPRINT_ERR(STORVSC, + "unable to send QUERY_PROPERTIES_OPERATION"); + goto Cleanup; + } + + osd_WaitEventWait(request->WaitEvent); + + /* TODO: Check returned version */ + if (vstorPacket->Operation != VStorOperationCompleteIo || + vstorPacket->Status != 0) { + DPRINT_ERR(STORVSC, "QUERY_PROPERTIES_OPERATION failed " + "(op %d status 0x%x)", + vstorPacket->Operation, vstorPacket->Status); + goto Cleanup; + } + + storDevice->PathId = vstorPacket->StorageChannelProperties.PathId; + storDevice->TargetId = vstorPacket->StorageChannelProperties.TargetId; + + DPRINT_DBG(STORVSC, "channel flag 0x%x, max xfer len 0x%x", + vstorPacket->StorageChannelProperties.Flags, + vstorPacket->StorageChannelProperties.MaxTransferBytes); + + DPRINT_INFO(STORVSC, "END_INITIALIZATION_OPERATION..."); + + memset(vstorPacket, sizeof(struct vstor_packet), 0); + vstorPacket->Operation = VStorOperationEndInitialization; + vstorPacket->Flags = REQUEST_COMPLETION_FLAG; + + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)request, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + + if (ret != 0) { + DPRINT_ERR(STORVSC, + "unable to send END_INITIALIZATION_OPERATION"); + goto Cleanup; + } + + osd_WaitEventWait(request->WaitEvent); + + if (vstorPacket->Operation != VStorOperationCompleteIo || + vstorPacket->Status != 0) { + DPRINT_ERR(STORVSC, "END_INITIALIZATION_OPERATION failed " + "(op %d status 0x%x)", + vstorPacket->Operation, vstorPacket->Status); + goto Cleanup; + } + + DPRINT_INFO(STORVSC, "**** storage channel up and running!! ****"); + +Cleanup: + kfree(request->WaitEvent); + request->WaitEvent = NULL; + + PutStorDevice(Device); + + DPRINT_EXIT(STORVSC); + return ret; +} + +static void StorVscOnIOCompletion(struct hv_device *Device, + struct vstor_packet *VStorPacket, + struct storvsc_request_extension *RequestExt) +{ + struct hv_storvsc_request *request; + struct storvsc_device *storDevice; + + DPRINT_ENTER(STORVSC); + + storDevice = MustGetStorDevice(Device); + if (!storDevice) { + DPRINT_ERR(STORVSC, "unable to get stor device..." + "device being destroyed?"); + DPRINT_EXIT(STORVSC); + return; + } + + DPRINT_DBG(STORVSC, "IO_COMPLETE_OPERATION - request extension %p " + "completed bytes xfer %u", RequestExt, + VStorPacket->VmSrb.DataTransferLength); + + ASSERT(RequestExt != NULL); + ASSERT(RequestExt->Request != NULL); + + request = RequestExt->Request; + + ASSERT(request->OnIOCompletion != NULL); + + /* Copy over the status...etc */ + request->Status = VStorPacket->VmSrb.ScsiStatus; + + if (request->Status != 0 || VStorPacket->VmSrb.SrbStatus != 1) { + DPRINT_WARN(STORVSC, + "cmd 0x%x scsi status 0x%x srb status 0x%x\n", + request->Cdb[0], VStorPacket->VmSrb.ScsiStatus, + VStorPacket->VmSrb.SrbStatus); + } + + if ((request->Status & 0xFF) == 0x02) { + /* CHECK_CONDITION */ + if (VStorPacket->VmSrb.SrbStatus & 0x80) { + /* autosense data available */ + DPRINT_WARN(STORVSC, "storvsc pkt %p autosense data " + "valid - len %d\n", RequestExt, + VStorPacket->VmSrb.SenseInfoLength); + + ASSERT(VStorPacket->VmSrb.SenseInfoLength <= + request->SenseBufferSize); + memcpy(request->SenseBuffer, + VStorPacket->VmSrb.SenseData, + VStorPacket->VmSrb.SenseInfoLength); + + request->SenseBufferSize = + VStorPacket->VmSrb.SenseInfoLength; + } + } + + /* TODO: */ + request->BytesXfer = VStorPacket->VmSrb.DataTransferLength; + + request->OnIOCompletion(request); + + atomic_dec(&storDevice->NumOutstandingRequests); + + PutStorDevice(Device); + + DPRINT_EXIT(STORVSC); +} + +static void StorVscOnReceive(struct hv_device *Device, + struct vstor_packet *VStorPacket, + struct storvsc_request_extension *RequestExt) +{ + switch (VStorPacket->Operation) { + case VStorOperationCompleteIo: + DPRINT_DBG(STORVSC, "IO_COMPLETE_OPERATION"); + StorVscOnIOCompletion(Device, VStorPacket, RequestExt); + break; + case VStorOperationRemoveDevice: + DPRINT_INFO(STORVSC, "REMOVE_DEVICE_OPERATION"); + /* TODO: */ + break; + + default: + DPRINT_INFO(STORVSC, "Unknown operation received - %d", + VStorPacket->Operation); + break; + } +} + +static void StorVscOnChannelCallback(void *context) +{ + struct hv_device *device = (struct hv_device *)context; + struct storvsc_device *storDevice; + u32 bytesRecvd; + u64 requestId; + unsigned char packet[ALIGN_UP(sizeof(struct vstor_packet), 8)]; + struct storvsc_request_extension *request; + int ret; + + DPRINT_ENTER(STORVSC); + + ASSERT(device); + + storDevice = MustGetStorDevice(device); + if (!storDevice) { + DPRINT_ERR(STORVSC, "unable to get stor device..." + "device being destroyed?"); + DPRINT_EXIT(STORVSC); + return; + } + + do { + ret = device->Driver->VmbusChannelInterface.RecvPacket(device, + packet, + ALIGN_UP(sizeof(struct vstor_packet), 8), + &bytesRecvd, &requestId); + if (ret == 0 && bytesRecvd > 0) { + DPRINT_DBG(STORVSC, "receive %d bytes - tid %llx", + bytesRecvd, requestId); + + /* ASSERT(bytesRecvd == sizeof(struct vstor_packet)); */ + + request = (struct storvsc_request_extension *) + (unsigned long)requestId; + ASSERT(request); + + /* if (vstorPacket.Flags & SYNTHETIC_FLAG) */ + if ((request == &storDevice->InitRequest) || + (request == &storDevice->ResetRequest)) { + /* DPRINT_INFO(STORVSC, + * "reset completion - operation " + * "%u status %u", + * vstorPacket.Operation, + * vstorPacket.Status); */ + + memcpy(&request->VStorPacket, packet, + sizeof(struct vstor_packet)); + + osd_WaitEventSet(request->WaitEvent); + } else { + StorVscOnReceive(device, + (struct vstor_packet *)packet, + request); + } + } else { + /* DPRINT_DBG(STORVSC, "nothing else to read..."); */ + break; + } + } while (1); + + PutStorDevice(device); + + DPRINT_EXIT(STORVSC); + return; +} + +static int StorVscConnectToVsp(struct hv_device *Device) +{ + struct vmstorage_channel_properties props; + struct storvsc_driver_object *storDriver; + int ret; + + storDriver = (struct storvsc_driver_object *)Device->Driver; + memset(&props, sizeof(struct vmstorage_channel_properties), 0); + + /* Open the channel */ + ret = Device->Driver->VmbusChannelInterface.Open(Device, + storDriver->RingBufferSize, + storDriver->RingBufferSize, + (void *)&props, + sizeof(struct vmstorage_channel_properties), + StorVscOnChannelCallback, + Device); + + DPRINT_DBG(STORVSC, "storage props: path id %d, tgt id %d, max xfer %d", + props.PathId, props.TargetId, props.MaxTransferBytes); + + if (ret != 0) { + DPRINT_ERR(STORVSC, "unable to open channel: %d", ret); + return -1; + } + + ret = StorVscChannelInit(Device); + + return ret; +} + +/** + * StorVscOnDeviceAdd - Callback when the device belonging to this driver is added + */ +static int StorVscOnDeviceAdd(struct hv_device *Device, void *AdditionalInfo) +{ + struct storvsc_device *storDevice; + /* struct vmstorage_channel_properties *props; */ + struct storvsc_device_info *deviceInfo; + int ret = 0; + + DPRINT_ENTER(STORVSC); + + deviceInfo = (struct storvsc_device_info *)AdditionalInfo; + storDevice = AllocStorDevice(Device); + if (!storDevice) { + ret = -1; + goto Cleanup; + } + + /* Save the channel properties to our storvsc channel */ + /* props = (struct vmstorage_channel_properties *) + * channel->offerMsg.Offer.u.Standard.UserDefined; */ + + /* FIXME: */ + /* + * If we support more than 1 scsi channel, we need to set the + * port number here to the scsi channel but how do we get the + * scsi channel prior to the bus scan + */ + + /* storChannel->PortNumber = 0; + storChannel->PathId = props->PathId; + storChannel->TargetId = props->TargetId; */ + + storDevice->PortNumber = deviceInfo->PortNumber; + /* Send it back up */ + ret = StorVscConnectToVsp(Device); + + /* deviceInfo->PortNumber = storDevice->PortNumber; */ + deviceInfo->PathId = storDevice->PathId; + deviceInfo->TargetId = storDevice->TargetId; + + DPRINT_DBG(STORVSC, "assigned port %u, path %u target %u\n", + storDevice->PortNumber, storDevice->PathId, + storDevice->TargetId); + +Cleanup: + DPRINT_EXIT(STORVSC); + + return ret; +} + +/** + * StorVscOnDeviceRemove - Callback when the our device is being removed + */ +static int StorVscOnDeviceRemove(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + + DPRINT_ENTER(STORVSC); + + DPRINT_INFO(STORVSC, "disabling storage device (%p)...", + Device->Extension); + + storDevice = ReleaseStorDevice(Device); + + /* + * At this point, all outbound traffic should be disable. We + * only allow inbound traffic (responses) to proceed so that + * outstanding requests can be completed. + */ + while (atomic_read(&storDevice->NumOutstandingRequests)) { + DPRINT_INFO(STORVSC, "waiting for %d requests to complete...", + atomic_read(&storDevice->NumOutstandingRequests)); + udelay(100); + } + + DPRINT_INFO(STORVSC, "removing storage device (%p)...", + Device->Extension); + + storDevice = FinalReleaseStorDevice(Device); + + DPRINT_INFO(STORVSC, "storage device (%p) safe to remove", storDevice); + + /* Close the channel */ + Device->Driver->VmbusChannelInterface.Close(Device); + + FreeStorDevice(storDevice); + + DPRINT_EXIT(STORVSC); + return 0; +} + +static int StorVscOnHostReset(struct hv_device *Device) +{ + struct storvsc_device *storDevice; + struct storvsc_request_extension *request; + struct vstor_packet *vstorPacket; + int ret; + + DPRINT_ENTER(STORVSC); + + DPRINT_INFO(STORVSC, "resetting host adapter..."); + + storDevice = GetStorDevice(Device); + if (!storDevice) { + DPRINT_ERR(STORVSC, "unable to get stor device..." + "device being destroyed?"); + DPRINT_EXIT(STORVSC); + return -1; + } + + request = &storDevice->ResetRequest; + vstorPacket = &request->VStorPacket; + + request->WaitEvent = osd_WaitEventCreate(); + + vstorPacket->Operation = VStorOperationResetBus; + vstorPacket->Flags = REQUEST_COMPLETION_FLAG; + vstorPacket->VmSrb.PathId = storDevice->PathId; + + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)&storDevice->ResetRequest, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + if (ret != 0) { + DPRINT_ERR(STORVSC, "Unable to send reset packet %p ret %d", + vstorPacket, ret); + goto Cleanup; + } + + /* FIXME: Add a timeout */ + osd_WaitEventWait(request->WaitEvent); + + kfree(request->WaitEvent); + DPRINT_INFO(STORVSC, "host adapter reset completed"); + + /* + * At this point, all outstanding requests in the adapter + * should have been flushed out and return to us + */ + +Cleanup: + PutStorDevice(Device); + DPRINT_EXIT(STORVSC); + return ret; +} + +/** + * StorVscOnIORequest - Callback to initiate an I/O request + */ +static int StorVscOnIORequest(struct hv_device *Device, + struct hv_storvsc_request *Request) +{ + struct storvsc_device *storDevice; + struct storvsc_request_extension *requestExtension; + struct vstor_packet *vstorPacket; + int ret = 0; + + DPRINT_ENTER(STORVSC); + + requestExtension = + (struct storvsc_request_extension *)Request->Extension; + vstorPacket = &requestExtension->VStorPacket; + storDevice = GetStorDevice(Device); + + DPRINT_DBG(STORVSC, "enter - Device %p, DeviceExt %p, Request %p, " + "Extension %p", Device, storDevice, Request, + requestExtension); + + DPRINT_DBG(STORVSC, "req %p len %d bus %d, target %d, lun %d cdblen %d", + Request, Request->DataBuffer.Length, Request->Bus, + Request->TargetId, Request->LunId, Request->CdbLen); + + if (!storDevice) { + DPRINT_ERR(STORVSC, "unable to get stor device..." + "device being destroyed?"); + DPRINT_EXIT(STORVSC); + return -2; + } + + /* print_hex_dump_bytes("", DUMP_PREFIX_NONE, Request->Cdb, + * Request->CdbLen); */ + + requestExtension->Request = Request; + requestExtension->Device = Device; + + memset(vstorPacket, 0 , sizeof(struct vstor_packet)); + + vstorPacket->Flags |= REQUEST_COMPLETION_FLAG; + + vstorPacket->VmSrb.Length = sizeof(struct vmscsi_request); + + vstorPacket->VmSrb.PortNumber = Request->Host; + vstorPacket->VmSrb.PathId = Request->Bus; + vstorPacket->VmSrb.TargetId = Request->TargetId; + vstorPacket->VmSrb.Lun = Request->LunId; + + vstorPacket->VmSrb.SenseInfoLength = SENSE_BUFFER_SIZE; + + /* Copy over the scsi command descriptor block */ + vstorPacket->VmSrb.CdbLength = Request->CdbLen; + memcpy(&vstorPacket->VmSrb.Cdb, Request->Cdb, Request->CdbLen); + + vstorPacket->VmSrb.DataIn = Request->Type; + vstorPacket->VmSrb.DataTransferLength = Request->DataBuffer.Length; + + vstorPacket->Operation = VStorOperationExecuteSRB; + + DPRINT_DBG(STORVSC, "srb - len %d port %d, path %d, target %d, " + "lun %d senselen %d cdblen %d", + vstorPacket->VmSrb.Length, + vstorPacket->VmSrb.PortNumber, + vstorPacket->VmSrb.PathId, + vstorPacket->VmSrb.TargetId, + vstorPacket->VmSrb.Lun, + vstorPacket->VmSrb.SenseInfoLength, + vstorPacket->VmSrb.CdbLength); + + if (requestExtension->Request->DataBuffer.Length) { + ret = Device->Driver->VmbusChannelInterface. + SendPacketMultiPageBuffer(Device, + &requestExtension->Request->DataBuffer, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)requestExtension); + } else { + ret = Device->Driver->VmbusChannelInterface.SendPacket(Device, + vstorPacket, + sizeof(struct vstor_packet), + (unsigned long)requestExtension, + VmbusPacketTypeDataInBand, + VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); + } + + if (ret != 0) { + DPRINT_DBG(STORVSC, "Unable to send packet %p ret %d", + vstorPacket, ret); + } + + atomic_inc(&storDevice->NumOutstandingRequests); + + PutStorDevice(Device); + + DPRINT_EXIT(STORVSC); + return ret; +} + +/** + * StorVscOnCleanup - Perform any cleanup when the driver is removed + */ +static void StorVscOnCleanup(struct hv_driver *Driver) +{ + DPRINT_ENTER(STORVSC); + DPRINT_EXIT(STORVSC); +} + +/** + * StorVscInitialize - Main entry point + */ +int StorVscInitialize(struct hv_driver *Driver) +{ + struct storvsc_driver_object *storDriver; + + DPRINT_ENTER(STORVSC); + + storDriver = (struct storvsc_driver_object *)Driver; + + DPRINT_DBG(STORVSC, "sizeof(STORVSC_REQUEST)=%zd " + "sizeof(struct storvsc_request_extension)=%zd " + "sizeof(struct vstor_packet)=%zd, " + "sizeof(struct vmscsi_request)=%zd", + sizeof(struct hv_storvsc_request), + sizeof(struct storvsc_request_extension), + sizeof(struct vstor_packet), + sizeof(struct vmscsi_request)); + + /* Make sure we are at least 2 pages since 1 page is used for control */ + ASSERT(storDriver->RingBufferSize >= (PAGE_SIZE << 1)); + + Driver->name = gDriverName; + memcpy(&Driver->deviceType, &gStorVscDeviceType, + sizeof(struct hv_guid)); + + storDriver->RequestExtSize = sizeof(struct storvsc_request_extension); + + /* + * Divide the ring buffer data size (which is 1 page less + * than the ring buffer size since that page is reserved for + * the ring buffer indices) by the max request size (which is + * VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER + struct vstor_packet + u64) + */ + storDriver->MaxOutstandingRequestsPerChannel = + ((storDriver->RingBufferSize - PAGE_SIZE) / + ALIGN_UP(MAX_MULTIPAGE_BUFFER_PACKET + + sizeof(struct vstor_packet) + sizeof(u64), + sizeof(u64))); + + DPRINT_INFO(STORVSC, "max io %u, currently %u\n", + storDriver->MaxOutstandingRequestsPerChannel, + STORVSC_MAX_IO_REQUESTS); + + /* Setup the dispatch table */ + storDriver->Base.OnDeviceAdd = StorVscOnDeviceAdd; + storDriver->Base.OnDeviceRemove = StorVscOnDeviceRemove; + storDriver->Base.OnCleanup = StorVscOnCleanup; + + storDriver->OnIORequest = StorVscOnIORequest; + storDriver->OnHostReset = StorVscOnHostReset; + + DPRINT_EXIT(STORVSC); + + return 0; +} diff --git a/drivers/staging/hv/StorVscApi.h b/drivers/staging/hv/StorVscApi.h new file mode 100644 index 00000000000..69c14066c47 --- /dev/null +++ b/drivers/staging/hv/StorVscApi.h @@ -0,0 +1,113 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _STORVSC_API_H_ +#define _STORVSC_API_H_ + +#include "VmbusApi.h" + +/* Defines */ +#define STORVSC_RING_BUFFER_SIZE (10*PAGE_SIZE) +#define BLKVSC_RING_BUFFER_SIZE (20*PAGE_SIZE) + +#define STORVSC_MAX_IO_REQUESTS 64 + +/* + * In Hyper-V, each port/path/target maps to 1 scsi host adapter. In + * reality, the path/target is not used (ie always set to 0) so our + * scsi host adapter essentially has 1 bus with 1 target that contains + * up to 256 luns. + */ +#define STORVSC_MAX_LUNS_PER_TARGET 64 +#define STORVSC_MAX_TARGETS 1 +#define STORVSC_MAX_CHANNELS 1 + +struct hv_storvsc_request; + +/* Matches Windows-end */ +enum storvsc_request_type{ + WRITE_TYPE, + READ_TYPE, + UNKNOWN_TYPE, +}; + +struct hv_storvsc_request { + enum storvsc_request_type Type; + u32 Host; + u32 Bus; + u32 TargetId; + u32 LunId; + u8 *Cdb; + u32 CdbLen; + u32 Status; + u32 BytesXfer; + + unsigned char *SenseBuffer; + u32 SenseBufferSize; + + void *Context; + + void (*OnIOCompletion)(struct hv_storvsc_request *Request); + + /* This points to the memory after DataBuffer */ + void *Extension; + + struct hv_multipage_buffer DataBuffer; +}; + +/* Represents the block vsc driver */ +struct storvsc_driver_object { + /* Must be the first field */ + /* Which is a bug FIXME! */ + struct hv_driver Base; + + /* Set by caller (in bytes) */ + u32 RingBufferSize; + + /* Allocate this much private extension for each I/O request */ + u32 RequestExtSize; + + /* Maximum # of requests in flight per channel/device */ + u32 MaxOutstandingRequestsPerChannel; + + /* Set by the caller to allow us to re-enumerate the bus on the host */ + void (*OnHostRescan)(struct hv_device *Device); + + /* Specific to this driver */ + int (*OnIORequest)(struct hv_device *Device, + struct hv_storvsc_request *Request); + int (*OnHostReset)(struct hv_device *Device); +}; + +struct storvsc_device_info { + unsigned int PortNumber; + unsigned char PathId; + unsigned char TargetId; +}; + +/* Interface */ +int StorVscInitialize(struct hv_driver *driver); +int BlkVscInitialize(struct hv_driver *driver); + +#endif /* _STORVSC_API_H_ */ diff --git a/drivers/staging/hv/TODO b/drivers/staging/hv/TODO new file mode 100644 index 00000000000..4d390b23774 --- /dev/null +++ b/drivers/staging/hv/TODO @@ -0,0 +1,13 @@ +TODO: + - fix remaining checkpatch warnings and errors + - remove RingBuffer.c to us in-kernel ringbuffer functions instead. + - audit the vmbus to verify it is working properly with the + driver model + - see if the vmbus can be merged with the other virtual busses + in the kernel + - audit the network driver + - audit the block driver + - audit the scsi driver + +Please send patches for this code to Greg Kroah-Hartman <gregkh@suse.de>, +Hank Janssen <hjanssen@microsoft.com>, and Haiyang Zhang <haiyangz@microsoft.com>. diff --git a/drivers/staging/hv/VersionInfo.h b/drivers/staging/hv/VersionInfo.h new file mode 100644 index 00000000000..9c3641d99ed --- /dev/null +++ b/drivers/staging/hv/VersionInfo.h @@ -0,0 +1,31 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#ifndef __HV_VERSION_INFO +#define __HV_VERSION_INFO + +static const char VersionDate[] = __DATE__; +static const char VersionTime[] = __TIME__; +static const char VersionDesc[] = "Version 2.0"; + +#endif diff --git a/drivers/staging/hv/Vmbus.c b/drivers/staging/hv/Vmbus.c new file mode 100644 index 00000000000..a4dd06f6d45 --- /dev/null +++ b/drivers/staging/hv/Vmbus.c @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#include <linux/kernel.h> +#include <linux/mm.h> +#include "osd.h" +#include "logging.h" +#include "VersionInfo.h" +#include "VmbusPrivate.h" + +static const char *gDriverName = "vmbus"; + +/* + * Windows vmbus does not defined this. + * We defined this to be consistent with other devices + */ +/* {c5295816-f63a-4d5f-8d1a-4daf999ca185} */ +static const struct hv_guid gVmbusDeviceType = { + .data = { + 0x16, 0x58, 0x29, 0xc5, 0x3a, 0xf6, 0x5f, 0x4d, + 0x8d, 0x1a, 0x4d, 0xaf, 0x99, 0x9c, 0xa1, 0x85 + } +}; + +/* {ac3760fc-9adf-40aa-9427-a70ed6de95c5} */ +static const struct hv_guid gVmbusDeviceId = { + .data = { + 0xfc, 0x60, 0x37, 0xac, 0xdf, 0x9a, 0xaa, 0x40, + 0x94, 0x27, 0xa7, 0x0e, 0xd6, 0xde, 0x95, 0xc5 + } +}; + +static struct hv_driver *gDriver; /* vmbus driver object */ +static struct hv_device *gDevice; /* vmbus root device */ + +/** + * VmbusGetChannelOffers - Retrieve the channel offers from the parent partition + */ +static void VmbusGetChannelOffers(void) +{ + DPRINT_ENTER(VMBUS); + VmbusChannelRequestOffers(); + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusGetChannelInterface - Get the channel interface + */ +static void VmbusGetChannelInterface(struct vmbus_channel_interface *Interface) +{ + GetChannelInterface(Interface); +} + +/** + * VmbusGetChannelInfo - Get the device info for the specified device object + */ +static void VmbusGetChannelInfo(struct hv_device *DeviceObject, + struct hv_device_info *DeviceInfo) +{ + GetChannelInfo(DeviceObject, DeviceInfo); +} + +/** + * VmbusCreateChildDevice - Creates the child device on the bus that represents the channel offer + */ +struct hv_device *VmbusChildDeviceCreate(struct hv_guid *DeviceType, + struct hv_guid *DeviceInstance, + void *Context) +{ + struct vmbus_driver *vmbusDriver = (struct vmbus_driver *)gDriver; + + return vmbusDriver->OnChildDeviceCreate(DeviceType, DeviceInstance, + Context); +} + +/** + * VmbusChildDeviceAdd - Registers the child device with the vmbus + */ +int VmbusChildDeviceAdd(struct hv_device *ChildDevice) +{ + struct vmbus_driver *vmbusDriver = (struct vmbus_driver *)gDriver; + + return vmbusDriver->OnChildDeviceAdd(gDevice, ChildDevice); +} + +/** + * VmbusChildDeviceRemove Unregisters the child device from the vmbus + */ +void VmbusChildDeviceRemove(struct hv_device *ChildDevice) +{ + struct vmbus_driver *vmbusDriver = (struct vmbus_driver *)gDriver; + + vmbusDriver->OnChildDeviceRemove(ChildDevice); +} + +/** + * VmbusOnDeviceAdd - Callback when the root bus device is added + */ +static int VmbusOnDeviceAdd(struct hv_device *dev, void *AdditionalInfo) +{ + u32 *irqvector = AdditionalInfo; + int ret; + + DPRINT_ENTER(VMBUS); + + gDevice = dev; + + memcpy(&gDevice->deviceType, &gVmbusDeviceType, sizeof(struct hv_guid)); + memcpy(&gDevice->deviceInstance, &gVmbusDeviceId, + sizeof(struct hv_guid)); + + /* strcpy(dev->name, "vmbus"); */ + /* SynIC setup... */ + ret = HvSynicInit(*irqvector); + + /* Connect to VMBus in the root partition */ + ret = VmbusConnect(); + + /* VmbusSendEvent(device->localPortId+1); */ + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusOnDeviceRemove - Callback when the root bus device is removed + */ +static int VmbusOnDeviceRemove(struct hv_device *dev) +{ + int ret = 0; + + DPRINT_ENTER(VMBUS); + VmbusChannelReleaseUnattachedChannels(); + VmbusDisconnect(); + HvSynicCleanup(); + DPRINT_EXIT(VMBUS); + + return ret; +} + +/** + * VmbusOnCleanup - Perform any cleanup when the driver is removed + */ +static void VmbusOnCleanup(struct hv_driver *drv) +{ + /* struct vmbus_driver *driver = (struct vmbus_driver *)drv; */ + + DPRINT_ENTER(VMBUS); + HvCleanup(); + DPRINT_EXIT(VMBUS); +} + +/** + * VmbusOnMsgDPC - DPC routine to handle messages from the hypervisior + */ +static void VmbusOnMsgDPC(struct hv_driver *drv) +{ + void *page_addr = gHvContext.synICMessagePage[0]; + struct hv_message *msg = (struct hv_message *)page_addr + + VMBUS_MESSAGE_SINT; + struct hv_message *copied; + + while (1) { + if (msg->Header.MessageType == HvMessageTypeNone) { + /* no msg */ + break; + } else { + copied = kmalloc(sizeof(*copied), GFP_ATOMIC); + if (copied == NULL) + continue; + + memcpy(copied, msg, sizeof(*copied)); + osd_schedule_callback(gVmbusConnection.WorkQueue, + VmbusOnChannelMessage, + (void *)copied); + } + + msg->Header.MessageType = HvMessageTypeNone; + + /* + * Make sure the write to MessageType (ie set to + * HvMessageTypeNone) happens before we read the + * MessagePending and EOMing. Otherwise, the EOMing + * will not deliver any more messages since there is + * no empty slot + */ + mb(); + + if (msg->Header.MessageFlags.MessagePending) { + /* + * This will cause message queue rescan to + * possibly deliver another msg from the + * hypervisor + */ + wrmsrl(HV_X64_MSR_EOM, 0); + } + } +} + +/** + * VmbusOnEventDPC - DPC routine to handle events from the hypervisior + */ +static void VmbusOnEventDPC(struct hv_driver *drv) +{ + /* TODO: Process any events */ + VmbusOnEvents(); +} + +/** + * VmbusOnISR - ISR routine + */ +static int VmbusOnISR(struct hv_driver *drv) +{ + int ret = 0; + void *page_addr; + struct hv_message *msg; + union hv_synic_event_flags *event; + + page_addr = gHvContext.synICMessagePage[0]; + msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; + + DPRINT_ENTER(VMBUS); + + /* Check if there are actual msgs to be process */ + if (msg->Header.MessageType != HvMessageTypeNone) { + DPRINT_DBG(VMBUS, "received msg type %d size %d", + msg->Header.MessageType, + msg->Header.PayloadSize); + ret |= 0x1; + } + + /* TODO: Check if there are events to be process */ + page_addr = gHvContext.synICEventPage[0]; + event = (union hv_synic_event_flags *)page_addr + VMBUS_MESSAGE_SINT; + + /* Since we are a child, we only need to check bit 0 */ + if (test_and_clear_bit(0, (unsigned long *) &event->Flags32[0])) { + DPRINT_DBG(VMBUS, "received event %d", event->Flags32[0]); + ret |= 0x2; + } + + DPRINT_EXIT(VMBUS); + return ret; +} + +/** + * VmbusInitialize - Main entry point + */ +int VmbusInitialize(struct hv_driver *drv) +{ + struct vmbus_driver *driver = (struct vmbus_driver *)drv; + int ret; + + DPRINT_ENTER(VMBUS); + + DPRINT_INFO(VMBUS, "+++++++ Build Date=%s %s +++++++", + VersionDate, VersionTime); + DPRINT_INFO(VMBUS, "+++++++ Build Description=%s +++++++", + VersionDesc); + DPRINT_INFO(VMBUS, "+++++++ Vmbus supported version = %d +++++++", + VMBUS_REVISION_NUMBER); + DPRINT_INFO(VMBUS, "+++++++ Vmbus using SINT %d +++++++", + VMBUS_MESSAGE_SINT); + DPRINT_DBG(VMBUS, "sizeof(VMBUS_CHANNEL_PACKET_PAGE_BUFFER)=%zd, " + "sizeof(VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER)=%zd", + sizeof(struct VMBUS_CHANNEL_PACKET_PAGE_BUFFER), + sizeof(struct VMBUS_CHANNEL_PACKET_MULITPAGE_BUFFER)); + + drv->name = gDriverName; + memcpy(&drv->deviceType, &gVmbusDeviceType, sizeof(struct hv_guid)); + + /* Setup dispatch table */ + driver->Base.OnDeviceAdd = VmbusOnDeviceAdd; + driver->Base.OnDeviceRemove = VmbusOnDeviceRemove; + driver->Base.OnCleanup = VmbusOnCleanup; + driver->OnIsr = VmbusOnISR; + driver->OnMsgDpc = VmbusOnMsgDPC; + driver->OnEventDpc = VmbusOnEventDPC; + driver->GetChannelOffers = VmbusGetChannelOffers; + driver->GetChannelInterface = VmbusGetChannelInterface; + driver->GetChannelInfo = VmbusGetChannelInfo; + + /* Hypervisor initialization...setup hypercall page..etc */ + ret = HvInit(); + if (ret != 0) + DPRINT_ERR(VMBUS, "Unable to initialize the hypervisor - 0x%x", + ret); + gDriver = drv; + + DPRINT_EXIT(VMBUS); + + return ret; +} diff --git a/drivers/staging/hv/VmbusApi.h b/drivers/staging/hv/VmbusApi.h new file mode 100644 index 00000000000..d089bb193e7 --- /dev/null +++ b/drivers/staging/hv/VmbusApi.h @@ -0,0 +1,175 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _VMBUS_API_H_ +#define _VMBUS_API_H_ + +#define MAX_PAGE_BUFFER_COUNT 16 +#define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */ + +#pragma pack(push, 1) + +/* Single-page buffer */ +struct hv_page_buffer { + u32 Length; + u32 Offset; + u64 Pfn; +}; + +/* Multiple-page buffer */ +struct hv_multipage_buffer { + /* Length and Offset determines the # of pfns in the array */ + u32 Length; + u32 Offset; + u64 PfnArray[MAX_MULTIPAGE_BUFFER_COUNT]; +}; + +/* 0x18 includes the proprietary packet header */ +#define MAX_PAGE_BUFFER_PACKET (0x18 + \ + (sizeof(struct hv_page_buffer) * \ + MAX_PAGE_BUFFER_COUNT)) +#define MAX_MULTIPAGE_BUFFER_PACKET (0x18 + \ + sizeof(struct hv_multipage_buffer)) + + +#pragma pack(pop) + +struct hv_driver; +struct hv_device; + +struct hv_dev_port_info { + u32 InterruptMask; + u32 ReadIndex; + u32 WriteIndex; + u32 BytesAvailToRead; + u32 BytesAvailToWrite; +}; + +struct hv_device_info { + u32 ChannelId; + u32 ChannelState; + struct hv_guid ChannelType; + struct hv_guid ChannelInstance; + + u32 MonitorId; + u32 ServerMonitorPending; + u32 ServerMonitorLatency; + u32 ServerMonitorConnectionId; + u32 ClientMonitorPending; + u32 ClientMonitorLatency; + u32 ClientMonitorConnectionId; + + struct hv_dev_port_info Inbound; + struct hv_dev_port_info Outbound; +}; + +struct vmbus_channel_interface { + int (*Open)(struct hv_device *Device, u32 SendBufferSize, + u32 RecvRingBufferSize, void *UserData, u32 UserDataLen, + void (*ChannelCallback)(void *context), + void *Context); + void (*Close)(struct hv_device *device); + int (*SendPacket)(struct hv_device *Device, const void *Buffer, + u32 BufferLen, u64 RequestId, u32 Type, u32 Flags); + int (*SendPacketPageBuffer)(struct hv_device *dev, + struct hv_page_buffer PageBuffers[], + u32 PageCount, void *Buffer, u32 BufferLen, + u64 RequestId); + int (*SendPacketMultiPageBuffer)(struct hv_device *device, + struct hv_multipage_buffer *mpb, + void *Buffer, + u32 BufferLen, + u64 RequestId); + int (*RecvPacket)(struct hv_device *dev, void *buf, u32 buflen, + u32 *BufferActualLen, u64 *RequestId); + int (*RecvPacketRaw)(struct hv_device *dev, void *buf, u32 buflen, + u32 *BufferActualLen, u64 *RequestId); + int (*EstablishGpadl)(struct hv_device *dev, void *buf, u32 buflen, + u32 *GpadlHandle); + int (*TeardownGpadl)(struct hv_device *device, u32 GpadlHandle); + void (*GetInfo)(struct hv_device *dev, struct hv_device_info *devinfo); +}; + +/* Base driver object */ +struct hv_driver { + const char *name; + + /* the device type supported by this driver */ + struct hv_guid deviceType; + + int (*OnDeviceAdd)(struct hv_device *device, void *data); + int (*OnDeviceRemove)(struct hv_device *device); + void (*OnCleanup)(struct hv_driver *driver); + + struct vmbus_channel_interface VmbusChannelInterface; +}; + +/* Base device object */ +struct hv_device { + /* the driver for this device */ + struct hv_driver *Driver; + + char name[64]; + + /* the device type id of this device */ + struct hv_guid deviceType; + + /* the device instance id of this device */ + struct hv_guid deviceInstance; + + void *context; + + /* Device extension; */ + void *Extension; +}; + +/* Vmbus driver object */ +struct vmbus_driver { + /* !! Must be the 1st field !! */ + /* FIXME if ^, then someone is doing somthing stupid */ + struct hv_driver Base; + + /* Set by the caller */ + struct hv_device * (*OnChildDeviceCreate)(struct hv_guid *DeviceType, + struct hv_guid *DeviceInstance, + void *Context); + void (*OnChildDeviceDestroy)(struct hv_device *device); + int (*OnChildDeviceAdd)(struct hv_device *RootDevice, + struct hv_device *ChildDevice); + void (*OnChildDeviceRemove)(struct hv_device *device); + + /* Set by the callee */ + int (*OnIsr)(struct hv_driver *driver); + void (*OnMsgDpc)(struct hv_driver *driver); + void (*OnEventDpc)(struct hv_driver *driver); + void (*GetChannelOffers)(void); + + void (*GetChannelInterface)(struct vmbus_channel_interface *i); + void (*GetChannelInfo)(struct hv_device *dev, + struct hv_device_info *devinfo); +}; + +int VmbusInitialize(struct hv_driver *drv); + +#endif /* _VMBUS_API_H_ */ diff --git a/drivers/staging/hv/VmbusChannelInterface.h b/drivers/staging/hv/VmbusChannelInterface.h new file mode 100644 index 00000000000..26742823748 --- /dev/null +++ b/drivers/staging/hv/VmbusChannelInterface.h @@ -0,0 +1,89 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#ifndef __VMBUSCHANNELINTERFACE_H +#define __VMBUSCHANNELINTERFACE_H + +/* + * A revision number of vmbus that is used for ensuring both ends on a + * partition are using compatible versions. + */ +#define VMBUS_REVISION_NUMBER 13 + +/* Make maximum size of pipe payload of 16K */ +#define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) + +/* Define PipeMode values. */ +#define VMBUS_PIPE_TYPE_BYTE 0x00000000 +#define VMBUS_PIPE_TYPE_MESSAGE 0x00000004 + +/* The size of the user defined data buffer for non-pipe offers. */ +#define MAX_USER_DEFINED_BYTES 120 + +/* The size of the user defined data buffer for pipe offers. */ +#define MAX_PIPE_USER_DEFINED_BYTES 116 + +/* + * At the center of the Channel Management library is the Channel Offer. This + * struct contains the fundamental information about an offer. + */ +struct vmbus_channel_offer { + struct hv_guid InterfaceType; + struct hv_guid InterfaceInstance; + u64 InterruptLatencyIn100nsUnits; + u32 InterfaceRevision; + u32 ServerContextAreaSize; /* in bytes */ + u16 ChannelFlags; + u16 MmioMegabytes; /* in bytes * 1024 * 1024 */ + + union { + /* Non-pipes: The user has MAX_USER_DEFINED_BYTES bytes. */ + struct { + unsigned char UserDefined[MAX_USER_DEFINED_BYTES]; + } Standard; + + /* + * Pipes: + * The following sructure is an integrated pipe protocol, which + * is implemented on top of standard user-defined data. Pipe + * clients have MAX_PIPE_USER_DEFINED_BYTES left for their own + * use. + */ + struct { + u32 PipeMode; + unsigned char UserDefined[MAX_PIPE_USER_DEFINED_BYTES]; + } Pipe; + } u; + u32 Padding; +} __attribute__((packed)); + +/* Server Flags */ +#define VMBUS_CHANNEL_ENUMERATE_DEVICE_INTERFACE 1 +#define VMBUS_CHANNEL_SERVER_SUPPORTS_TRANSFER_PAGES 2 +#define VMBUS_CHANNEL_SERVER_SUPPORTS_GPADLS 4 +#define VMBUS_CHANNEL_NAMED_PIPE_MODE 0x10 +#define VMBUS_CHANNEL_LOOPBACK_OFFER 0x100 +#define VMBUS_CHANNEL_PARENT_OFFER 0x200 +#define VMBUS_CHANNEL_REQUEST_MONITORED_NOTIFICATION 0x400 + +#endif diff --git a/drivers/staging/hv/VmbusPacketFormat.h b/drivers/staging/hv/VmbusPacketFormat.h new file mode 100644 index 00000000000..79120bc742d --- /dev/null +++ b/drivers/staging/hv/VmbusPacketFormat.h @@ -0,0 +1,160 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#ifndef _VMBUSPACKETFORMAT_H_ + +struct vmpacket_descriptor { + u16 Type; + u16 DataOffset8; + u16 Length8; + u16 Flags; + u64 TransactionId; +} __attribute__((packed)); + +struct vmpacket_header { + u32 PreviousPacketStartOffset; + struct vmpacket_descriptor Descriptor; +} __attribute__((packed)); + +struct vmtransfer_page_range { + u32 ByteCount; + u32 ByteOffset; +} __attribute__((packed)); + +struct vmtransfer_page_packet_header { + struct vmpacket_descriptor d; + u16 TransferPageSetId; + bool SenderOwnsSet; + u8 Reserved; + u32 RangeCount; + struct vmtransfer_page_range Ranges[1]; +} __attribute__((packed)); + +struct vmgpadl_packet_header { + struct vmpacket_descriptor d; + u32 Gpadl; + u32 Reserved; +} __attribute__((packed)); + +struct vmadd_remove_transfer_page_set { + struct vmpacket_descriptor d; + u32 Gpadl; + u16 TransferPageSetId; + u16 Reserved; +} __attribute__((packed)); + +/* + * This structure defines a range in guest physical space that can be made to + * look virtually contiguous. + */ +struct gpa_range { + u32 ByteCount; + u32 ByteOffset; + u64 PfnArray[0]; +}; + +/* + * This is the format for an Establish Gpadl packet, which contains a handle by + * which this GPADL will be known and a set of GPA ranges associated with it. + * This can be converted to a MDL by the guest OS. If there are multiple GPA + * ranges, then the resulting MDL will be "chained," representing multiple VA + * ranges. + */ +struct vmestablish_gpadl { + struct vmpacket_descriptor d; + u32 Gpadl; + u32 RangeCount; + struct gpa_range Range[1]; +} __attribute__((packed)); + +/* + * This is the format for a Teardown Gpadl packet, which indicates that the + * GPADL handle in the Establish Gpadl packet will never be referenced again. + */ +struct vmteardown_gpadl { + struct vmpacket_descriptor d; + u32 Gpadl; + u32 Reserved; /* for alignment to a 8-byte boundary */ +} __attribute__((packed)); + +/* + * This is the format for a GPA-Direct packet, which contains a set of GPA + * ranges, in addition to commands and/or data. + */ +struct vmdata_gpa_direct { + struct vmpacket_descriptor d; + u32 Reserved; + u32 RangeCount; + struct gpa_range Range[1]; +} __attribute__((packed)); + +/* This is the format for a Additional Data Packet. */ +struct vmadditional_data { + struct vmpacket_descriptor d; + u64 TotalBytes; + u32 ByteOffset; + u32 ByteCount; + unsigned char Data[1]; +} __attribute__((packed)); + +union vmpacket_largest_possible_header { + struct vmpacket_descriptor SimpleHeader; + struct vmtransfer_page_packet_header TransferPageHeader; + struct vmgpadl_packet_header GpadlHeader; + struct vmadd_remove_transfer_page_set AddRemoveTransferPageHeader; + struct vmestablish_gpadl EstablishGpadlHeader; + struct vmteardown_gpadl TeardownGpadlHeader; + struct vmdata_gpa_direct DataGpaDirectHeader; +}; + +#define VMPACKET_DATA_START_ADDRESS(__packet) \ + (void *)(((unsigned char *)__packet) + \ + ((struct vmpacket_descriptor)__packet)->DataOffset8 * 8) + +#define VMPACKET_DATA_LENGTH(__packet) \ + ((((struct vmpacket_descriptor)__packet)->Length8 - \ + ((struct vmpacket_descriptor)__packet)->DataOffset8) * 8) + +#define VMPACKET_TRANSFER_MODE(__packet) \ + (((struct IMPACT)__packet)->Type) + +enum vmbus_packet_type { + VmbusPacketTypeInvalid = 0x0, + VmbusPacketTypeSynch = 0x1, + VmbusPacketTypeAddTransferPageSet = 0x2, + VmbusPacketTypeRemoveTransferPageSet = 0x3, + VmbusPacketTypeEstablishGpadl = 0x4, + VmbusPacketTypeTearDownGpadl = 0x5, + VmbusPacketTypeDataInBand = 0x6, + VmbusPacketTypeDataUsingTransferPages = 0x7, + VmbusPacketTypeDataUsingGpadl = 0x8, + VmbusPacketTypeDataUsingGpaDirect = 0x9, + VmbusPacketTypeCancelRequest = 0xa, + VmbusPacketTypeCompletion = 0xb, + VmbusPacketTypeDataUsingAdditionalPackets = 0xc, + VmbusPacketTypeAdditionalData = 0xd +}; + +#define VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED 1 + +#endif diff --git a/drivers/staging/hv/VmbusPrivate.h b/drivers/staging/hv/VmbusPrivate.h new file mode 100644 index 00000000000..05ad2c9380d --- /dev/null +++ b/drivers/staging/hv/VmbusPrivate.h @@ -0,0 +1,134 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _VMBUS_PRIVATE_H_ +#define _VMBUS_PRIVATE_H_ + +#include "Hv.h" +#include "VmbusApi.h" +#include "Channel.h" +#include "ChannelMgmt.h" +#include "ChannelInterface.h" +#include "RingBuffer.h" +#include <linux/list.h> + + +/* + * Maximum channels is determined by the size of the interrupt page + * which is PAGE_SIZE. 1/2 of PAGE_SIZE is for send endpoint interrupt + * and the other is receive endpoint interrupt + */ +#define MAX_NUM_CHANNELS ((PAGE_SIZE >> 1) << 3) /* 16348 channels */ + +/* The value here must be in multiple of 32 */ +/* TODO: Need to make this configurable */ +#define MAX_NUM_CHANNELS_SUPPORTED 256 + + +enum VMBUS_CONNECT_STATE { + Disconnected, + Connecting, + Connected, + Disconnecting +}; + +#define MAX_SIZE_CHANNEL_MESSAGE HV_MESSAGE_PAYLOAD_BYTE_COUNT + +struct VMBUS_CONNECTION { + enum VMBUS_CONNECT_STATE ConnectState; + + atomic_t NextGpadlHandle; + + /* + * Represents channel interrupts. Each bit position represents a + * channel. When a channel sends an interrupt via VMBUS, it finds its + * bit in the sendInterruptPage, set it and calls Hv to generate a port + * event. The other end receives the port event and parse the + * recvInterruptPage to see which bit is set + */ + void *InterruptPage; + void *SendInterruptPage; + void *RecvInterruptPage; + + /* + * 2 pages - 1st page for parent->child notification and 2nd + * is child->parent notification + */ + void *MonitorPages; + struct list_head ChannelMsgList; + spinlock_t channelmsg_lock; + + /* List of channels */ + struct list_head ChannelList; + spinlock_t channel_lock; + + struct workqueue_struct *WorkQueue; +}; + + +struct VMBUS_MSGINFO { + /* Bookkeeping stuff */ + struct list_head MsgListEntry; + + /* Synchronize the request/response if needed */ + struct osd_waitevent *WaitEvent; + + /* The message itself */ + unsigned char Msg[0]; +}; + + +extern struct VMBUS_CONNECTION gVmbusConnection; + +/* General vmbus interface */ + +struct hv_device *VmbusChildDeviceCreate(struct hv_guid *deviceType, + struct hv_guid *deviceInstance, + void *context); + +int VmbusChildDeviceAdd(struct hv_device *Device); + +void VmbusChildDeviceRemove(struct hv_device *Device); + +/* static void */ +/* VmbusChildDeviceDestroy( */ +/* struct hv_device *); */ + +struct vmbus_channel *GetChannelFromRelId(u32 relId); + + +/* Connection interface */ + +int VmbusConnect(void); + +int VmbusDisconnect(void); + +int VmbusPostMessage(void *buffer, size_t bufSize); + +int VmbusSetEvent(u32 childRelId); + +void VmbusOnEvents(void); + + +#endif /* _VMBUS_PRIVATE_H_ */ diff --git a/drivers/staging/hv/blkvsc_drv.c b/drivers/staging/hv/blkvsc_drv.c new file mode 100644 index 00000000000..99c49261a8b --- /dev/null +++ b/drivers/staging/hv/blkvsc_drv.c @@ -0,0 +1,1511 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/blkdev.h> +#include <linux/major.h> +#include <linux/delay.h> +#include <linux/hdreg.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_eh.h> +#include <scsi/scsi_dbg.h> +#include "osd.h" +#include "logging.h" +#include "vmbus.h" +#include "StorVscApi.h" + + +#define BLKVSC_MINORS 64 + +enum blkvsc_device_type { + UNKNOWN_DEV_TYPE, + HARDDISK_TYPE, + DVD_TYPE, +}; + +/* + * This request ties the struct request and struct + * blkvsc_request/hv_storvsc_request together A struct request may be + * represented by 1 or more struct blkvsc_request + */ +struct blkvsc_request_group { + int outstanding; + int status; + struct list_head blkvsc_req_list; /* list of blkvsc_requests */ +}; + +struct blkvsc_request { + /* blkvsc_request_group.blkvsc_req_list */ + struct list_head req_entry; + + /* block_device_context.pending_list */ + struct list_head pend_entry; + + /* This may be null if we generate a request internally */ + struct request *req; + + struct block_device_context *dev; + + /* The group this request is part of. Maybe null */ + struct blkvsc_request_group *group; + + wait_queue_head_t wevent; + int cond; + + int write; + sector_t sector_start; + unsigned long sector_count; + + unsigned char sense_buffer[SCSI_SENSE_BUFFERSIZE]; + unsigned char cmd_len; + unsigned char cmnd[MAX_COMMAND_SIZE]; + + struct hv_storvsc_request request; + /* + * !!!DO NOT ADD ANYTHING BELOW HERE!!! Otherwise, memory can overlap, + * because - The extension buffer falls right here and is pointed to by + * request.Extension; + * Which sounds like a horrible idea, who designed this? + */ +}; + +/* Per device structure */ +struct block_device_context { + /* point back to our device context */ + struct device_context *device_ctx; + struct kmem_cache *request_pool; + spinlock_t lock; + struct gendisk *gd; + enum blkvsc_device_type device_type; + struct list_head pending_list; + + unsigned char device_id[64]; + unsigned int device_id_len; + int num_outstanding_reqs; + int shutting_down; + int media_not_present; + unsigned int sector_size; + sector_t capacity; + unsigned int port; + unsigned char path; + unsigned char target; + int users; +}; + +/* Per driver */ +struct blkvsc_driver_context { + /* !! These must be the first 2 fields !! */ + /* FIXME this is a bug! */ + struct driver_context drv_ctx; + struct storvsc_driver_object drv_obj; +}; + +/* Static decl */ +static int blkvsc_probe(struct device *dev); +static int blkvsc_remove(struct device *device); +static void blkvsc_shutdown(struct device *device); + +static int blkvsc_open(struct block_device *bdev, fmode_t mode); +static int blkvsc_release(struct gendisk *disk, fmode_t mode); +static int blkvsc_media_changed(struct gendisk *gd); +static int blkvsc_revalidate_disk(struct gendisk *gd); +static int blkvsc_getgeo(struct block_device *bd, struct hd_geometry *hg); +static int blkvsc_ioctl(struct block_device *bd, fmode_t mode, + unsigned cmd, unsigned long argument); +static void blkvsc_request(struct request_queue *queue); +static void blkvsc_request_completion(struct hv_storvsc_request *request); +static int blkvsc_do_request(struct block_device_context *blkdev, + struct request *req); +static int blkvsc_submit_request(struct blkvsc_request *blkvsc_req, + void (*request_completion)(struct hv_storvsc_request *)); +static void blkvsc_init_rw(struct blkvsc_request *blkvsc_req); +static void blkvsc_cmd_completion(struct hv_storvsc_request *request); +static int blkvsc_do_inquiry(struct block_device_context *blkdev); +static int blkvsc_do_read_capacity(struct block_device_context *blkdev); +static int blkvsc_do_read_capacity16(struct block_device_context *blkdev); +static int blkvsc_do_flush(struct block_device_context *blkdev); +static int blkvsc_cancel_pending_reqs(struct block_device_context *blkdev); +static int blkvsc_do_pending_reqs(struct block_device_context *blkdev); + + +static int blkvsc_ringbuffer_size = BLKVSC_RING_BUFFER_SIZE; + +/* The one and only one */ +static struct blkvsc_driver_context g_blkvsc_drv; + +static struct block_device_operations block_ops = { + .owner = THIS_MODULE, + .open = blkvsc_open, + .release = blkvsc_release, + .media_changed = blkvsc_media_changed, + .revalidate_disk = blkvsc_revalidate_disk, + .getgeo = blkvsc_getgeo, + .ioctl = blkvsc_ioctl, +}; + +/** + * blkvsc_drv_init - BlkVsc driver initialization. + */ +static int blkvsc_drv_init(int (*drv_init)(struct hv_driver *drv)) +{ + struct storvsc_driver_object *storvsc_drv_obj = &g_blkvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_blkvsc_drv.drv_ctx; + int ret; + + DPRINT_ENTER(BLKVSC_DRV); + + vmbus_get_interface(&storvsc_drv_obj->Base.VmbusChannelInterface); + + storvsc_drv_obj->RingBufferSize = blkvsc_ringbuffer_size; + + /* Callback to client driver to complete the initialization */ + drv_init(&storvsc_drv_obj->Base); + + drv_ctx->driver.name = storvsc_drv_obj->Base.name; + memcpy(&drv_ctx->class_id, &storvsc_drv_obj->Base.deviceType, + sizeof(struct hv_guid)); + + drv_ctx->probe = blkvsc_probe; + drv_ctx->remove = blkvsc_remove; + drv_ctx->shutdown = blkvsc_shutdown; + + /* The driver belongs to vmbus */ + ret = vmbus_child_driver_register(drv_ctx); + + DPRINT_EXIT(BLKVSC_DRV); + + return ret; +} + +static int blkvsc_drv_exit_cb(struct device *dev, void *data) +{ + struct device **curr = (struct device **)data; + *curr = dev; + return 1; /* stop iterating */ +} + +static void blkvsc_drv_exit(void) +{ + struct storvsc_driver_object *storvsc_drv_obj = &g_blkvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_blkvsc_drv.drv_ctx; + struct device *current_dev; + int ret; + + DPRINT_ENTER(BLKVSC_DRV); + + while (1) { + current_dev = NULL; + + /* Get the device */ + ret = driver_for_each_device(&drv_ctx->driver, NULL, + (void *) ¤t_dev, + blkvsc_drv_exit_cb); + + if (ret) + DPRINT_WARN(BLKVSC_DRV, + "driver_for_each_device returned %d", ret); + + + if (current_dev == NULL) + break; + + /* Initiate removal from the top-down */ + device_unregister(current_dev); + } + + if (storvsc_drv_obj->Base.OnCleanup) + storvsc_drv_obj->Base.OnCleanup(&storvsc_drv_obj->Base); + + vmbus_child_driver_unregister(drv_ctx); + + DPRINT_EXIT(BLKVSC_DRV); + + return; +} + +/** + * blkvsc_probe - Add a new device for this driver + */ +static int blkvsc_probe(struct device *device) +{ + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct blkvsc_driver_context *blkvsc_drv_ctx = + (struct blkvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &blkvsc_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct hv_device *device_obj = &device_ctx->device_obj; + + struct block_device_context *blkdev = NULL; + struct storvsc_device_info device_info; + int major = 0; + int devnum = 0; + int ret = 0; + static int ide0_registered; + static int ide1_registered; + + DPRINT_ENTER(BLKVSC_DRV); + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_probe - enter"); + + if (!storvsc_drv_obj->Base.OnDeviceAdd) { + DPRINT_ERR(BLKVSC_DRV, "OnDeviceAdd() not set"); + ret = -1; + goto Cleanup; + } + + blkdev = kzalloc(sizeof(struct block_device_context), GFP_KERNEL); + if (!blkdev) { + ret = -ENOMEM; + goto Cleanup; + } + + INIT_LIST_HEAD(&blkdev->pending_list); + + /* Initialize what we can here */ + spin_lock_init(&blkdev->lock); + + ASSERT(sizeof(struct blkvsc_request_group) <= + sizeof(struct blkvsc_request)); + + blkdev->request_pool = kmem_cache_create(dev_name(&device_ctx->device), + sizeof(struct blkvsc_request) + + storvsc_drv_obj->RequestExtSize, 0, + SLAB_HWCACHE_ALIGN, NULL); + if (!blkdev->request_pool) { + ret = -ENOMEM; + goto Cleanup; + } + + + /* Call to the vsc driver to add the device */ + ret = storvsc_drv_obj->Base.OnDeviceAdd(device_obj, &device_info); + if (ret != 0) { + DPRINT_ERR(BLKVSC_DRV, "unable to add blkvsc device"); + goto Cleanup; + } + + blkdev->device_ctx = device_ctx; + /* this identified the device 0 or 1 */ + blkdev->target = device_info.TargetId; + /* this identified the ide ctrl 0 or 1 */ + blkdev->path = device_info.PathId; + + dev_set_drvdata(device, blkdev); + + /* Calculate the major and device num */ + if (blkdev->path == 0) { + major = IDE0_MAJOR; + devnum = blkdev->path + blkdev->target; /* 0 or 1 */ + + if (!ide0_registered) { + ret = register_blkdev(major, "ide"); + if (ret != 0) { + DPRINT_ERR(BLKVSC_DRV, + "register_blkdev() failed! ret %d", + ret); + goto Remove; + } + + ide0_registered = 1; + } + } else if (blkdev->path == 1) { + major = IDE1_MAJOR; + devnum = blkdev->path + blkdev->target + 1; /* 2 or 3 */ + + if (!ide1_registered) { + ret = register_blkdev(major, "ide"); + if (ret != 0) { + DPRINT_ERR(BLKVSC_DRV, + "register_blkdev() failed! ret %d", + ret); + goto Remove; + } + + ide1_registered = 1; + } + } else { + DPRINT_ERR(BLKVSC_DRV, "invalid pathid"); + ret = -1; + goto Cleanup; + } + + DPRINT_INFO(BLKVSC_DRV, "blkvsc registered for major %d!!", major); + + blkdev->gd = alloc_disk(BLKVSC_MINORS); + if (!blkdev->gd) { + DPRINT_ERR(BLKVSC_DRV, "register_blkdev() failed! ret %d", ret); + ret = -1; + goto Cleanup; + } + + blkdev->gd->queue = blk_init_queue(blkvsc_request, &blkdev->lock); + + blk_queue_max_segment_size(blkdev->gd->queue, PAGE_SIZE); + blk_queue_max_phys_segments(blkdev->gd->queue, + MAX_MULTIPAGE_BUFFER_COUNT); + blk_queue_max_hw_segments(blkdev->gd->queue, + MAX_MULTIPAGE_BUFFER_COUNT); + blk_queue_segment_boundary(blkdev->gd->queue, PAGE_SIZE-1); + blk_queue_bounce_limit(blkdev->gd->queue, BLK_BOUNCE_ANY); + blk_queue_dma_alignment(blkdev->gd->queue, 511); + + blkdev->gd->major = major; + if (devnum == 1 || devnum == 3) + blkdev->gd->first_minor = BLKVSC_MINORS; + else + blkdev->gd->first_minor = 0; + blkdev->gd->fops = &block_ops; + blkdev->gd->private_data = blkdev; + sprintf(blkdev->gd->disk_name, "hd%c", 'a' + devnum); + + blkvsc_do_inquiry(blkdev); + if (blkdev->device_type == DVD_TYPE) { + set_disk_ro(blkdev->gd, 1); + blkdev->gd->flags |= GENHD_FL_REMOVABLE; + blkvsc_do_read_capacity(blkdev); + } else { + blkvsc_do_read_capacity16(blkdev); + } + + set_capacity(blkdev->gd, blkdev->capacity * (blkdev->sector_size/512)); + blk_queue_logical_block_size(blkdev->gd->queue, blkdev->sector_size); + /* go! */ + add_disk(blkdev->gd); + + DPRINT_INFO(BLKVSC_DRV, "%s added!! capacity %lu sector_size %d", + blkdev->gd->disk_name, (unsigned long)blkdev->capacity, + blkdev->sector_size); + + return ret; + +Remove: + storvsc_drv_obj->Base.OnDeviceRemove(device_obj); + +Cleanup: + if (blkdev) { + if (blkdev->request_pool) { + kmem_cache_destroy(blkdev->request_pool); + blkdev->request_pool = NULL; + } + kfree(blkdev); + blkdev = NULL; + } + + DPRINT_EXIT(BLKVSC_DRV); + + return ret; +} + +static void blkvsc_shutdown(struct device *device) +{ + struct block_device_context *blkdev = dev_get_drvdata(device); + unsigned long flags; + + if (!blkdev) + return; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_shutdown - users %d disk %s\n", + blkdev->users, blkdev->gd->disk_name); + + spin_lock_irqsave(&blkdev->lock, flags); + + blkdev->shutting_down = 1; + + blk_stop_queue(blkdev->gd->queue); + + spin_unlock_irqrestore(&blkdev->lock, flags); + + while (blkdev->num_outstanding_reqs) { + DPRINT_INFO(STORVSC, "waiting for %d requests to complete...", + blkdev->num_outstanding_reqs); + udelay(100); + } + + blkvsc_do_flush(blkdev); + + spin_lock_irqsave(&blkdev->lock, flags); + + blkvsc_cancel_pending_reqs(blkdev); + + spin_unlock_irqrestore(&blkdev->lock, flags); +} + +static int blkvsc_do_flush(struct block_device_context *blkdev) +{ + struct blkvsc_request *blkvsc_req; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_do_flush()\n"); + + if (blkdev->device_type != HARDDISK_TYPE) + return 0; + + blkvsc_req = kmem_cache_alloc(blkdev->request_pool, GFP_KERNEL); + if (!blkvsc_req) + return -ENOMEM; + + memset(blkvsc_req, 0, sizeof(struct blkvsc_request)); + init_waitqueue_head(&blkvsc_req->wevent); + blkvsc_req->dev = blkdev; + blkvsc_req->req = NULL; + blkvsc_req->write = 0; + + blkvsc_req->request.DataBuffer.PfnArray[0] = 0; + blkvsc_req->request.DataBuffer.Offset = 0; + blkvsc_req->request.DataBuffer.Length = 0; + + blkvsc_req->cmnd[0] = SYNCHRONIZE_CACHE; + blkvsc_req->cmd_len = 10; + + /* + * Set this here since the completion routine may be invoked and + * completed before we return + */ + blkvsc_req->cond = 0; + blkvsc_submit_request(blkvsc_req, blkvsc_cmd_completion); + + wait_event_interruptible(blkvsc_req->wevent, blkvsc_req->cond); + + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + + return 0; +} + +/* Do a scsi INQUIRY cmd here to get the device type (ie disk or dvd) */ +static int blkvsc_do_inquiry(struct block_device_context *blkdev) +{ + struct blkvsc_request *blkvsc_req; + struct page *page_buf; + unsigned char *buf; + unsigned char device_type; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_do_inquiry()\n"); + + blkvsc_req = kmem_cache_alloc(blkdev->request_pool, GFP_KERNEL); + if (!blkvsc_req) + return -ENOMEM; + + memset(blkvsc_req, 0, sizeof(struct blkvsc_request)); + page_buf = alloc_page(GFP_KERNEL); + if (!page_buf) { + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + return -ENOMEM; + } + + init_waitqueue_head(&blkvsc_req->wevent); + blkvsc_req->dev = blkdev; + blkvsc_req->req = NULL; + blkvsc_req->write = 0; + + blkvsc_req->request.DataBuffer.PfnArray[0] = page_to_pfn(page_buf); + blkvsc_req->request.DataBuffer.Offset = 0; + blkvsc_req->request.DataBuffer.Length = 64; + + blkvsc_req->cmnd[0] = INQUIRY; + blkvsc_req->cmnd[1] = 0x1; /* Get product data */ + blkvsc_req->cmnd[2] = 0x83; /* mode page 83 */ + blkvsc_req->cmnd[4] = 64; + blkvsc_req->cmd_len = 6; + + /* + * Set this here since the completion routine may be invoked and + * completed before we return + */ + blkvsc_req->cond = 0; + + blkvsc_submit_request(blkvsc_req, blkvsc_cmd_completion); + + DPRINT_DBG(BLKVSC_DRV, "waiting %p to complete - cond %d\n", + blkvsc_req, blkvsc_req->cond); + + wait_event_interruptible(blkvsc_req->wevent, blkvsc_req->cond); + + buf = kmap(page_buf); + + /* print_hex_dump_bytes("", DUMP_PREFIX_NONE, buf, 64); */ + /* be to le */ + device_type = buf[0] & 0x1F; + + if (device_type == 0x0) { + blkdev->device_type = HARDDISK_TYPE; + } else if (device_type == 0x5) { + blkdev->device_type = DVD_TYPE; + } else { + /* TODO: this is currently unsupported device type */ + blkdev->device_type = UNKNOWN_DEV_TYPE; + } + + DPRINT_DBG(BLKVSC_DRV, "device type %d \n", device_type); + + blkdev->device_id_len = buf[7]; + if (blkdev->device_id_len > 64) + blkdev->device_id_len = 64; + + memcpy(blkdev->device_id, &buf[8], blkdev->device_id_len); + /* printk_hex_dump_bytes("", DUMP_PREFIX_NONE, blkdev->device_id, + * blkdev->device_id_len); */ + + kunmap(page_buf); + + __free_page(page_buf); + + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + + return 0; +} + +/* Do a scsi READ_CAPACITY cmd here to get the size of the disk */ +static int blkvsc_do_read_capacity(struct block_device_context *blkdev) +{ + struct blkvsc_request *blkvsc_req; + struct page *page_buf; + unsigned char *buf; + struct scsi_sense_hdr sense_hdr; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_do_read_capacity()\n"); + + blkdev->sector_size = 0; + blkdev->capacity = 0; + blkdev->media_not_present = 0; /* assume a disk is present */ + + blkvsc_req = kmem_cache_alloc(blkdev->request_pool, GFP_KERNEL); + if (!blkvsc_req) + return -ENOMEM; + + memset(blkvsc_req, 0, sizeof(struct blkvsc_request)); + page_buf = alloc_page(GFP_KERNEL); + if (!page_buf) { + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + return -ENOMEM; + } + + init_waitqueue_head(&blkvsc_req->wevent); + blkvsc_req->dev = blkdev; + blkvsc_req->req = NULL; + blkvsc_req->write = 0; + + blkvsc_req->request.DataBuffer.PfnArray[0] = page_to_pfn(page_buf); + blkvsc_req->request.DataBuffer.Offset = 0; + blkvsc_req->request.DataBuffer.Length = 8; + + blkvsc_req->cmnd[0] = READ_CAPACITY; + blkvsc_req->cmd_len = 16; + + /* + * Set this here since the completion routine may be invoked + * and completed before we return + */ + blkvsc_req->cond = 0; + + blkvsc_submit_request(blkvsc_req, blkvsc_cmd_completion); + + DPRINT_DBG(BLKVSC_DRV, "waiting %p to complete - cond %d\n", + blkvsc_req, blkvsc_req->cond); + + wait_event_interruptible(blkvsc_req->wevent, blkvsc_req->cond); + + /* check error */ + if (blkvsc_req->request.Status) { + scsi_normalize_sense(blkvsc_req->sense_buffer, + SCSI_SENSE_BUFFERSIZE, &sense_hdr); + + if (sense_hdr.asc == 0x3A) { + /* Medium not present */ + blkdev->media_not_present = 1; + } + return 0; + } + buf = kmap(page_buf); + + /* be to le */ + blkdev->capacity = ((buf[0] << 24) | (buf[1] << 16) | + (buf[2] << 8) | buf[3]) + 1; + blkdev->sector_size = (buf[4] << 24) | (buf[5] << 16) | + (buf[6] << 8) | buf[7]; + + kunmap(page_buf); + + __free_page(page_buf); + + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + + return 0; +} + +static int blkvsc_do_read_capacity16(struct block_device_context *blkdev) +{ + struct blkvsc_request *blkvsc_req; + struct page *page_buf; + unsigned char *buf; + struct scsi_sense_hdr sense_hdr; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_do_read_capacity16()\n"); + + blkdev->sector_size = 0; + blkdev->capacity = 0; + blkdev->media_not_present = 0; /* assume a disk is present */ + + blkvsc_req = kmem_cache_alloc(blkdev->request_pool, GFP_KERNEL); + if (!blkvsc_req) + return -ENOMEM; + + memset(blkvsc_req, 0, sizeof(struct blkvsc_request)); + page_buf = alloc_page(GFP_KERNEL); + if (!page_buf) { + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + return -ENOMEM; + } + + init_waitqueue_head(&blkvsc_req->wevent); + blkvsc_req->dev = blkdev; + blkvsc_req->req = NULL; + blkvsc_req->write = 0; + + blkvsc_req->request.DataBuffer.PfnArray[0] = page_to_pfn(page_buf); + blkvsc_req->request.DataBuffer.Offset = 0; + blkvsc_req->request.DataBuffer.Length = 12; + + blkvsc_req->cmnd[0] = 0x9E; /* READ_CAPACITY16; */ + blkvsc_req->cmd_len = 16; + + /* + * Set this here since the completion routine may be invoked + * and completed before we return + */ + blkvsc_req->cond = 0; + + blkvsc_submit_request(blkvsc_req, blkvsc_cmd_completion); + + DPRINT_DBG(BLKVSC_DRV, "waiting %p to complete - cond %d\n", + blkvsc_req, blkvsc_req->cond); + + wait_event_interruptible(blkvsc_req->wevent, blkvsc_req->cond); + + /* check error */ + if (blkvsc_req->request.Status) { + scsi_normalize_sense(blkvsc_req->sense_buffer, + SCSI_SENSE_BUFFERSIZE, &sense_hdr); + if (sense_hdr.asc == 0x3A) { + /* Medium not present */ + blkdev->media_not_present = 1; + } + return 0; + } + buf = kmap(page_buf); + + /* be to le */ + blkdev->capacity = be64_to_cpu(*(unsigned long long *) &buf[0]) + 1; + blkdev->sector_size = be32_to_cpu(*(unsigned int *)&buf[8]); + +#if 0 + blkdev->capacity = ((buf[0] << 24) | (buf[1] << 16) | + (buf[2] << 8) | buf[3]) + 1; + blkdev->sector_size = (buf[4] << 24) | (buf[5] << 16) | + (buf[6] << 8) | buf[7]; +#endif + + kunmap(page_buf); + + __free_page(page_buf); + + kmem_cache_free(blkvsc_req->dev->request_pool, blkvsc_req); + + return 0; +} + +/** + * blkvsc_remove() - Callback when our device is removed + */ +static int blkvsc_remove(struct device *device) +{ + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct blkvsc_driver_context *blkvsc_drv_ctx = + (struct blkvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &blkvsc_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct hv_device *device_obj = &device_ctx->device_obj; + struct block_device_context *blkdev = dev_get_drvdata(device); + unsigned long flags; + int ret; + + DPRINT_ENTER(BLKVSC_DRV); + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_remove()\n"); + + if (!storvsc_drv_obj->Base.OnDeviceRemove) { + DPRINT_EXIT(BLKVSC_DRV); + return -1; + } + + /* + * Call to the vsc driver to let it know that the device is being + * removed + */ + ret = storvsc_drv_obj->Base.OnDeviceRemove(device_obj); + if (ret != 0) { + /* TODO: */ + DPRINT_ERR(BLKVSC_DRV, + "unable to remove blkvsc device (ret %d)", ret); + } + + /* Get to a known state */ + spin_lock_irqsave(&blkdev->lock, flags); + + blkdev->shutting_down = 1; + + blk_stop_queue(blkdev->gd->queue); + + spin_unlock_irqrestore(&blkdev->lock, flags); + + while (blkdev->num_outstanding_reqs) { + DPRINT_INFO(STORVSC, "waiting for %d requests to complete...", + blkdev->num_outstanding_reqs); + udelay(100); + } + + blkvsc_do_flush(blkdev); + + spin_lock_irqsave(&blkdev->lock, flags); + + blkvsc_cancel_pending_reqs(blkdev); + + spin_unlock_irqrestore(&blkdev->lock, flags); + + blk_cleanup_queue(blkdev->gd->queue); + + del_gendisk(blkdev->gd); + + kmem_cache_destroy(blkdev->request_pool); + + kfree(blkdev); + + DPRINT_EXIT(BLKVSC_DRV); + + return ret; +} + +static void blkvsc_init_rw(struct blkvsc_request *blkvsc_req) +{ + ASSERT(blkvsc_req->req); + ASSERT(blkvsc_req->sector_count <= (MAX_MULTIPAGE_BUFFER_COUNT*8)); + + blkvsc_req->cmd_len = 16; + + if (blkvsc_req->sector_start > 0xffffffff) { + if (rq_data_dir(blkvsc_req->req)) { + blkvsc_req->write = 1; + blkvsc_req->cmnd[0] = WRITE_16; + } else { + blkvsc_req->write = 0; + blkvsc_req->cmnd[0] = READ_16; + } + + blkvsc_req->cmnd[1] |= blk_fua_rq(blkvsc_req->req) ? 0x8 : 0; + + *(unsigned long long *)&blkvsc_req->cmnd[2] = + cpu_to_be64(blkvsc_req->sector_start); + *(unsigned int *)&blkvsc_req->cmnd[10] = + cpu_to_be32(blkvsc_req->sector_count); + } else if ((blkvsc_req->sector_count > 0xff) || + (blkvsc_req->sector_start > 0x1fffff)) { + if (rq_data_dir(blkvsc_req->req)) { + blkvsc_req->write = 1; + blkvsc_req->cmnd[0] = WRITE_10; + } else { + blkvsc_req->write = 0; + blkvsc_req->cmnd[0] = READ_10; + } + + blkvsc_req->cmnd[1] |= blk_fua_rq(blkvsc_req->req) ? 0x8 : 0; + + *(unsigned int *)&blkvsc_req->cmnd[2] = + cpu_to_be32(blkvsc_req->sector_start); + *(unsigned short *)&blkvsc_req->cmnd[7] = + cpu_to_be16(blkvsc_req->sector_count); + } else { + if (rq_data_dir(blkvsc_req->req)) { + blkvsc_req->write = 1; + blkvsc_req->cmnd[0] = WRITE_6; + } else { + blkvsc_req->write = 0; + blkvsc_req->cmnd[0] = READ_6; + } + + *(unsigned int *)&blkvsc_req->cmnd[1] = + cpu_to_be32(blkvsc_req->sector_start) >> 8; + blkvsc_req->cmnd[1] &= 0x1f; + blkvsc_req->cmnd[4] = (unsigned char)blkvsc_req->sector_count; + } +} + +static int blkvsc_submit_request(struct blkvsc_request *blkvsc_req, + void (*request_completion)(struct hv_storvsc_request *)) +{ + struct block_device_context *blkdev = blkvsc_req->dev; + struct device_context *device_ctx = blkdev->device_ctx; + struct driver_context *driver_ctx = + driver_to_driver_context(device_ctx->device.driver); + struct blkvsc_driver_context *blkvsc_drv_ctx = + (struct blkvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &blkvsc_drv_ctx->drv_obj; + struct hv_storvsc_request *storvsc_req; + int ret; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_submit_request() - " + "req %p type %s start_sector %lu count %ld offset %d " + "len %d\n", blkvsc_req, + (blkvsc_req->write) ? "WRITE" : "READ", + (unsigned long) blkvsc_req->sector_start, + blkvsc_req->sector_count, + blkvsc_req->request.DataBuffer.Offset, + blkvsc_req->request.DataBuffer.Length); +#if 0 + for (i = 0; i < (blkvsc_req->request.DataBuffer.Length >> 12); i++) { + DPRINT_DBG(BLKVSC_DRV, "blkvsc_submit_request() - " + "req %p pfn[%d] %llx\n", + blkvsc_req, i, + blkvsc_req->request.DataBuffer.PfnArray[i]); + } +#endif + + storvsc_req = &blkvsc_req->request; + storvsc_req->Extension = (void *)((unsigned long)blkvsc_req + + sizeof(struct blkvsc_request)); + + storvsc_req->Type = blkvsc_req->write ? WRITE_TYPE : READ_TYPE; + + storvsc_req->OnIOCompletion = request_completion; + storvsc_req->Context = blkvsc_req; + + storvsc_req->Host = blkdev->port; + storvsc_req->Bus = blkdev->path; + storvsc_req->TargetId = blkdev->target; + storvsc_req->LunId = 0; /* this is not really used at all */ + + storvsc_req->CdbLen = blkvsc_req->cmd_len; + storvsc_req->Cdb = blkvsc_req->cmnd; + + storvsc_req->SenseBuffer = blkvsc_req->sense_buffer; + storvsc_req->SenseBufferSize = SCSI_SENSE_BUFFERSIZE; + + ret = storvsc_drv_obj->OnIORequest(&blkdev->device_ctx->device_obj, + &blkvsc_req->request); + if (ret == 0) + blkdev->num_outstanding_reqs++; + + return ret; +} + +/* + * We break the request into 1 or more blkvsc_requests and submit + * them. If we cant submit them all, we put them on the + * pending_list. The blkvsc_request() will work on the pending_list. + */ +static int blkvsc_do_request(struct block_device_context *blkdev, + struct request *req) +{ + struct bio *bio = NULL; + struct bio_vec *bvec = NULL; + struct bio_vec *prev_bvec = NULL; + struct blkvsc_request *blkvsc_req = NULL; + struct blkvsc_request *tmp; + int databuf_idx = 0; + int seg_idx = 0; + sector_t start_sector; + unsigned long num_sectors = 0; + int ret = 0; + int pending = 0; + struct blkvsc_request_group *group = NULL; + + DPRINT_DBG(BLKVSC_DRV, "blkdev %p req %p sect %lu \n", blkdev, req, + (unsigned long)blk_rq_pos(req)); + + /* Create a group to tie req to list of blkvsc_reqs */ + group = kmem_cache_alloc(blkdev->request_pool, GFP_ATOMIC); + if (!group) + return -ENOMEM; + + INIT_LIST_HEAD(&group->blkvsc_req_list); + group->outstanding = group->status = 0; + + start_sector = blk_rq_pos(req); + + /* foreach bio in the request */ + if (req->bio) { + for (bio = req->bio; bio; bio = bio->bi_next) { + /* + * Map this bio into an existing or new storvsc request + */ + bio_for_each_segment(bvec, bio, seg_idx) { + DPRINT_DBG(BLKVSC_DRV, "bio_for_each_segment() " + "- req %p bio %p bvec %p seg_idx %d " + "databuf_idx %d\n", req, bio, bvec, + seg_idx, databuf_idx); + + /* Get a new storvsc request */ + /* 1st-time */ + if ((!blkvsc_req) || + (databuf_idx >= MAX_MULTIPAGE_BUFFER_COUNT) + /* hole at the begin of page */ + || (bvec->bv_offset != 0) || + /* hold at the end of page */ + (prev_bvec && + (prev_bvec->bv_len != PAGE_SIZE))) { + /* submit the prev one */ + if (blkvsc_req) { + blkvsc_req->sector_start = start_sector; + sector_div(blkvsc_req->sector_start, (blkdev->sector_size >> 9)); + + blkvsc_req->sector_count = num_sectors / (blkdev->sector_size >> 9); + blkvsc_init_rw(blkvsc_req); + } + + /* + * Create new blkvsc_req to represent + * the current bvec + */ + blkvsc_req = kmem_cache_alloc(blkdev->request_pool, GFP_ATOMIC); + if (!blkvsc_req) { + /* free up everything */ + list_for_each_entry_safe( + blkvsc_req, tmp, + &group->blkvsc_req_list, + req_entry) { + list_del(&blkvsc_req->req_entry); + kmem_cache_free(blkdev->request_pool, blkvsc_req); + } + + kmem_cache_free(blkdev->request_pool, group); + return -ENOMEM; + } + + memset(blkvsc_req, 0, + sizeof(struct blkvsc_request)); + + blkvsc_req->dev = blkdev; + blkvsc_req->req = req; + blkvsc_req->request.DataBuffer.Offset = bvec->bv_offset; + blkvsc_req->request.DataBuffer.Length = 0; + + /* Add to the group */ + blkvsc_req->group = group; + blkvsc_req->group->outstanding++; + list_add_tail(&blkvsc_req->req_entry, + &blkvsc_req->group->blkvsc_req_list); + + start_sector += num_sectors; + num_sectors = 0; + databuf_idx = 0; + } + + /* Add the curr bvec/segment to the curr blkvsc_req */ + blkvsc_req->request.DataBuffer.PfnArray[databuf_idx] = page_to_pfn(bvec->bv_page); + blkvsc_req->request.DataBuffer.Length += bvec->bv_len; + + prev_bvec = bvec; + + databuf_idx++; + num_sectors += bvec->bv_len >> 9; + + } /* bio_for_each_segment */ + + } /* rq_for_each_bio */ + } + + /* Handle the last one */ + if (blkvsc_req) { + DPRINT_DBG(BLKVSC_DRV, "blkdev %p req %p group %p count %d\n", + blkdev, req, blkvsc_req->group, + blkvsc_req->group->outstanding); + + blkvsc_req->sector_start = start_sector; + sector_div(blkvsc_req->sector_start, + (blkdev->sector_size >> 9)); + + blkvsc_req->sector_count = num_sectors / + (blkdev->sector_size >> 9); + + blkvsc_init_rw(blkvsc_req); + } + + list_for_each_entry(blkvsc_req, &group->blkvsc_req_list, req_entry) { + if (pending) { + DPRINT_DBG(BLKVSC_DRV, "adding blkvsc_req to " + "pending_list - blkvsc_req %p start_sect %lu" + " sect_count %ld (%lu %ld)\n", blkvsc_req, + (unsigned long)blkvsc_req->sector_start, + blkvsc_req->sector_count, + (unsigned long)start_sector, + (unsigned long)num_sectors); + + list_add_tail(&blkvsc_req->pend_entry, + &blkdev->pending_list); + } else { + ret = blkvsc_submit_request(blkvsc_req, + blkvsc_request_completion); + if (ret == -1) { + pending = 1; + list_add_tail(&blkvsc_req->pend_entry, + &blkdev->pending_list); + } + + DPRINT_DBG(BLKVSC_DRV, "submitted blkvsc_req %p " + "start_sect %lu sect_count %ld (%lu %ld) " + "ret %d\n", blkvsc_req, + (unsigned long)blkvsc_req->sector_start, + blkvsc_req->sector_count, + (unsigned long)start_sector, + num_sectors, ret); + } + } + + return pending; +} + +static void blkvsc_cmd_completion(struct hv_storvsc_request *request) +{ + struct blkvsc_request *blkvsc_req = + (struct blkvsc_request *)request->Context; + struct block_device_context *blkdev = + (struct block_device_context *)blkvsc_req->dev; + struct scsi_sense_hdr sense_hdr; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_cmd_completion() - req %p\n", + blkvsc_req); + + blkdev->num_outstanding_reqs--; + + if (blkvsc_req->request.Status) + if (scsi_normalize_sense(blkvsc_req->sense_buffer, + SCSI_SENSE_BUFFERSIZE, &sense_hdr)) + scsi_print_sense_hdr("blkvsc", &sense_hdr); + + blkvsc_req->cond = 1; + wake_up_interruptible(&blkvsc_req->wevent); +} + +static void blkvsc_request_completion(struct hv_storvsc_request *request) +{ + struct blkvsc_request *blkvsc_req = + (struct blkvsc_request *)request->Context; + struct block_device_context *blkdev = + (struct block_device_context *)blkvsc_req->dev; + unsigned long flags; + struct blkvsc_request *comp_req, *tmp; + + ASSERT(blkvsc_req->group); + + DPRINT_DBG(BLKVSC_DRV, "blkdev %p blkvsc_req %p group %p type %s " + "sect_start %lu sect_count %ld len %d group outstd %d " + "total outstd %d\n", + blkdev, blkvsc_req, blkvsc_req->group, + (blkvsc_req->write) ? "WRITE" : "READ", + (unsigned long)blkvsc_req->sector_start, + blkvsc_req->sector_count, + blkvsc_req->request.DataBuffer.Length, + blkvsc_req->group->outstanding, + blkdev->num_outstanding_reqs); + + spin_lock_irqsave(&blkdev->lock, flags); + + blkdev->num_outstanding_reqs--; + blkvsc_req->group->outstanding--; + + /* + * Only start processing when all the blkvsc_reqs are + * completed. This guarantees no out-of-order blkvsc_req + * completion when calling end_that_request_first() + */ + if (blkvsc_req->group->outstanding == 0) { + list_for_each_entry_safe(comp_req, tmp, + &blkvsc_req->group->blkvsc_req_list, + req_entry) { + DPRINT_DBG(BLKVSC_DRV, "completing blkvsc_req %p " + "sect_start %lu sect_count %ld \n", + comp_req, + (unsigned long)comp_req->sector_start, + comp_req->sector_count); + + list_del(&comp_req->req_entry); + + if (!__blk_end_request(comp_req->req, + (!comp_req->request.Status ? 0 : -EIO), + comp_req->sector_count * blkdev->sector_size)) { + /* + * All the sectors have been xferred ie the + * request is done + */ + DPRINT_DBG(BLKVSC_DRV, "req %p COMPLETED\n", + comp_req->req); + kmem_cache_free(blkdev->request_pool, + comp_req->group); + } + + kmem_cache_free(blkdev->request_pool, comp_req); + } + + if (!blkdev->shutting_down) { + blkvsc_do_pending_reqs(blkdev); + blk_start_queue(blkdev->gd->queue); + blkvsc_request(blkdev->gd->queue); + } + } + + spin_unlock_irqrestore(&blkdev->lock, flags); +} + +static int blkvsc_cancel_pending_reqs(struct block_device_context *blkdev) +{ + struct blkvsc_request *pend_req, *tmp; + struct blkvsc_request *comp_req, *tmp2; + + int ret = 0; + + DPRINT_DBG(BLKVSC_DRV, "blkvsc_cancel_pending_reqs()"); + + /* Flush the pending list first */ + list_for_each_entry_safe(pend_req, tmp, &blkdev->pending_list, + pend_entry) { + /* + * The pend_req could be part of a partially completed + * request. If so, complete those req first until we + * hit the pend_req + */ + list_for_each_entry_safe(comp_req, tmp2, + &pend_req->group->blkvsc_req_list, + req_entry) { + DPRINT_DBG(BLKVSC_DRV, "completing blkvsc_req %p " + "sect_start %lu sect_count %ld \n", + comp_req, + (unsigned long) comp_req->sector_start, + comp_req->sector_count); + + if (comp_req == pend_req) + break; + + list_del(&comp_req->req_entry); + + if (comp_req->req) { + ret = __blk_end_request(comp_req->req, + (!comp_req->request.Status ? 0 : -EIO), + comp_req->sector_count * + blkdev->sector_size); + ASSERT(ret != 0); + } + + kmem_cache_free(blkdev->request_pool, comp_req); + } + + DPRINT_DBG(BLKVSC_DRV, "cancelling pending request - %p\n", + pend_req); + + list_del(&pend_req->pend_entry); + + list_del(&pend_req->req_entry); + + if (comp_req->req) { + if (!__blk_end_request(pend_req->req, -EIO, + pend_req->sector_count * + blkdev->sector_size)) { + /* + * All the sectors have been xferred ie the + * request is done + */ + DPRINT_DBG(BLKVSC_DRV, + "blkvsc_cancel_pending_reqs() - " + "req %p COMPLETED\n", pend_req->req); + kmem_cache_free(blkdev->request_pool, + pend_req->group); + } + } + + kmem_cache_free(blkdev->request_pool, pend_req); + } + + return ret; +} + +static int blkvsc_do_pending_reqs(struct block_device_context *blkdev) +{ + struct blkvsc_request *pend_req, *tmp; + int ret = 0; + + /* Flush the pending list first */ + list_for_each_entry_safe(pend_req, tmp, &blkdev->pending_list, + pend_entry) { + DPRINT_DBG(BLKVSC_DRV, "working off pending_list - %p\n", + pend_req); + + ret = blkvsc_submit_request(pend_req, + blkvsc_request_completion); + if (ret != 0) + break; + else + list_del(&pend_req->pend_entry); + } + + return ret; +} + +static void blkvsc_request(struct request_queue *queue) +{ + struct block_device_context *blkdev = NULL; + struct request *req; + int ret = 0; + + DPRINT_DBG(BLKVSC_DRV, "- enter \n"); + while ((req = blk_peek_request(queue)) != NULL) { + DPRINT_DBG(BLKVSC_DRV, "- req %p\n", req); + + blkdev = req->rq_disk->private_data; + if (blkdev->shutting_down || !blk_fs_request(req) || + blkdev->media_not_present) { + __blk_end_request_cur(req, 0); + continue; + } + + ret = blkvsc_do_pending_reqs(blkdev); + + if (ret != 0) { + DPRINT_DBG(BLKVSC_DRV, + "- stop queue - pending_list not empty\n"); + blk_stop_queue(queue); + break; + } + + blk_start_request(req); + + ret = blkvsc_do_request(blkdev, req); + if (ret > 0) { + DPRINT_DBG(BLKVSC_DRV, "- stop queue - no room\n"); + blk_stop_queue(queue); + break; + } else if (ret < 0) { + DPRINT_DBG(BLKVSC_DRV, "- stop queue - no mem\n"); + blk_requeue_request(queue, req); + blk_stop_queue(queue); + break; + } + } +} + +static int blkvsc_open(struct block_device *bdev, fmode_t mode) +{ + struct block_device_context *blkdev = bdev->bd_disk->private_data; + + DPRINT_DBG(BLKVSC_DRV, "- users %d disk %s\n", blkdev->users, + blkdev->gd->disk_name); + + spin_lock(&blkdev->lock); + + if (!blkdev->users && blkdev->device_type == DVD_TYPE) { + spin_unlock(&blkdev->lock); + check_disk_change(bdev); + spin_lock(&blkdev->lock); + } + + blkdev->users++; + + spin_unlock(&blkdev->lock); + return 0; +} + +static int blkvsc_release(struct gendisk *disk, fmode_t mode) +{ + struct block_device_context *blkdev = disk->private_data; + + DPRINT_DBG(BLKVSC_DRV, "- users %d disk %s\n", blkdev->users, + blkdev->gd->disk_name); + + spin_lock(&blkdev->lock); + if (blkdev->users == 1) { + spin_unlock(&blkdev->lock); + blkvsc_do_flush(blkdev); + spin_lock(&blkdev->lock); + } + + blkdev->users--; + + spin_unlock(&blkdev->lock); + return 0; +} + +static int blkvsc_media_changed(struct gendisk *gd) +{ + DPRINT_DBG(BLKVSC_DRV, "- enter\n"); + return 1; +} + +static int blkvsc_revalidate_disk(struct gendisk *gd) +{ + struct block_device_context *blkdev = gd->private_data; + + DPRINT_DBG(BLKVSC_DRV, "- enter\n"); + + if (blkdev->device_type == DVD_TYPE) { + blkvsc_do_read_capacity(blkdev); + set_capacity(blkdev->gd, blkdev->capacity * + (blkdev->sector_size/512)); + blk_queue_logical_block_size(gd->queue, blkdev->sector_size); + } + return 0; +} + +static int blkvsc_getgeo(struct block_device *bd, struct hd_geometry *hg) +{ + sector_t total_sectors = get_capacity(bd->bd_disk); + sector_t cylinder_times_heads = 0; + sector_t temp = 0; + + int sectors_per_track = 0; + int heads = 0; + int cylinders = 0; + int rem = 0; + + if (total_sectors > (65535 * 16 * 255)) + total_sectors = (65535 * 16 * 255); + + if (total_sectors >= (65535 * 16 * 63)) { + sectors_per_track = 255; + heads = 16; + + cylinder_times_heads = total_sectors; + /* sector_div stores the quotient in cylinder_times_heads */ + rem = sector_div(cylinder_times_heads, sectors_per_track); + } else { + sectors_per_track = 17; + + cylinder_times_heads = total_sectors; + /* sector_div stores the quotient in cylinder_times_heads */ + rem = sector_div(cylinder_times_heads, sectors_per_track); + + temp = cylinder_times_heads + 1023; + /* sector_div stores the quotient in temp */ + rem = sector_div(temp, 1024); + + heads = temp; + + if (heads < 4) + heads = 4; + + + if (cylinder_times_heads >= (heads * 1024) || (heads > 16)) { + sectors_per_track = 31; + heads = 16; + + cylinder_times_heads = total_sectors; + /* + * sector_div stores the quotient in + * cylinder_times_heads + */ + rem = sector_div(cylinder_times_heads, + sectors_per_track); + } + + if (cylinder_times_heads >= (heads * 1024)) { + sectors_per_track = 63; + heads = 16; + + cylinder_times_heads = total_sectors; + /* + * sector_div stores the quotient in + * cylinder_times_heads + */ + rem = sector_div(cylinder_times_heads, + sectors_per_track); + } + } + + temp = cylinder_times_heads; + /* sector_div stores the quotient in temp */ + rem = sector_div(temp, heads); + cylinders = temp; + + hg->heads = heads; + hg->sectors = sectors_per_track; + hg->cylinders = cylinders; + + DPRINT_INFO(BLKVSC_DRV, "CHS (%d, %d, %d)", cylinders, heads, + sectors_per_track); + + return 0; +} + +static int blkvsc_ioctl(struct block_device *bd, fmode_t mode, + unsigned cmd, unsigned long argument) +{ +/* struct block_device_context *blkdev = bd->bd_disk->private_data; */ + int ret; + + switch (cmd) { + /* + * TODO: I think there is certain format for HDIO_GET_IDENTITY rather + * than just a GUID. Commented it out for now. + */ +#if 0 + case HDIO_GET_IDENTITY: + DPRINT_INFO(BLKVSC_DRV, "HDIO_GET_IDENTITY\n"); + if (copy_to_user((void __user *)arg, blkdev->device_id, + blkdev->device_id_len)) + ret = -EFAULT; + break; +#endif + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int __init blkvsc_init(void) +{ + int ret; + + ASSERT(sizeof(sector_t) == 8); /* Make sure CONFIG_LBD is set */ + + DPRINT_ENTER(BLKVSC_DRV); + + DPRINT_INFO(BLKVSC_DRV, "Blkvsc initializing...."); + + ret = blkvsc_drv_init(BlkVscInitialize); + + DPRINT_EXIT(BLKVSC_DRV); + + return ret; +} + +static void __exit blkvsc_exit(void) +{ + DPRINT_ENTER(BLKVSC_DRV); + blkvsc_drv_exit(); + DPRINT_ENTER(BLKVSC_DRV); +} + +MODULE_LICENSE("GPL"); +module_param(blkvsc_ringbuffer_size, int, S_IRUGO); +module_init(blkvsc_init); +module_exit(blkvsc_exit); diff --git a/drivers/staging/hv/hv_api.h b/drivers/staging/hv/hv_api.h new file mode 100644 index 00000000000..251e2d15533 --- /dev/null +++ b/drivers/staging/hv/hv_api.h @@ -0,0 +1,905 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ +#ifndef __HV_API_H +#define __HV_API_H + + +/* Status codes for hypervisor operations. */ + +/* + * HV_STATUS_SUCCESS + * The specified hypercall succeeded + */ +#define HV_STATUS_SUCCESS ((u16)0x0000) + +/* + * HV_STATUS_INVALID_HYPERCALL_CODE + * The hypervisor does not support the operation because the specified + * hypercall code is not supported. + */ +#define HV_STATUS_INVALID_HYPERCALL_CODE ((u16)0x0002) + +/* + * HV_STATUS_INVALID_HYPERCALL_INPUT + * The hypervisor does not support the operation because the encoding for the + * hypercall input register is not supported. + */ +#define HV_STATUS_INVALID_HYPERCALL_INPUT ((u16)0x0003) + +/* + * HV_STATUS_INVALID_ALIGNMENT + * The hypervisor could not perform the operation beacuse a parameter has an + * invalid alignment. + */ +#define HV_STATUS_INVALID_ALIGNMENT ((u16)0x0004) + +/* + * HV_STATUS_INVALID_PARAMETER + * The hypervisor could not perform the operation beacuse an invalid parameter + * was specified. + */ +#define HV_STATUS_INVALID_PARAMETER ((u16)0x0005) + +/* + * HV_STATUS_ACCESS_DENIED + * Access to the specified object was denied. + */ +#define HV_STATUS_ACCESS_DENIED ((u16)0x0006) + +/* + * HV_STATUS_INVALID_PARTITION_STATE + * The hypervisor could not perform the operation because the partition is + * entering or in an invalid state. + */ +#define HV_STATUS_INVALID_PARTITION_STATE ((u16)0x0007) + +/* + * HV_STATUS_OPERATION_DENIED + * The operation is not allowed in the current state. + */ +#define HV_STATUS_OPERATION_DENIED ((u16)0x0008) + +/* + * HV_STATUS_UNKNOWN_PROPERTY + * The hypervisor does not recognize the specified partition property. + */ +#define HV_STATUS_UNKNOWN_PROPERTY ((u16)0x0009) + +/* + * HV_STATUS_PROPERTY_VALUE_OUT_OF_RANGE + * The specified value of a partition property is out of range or violates an + * invariant. + */ +#define HV_STATUS_PROPERTY_VALUE_OUT_OF_RANGE ((u16)0x000A) + +/* + * HV_STATUS_INSUFFICIENT_MEMORY + * There is not enough memory in the hypervisor pool to complete the operation. + */ +#define HV_STATUS_INSUFFICIENT_MEMORY ((u16)0x000B) + +/* + * HV_STATUS_PARTITION_TOO_DEEP + * The maximum partition depth has been exceeded for the partition hierarchy. + */ +#define HV_STATUS_PARTITION_TOO_DEEP ((u16)0x000C) + +/* + * HV_STATUS_INVALID_PARTITION_ID + * A partition with the specified partition Id does not exist. + */ +#define HV_STATUS_INVALID_PARTITION_ID ((u16)0x000D) + +/* + * HV_STATUS_INVALID_VP_INDEX + * The hypervisor could not perform the operation because the specified VP + * index is invalid. + */ +#define HV_STATUS_INVALID_VP_INDEX ((u16)0x000E) + +/* + * HV_STATUS_NOT_FOUND + * The iteration is complete; no addition items in the iteration could be + * found. + */ +#define HV_STATUS_NOT_FOUND ((u16)0x0010) + +/* + * HV_STATUS_INVALID_PORT_ID + * The hypervisor could not perform the operation because the specified port + * identifier is invalid. + */ +#define HV_STATUS_INVALID_PORT_ID ((u16)0x0011) + +/* + * HV_STATUS_INVALID_CONNECTION_ID + * The hypervisor could not perform the operation because the specified + * connection identifier is invalid. + */ +#define HV_STATUS_INVALID_CONNECTION_ID ((u16)0x0012) + +/* + * HV_STATUS_INSUFFICIENT_BUFFERS + * You did not supply enough message buffers to send a message. + */ +#define HV_STATUS_INSUFFICIENT_BUFFERS ((u16)0x0013) + +/* + * HV_STATUS_NOT_ACKNOWLEDGED + * The previous virtual interrupt has not been acknowledged. + */ +#define HV_STATUS_NOT_ACKNOWLEDGED ((u16)0x0014) + +/* + * HV_STATUS_INVALID_VP_STATE + * A virtual processor is not in the correct state for the performance of the + * indicated operation. + */ +#define HV_STATUS_INVALID_VP_STATE ((u16)0x0015) + +/* + * HV_STATUS_ACKNOWLEDGED + * The previous virtual interrupt has already been acknowledged. + */ +#define HV_STATUS_ACKNOWLEDGED ((u16)0x0016) + +/* + * HV_STATUS_INVALID_SAVE_RESTORE_STATE + * The indicated partition is not in a valid state for saving or restoring. + */ +#define HV_STATUS_INVALID_SAVE_RESTORE_STATE ((u16)0x0017) + +/* + * HV_STATUS_INVALID_SYNIC_STATE + * The hypervisor could not complete the operation because a required feature + * of the synthetic interrupt controller (SynIC) was disabled. + */ +#define HV_STATUS_INVALID_SYNIC_STATE ((u16)0x0018) + +/* + * HV_STATUS_OBJECT_IN_USE + * The hypervisor could not perform the operation because the object or value + * was either already in use or being used for a purpose that would not permit + * completing the operation. + */ +#define HV_STATUS_OBJECT_IN_USE ((u16)0x0019) + +/* + * HV_STATUS_INVALID_PROXIMITY_DOMAIN_INFO + * The proximity domain information is invalid. + */ +#define HV_STATUS_INVALID_PROXIMITY_DOMAIN_INFO ((u16)0x001A) + +/* + * HV_STATUS_NO_DATA + * An attempt to retrieve debugging data failed because none was available. + */ +#define HV_STATUS_NO_DATA ((u16)0x001B) + +/* + * HV_STATUS_INACTIVE + * The physical connection being used for debuggging has not recorded any + * receive activity since the last operation. + */ +#define HV_STATUS_INACTIVE ((u16)0x001C) + +/* + * HV_STATUS_NO_RESOURCES + * There are not enough resources to complete the operation. + */ +#define HV_STATUS_NO_RESOURCES ((u16)0x001D) + +/* + * HV_STATUS_FEATURE_UNAVAILABLE + * A hypervisor feature is not available to the user. + */ +#define HV_STATUS_FEATURE_UNAVAILABLE ((u16)0x001E) + +/* + * HV_STATUS_UNSUCCESSFUL + * {Operation Failed} The requested operation was unsuccessful. + */ +#define HV_STATUS_UNSUCCESSFUL ((u16)0x1001) + +/* + * HV_STATUS_INSUFFICIENT_BUFFER + * The specified buffer was too small to contain all of the requested data. + */ +#define HV_STATUS_INSUFFICIENT_BUFFER ((u16)0x1002) + +/* + * HV_STATUS_GPA_NOT_PRESENT + * The guest physical address is not currently associated with a system + * physical address. + */ +#define HV_STATUS_GPA_NOT_PRESENT ((u16)0x1003) + +/* + * HV_STATUS_GUEST_PAGE_FAULT + * The operation would have resulted in a page fault in the guest. + */ +#define HV_STATUS_GUEST_PAGE_FAULT ((u16)0x1004) + +/* + * HV_STATUS_RUNDOWN_DISABLED + * The operation cannot proceed as the rundown object was marked disabled. + */ +#define HV_STATUS_RUNDOWN_DISABLED ((u16)0x1005) + +/* + * HV_STATUS_KEY_ALREADY_EXISTS + * The entry cannot be added as another entry with the same key already exists. + */ +#define HV_STATUS_KEY_ALREADY_EXISTS ((u16)0x1006) + +/* + * HV_STATUS_GPA_INTERCEPT + * The operation resulted an intercept on a region of guest physical memory. + */ +#define HV_STATUS_GPA_INTERCEPT ((u16)0x1007) + +/* + * HV_STATUS_GUEST_GENERAL_PROTECTION_FAULT + * The operation would have resulted in a general protection fault in the + * guest. + */ +#define HV_STATUS_GUEST_GENERAL_PROTECTION_FAULT ((u16)0x1008) + +/* + * HV_STATUS_GUEST_STACK_FAULT + * The operation would have resulted in a stack fault in the guest. + */ +#define HV_STATUS_GUEST_STACK_FAULT ((u16)0x1009) + +/* + * HV_STATUS_GUEST_INVALID_OPCODE_FAULT + * The operation would have resulted in an invalid opcode fault in the guest. + */ +#define HV_STATUS_GUEST_INVALID_OPCODE_FAULT ((u16)0x100A) + +/* + * HV_STATUS_FINALIZE_INCOMPLETE + * The partition is not completely finalized. + */ +#define HV_STATUS_FINALIZE_INCOMPLETE ((u16)0x100B) + +/* + * HV_STATUS_GUEST_MACHINE_CHECK_ABORT + * The operation would have resulted in an machine check abort in the guest. + */ +#define HV_STATUS_GUEST_MACHINE_CHECK_ABORT ((u16)0x100C) + +/* + * HV_STATUS_ILLEGAL_OVERLAY_ACCESS + * An illegal access was attempted to an overlay page. + */ +#define HV_STATUS_ILLEGAL_OVERLAY_ACCESS ((u16)0x100D) + +/* + * HV_STATUS_INSUFFICIENT_SYSTEM_VA + * There is not enough system VA space available to satisfy the request, + */ +#define HV_STATUS_INSUFFICIENT_SYSTEM_VA ((u16)0x100E) + +/* + * HV_STATUS_VIRTUAL_ADDRESS_NOT_MAPPED + * The passed virtual address was not mapped in the hypervisor address space. + */ +#define HV_STATUS_VIRTUAL_ADDRESS_NOT_MAPPED ((u16)0x100F) + +/* + * HV_STATUS_NOT_IMPLEMENTED + * The requested operation is not implemented in this version of the + * hypervisor. + */ +#define HV_STATUS_NOT_IMPLEMENTED ((u16)0x1010) + +/* + * HV_STATUS_VMX_INSTRUCTION_FAILED + * The requested VMX instruction failed to complete succesfully. + */ +#define HV_STATUS_VMX_INSTRUCTION_FAILED ((u16)0x1011) + +/* + * HV_STATUS_VMX_INSTRUCTION_FAILED_WITH_STATUS + * The requested VMX instruction failed to complete succesfully indicating + * status. + */ +#define HV_STATUS_VMX_INSTRUCTION_FAILED_WITH_STATUS ((u16)0x1012) + +/* + * HV_STATUS_MSR_ACCESS_FAILED + * The requested access to the model specific register failed. + */ +#define HV_STATUS_MSR_ACCESS_FAILED ((u16)0x1013) + +/* + * HV_STATUS_CR_ACCESS_FAILED + * The requested access to the control register failed. + */ +#define HV_STATUS_CR_ACCESS_FAILED ((u16)0x1014) + +/* + * HV_STATUS_TIMEOUT + * The specified timeout expired before the operation completed. + */ +#define HV_STATUS_TIMEOUT ((u16)0x1016) + +/* + * HV_STATUS_MSR_INTERCEPT + * The requested access to the model specific register generated an intercept. + */ +#define HV_STATUS_MSR_INTERCEPT ((u16)0x1017) + +/* + * HV_STATUS_CPUID_INTERCEPT + * The CPUID instruction generated an intercept. + */ +#define HV_STATUS_CPUID_INTERCEPT ((u16)0x1018) + +/* + * HV_STATUS_REPEAT_INSTRUCTION + * The current instruction should be repeated and the instruction pointer not + * advanced. + */ +#define HV_STATUS_REPEAT_INSTRUCTION ((u16)0x1019) + +/* + * HV_STATUS_PAGE_PROTECTION_VIOLATION + * The current instruction should be repeated and the instruction pointer not + * advanced. + */ +#define HV_STATUS_PAGE_PROTECTION_VIOLATION ((u16)0x101A) + +/* + * HV_STATUS_PAGE_TABLE_INVALID + * The current instruction should be repeated and the instruction pointer not + * advanced. + */ +#define HV_STATUS_PAGE_TABLE_INVALID ((u16)0x101B) + +/* + * HV_STATUS_PAGE_NOT_PRESENT + * The current instruction should be repeated and the instruction pointer not + * advanced. + */ +#define HV_STATUS_PAGE_NOT_PRESENT ((u16)0x101C) + +/* + * HV_STATUS_IO_INTERCEPT + * The requested access to the I/O port generated an intercept. + */ +#define HV_STATUS_IO_INTERCEPT ((u16)0x101D) + +/* + * HV_STATUS_NOTHING_TO_DO + * There is nothing to do. + */ +#define HV_STATUS_NOTHING_TO_DO ((u16)0x101E) + +/* + * HV_STATUS_THREAD_TERMINATING + * The requested thread is terminating. + */ +#define HV_STATUS_THREAD_TERMINATING ((u16)0x101F) + +/* + * HV_STATUS_SECTION_ALREADY_CONSTRUCTED + * The specified section was already constructed. + */ +#define HV_STATUS_SECTION_ALREADY_CONSTRUCTED ((u16)0x1020) + +/* HV_STATUS_SECTION_NOT_ALREADY_CONSTRUCTED + * The specified section was not already constructed. + */ +#define HV_STATUS_SECTION_NOT_ALREADY_CONSTRUCTED ((u16)0x1021) + +/* + * HV_STATUS_PAGE_ALREADY_COMMITTED + * The specified virtual address was already backed by physical memory. + */ +#define HV_STATUS_PAGE_ALREADY_COMMITTED ((u16)0x1022) + +/* + * HV_STATUS_PAGE_NOT_ALREADY_COMMITTED + * The specified virtual address was not already backed by physical memory. + */ +#define HV_STATUS_PAGE_NOT_ALREADY_COMMITTED ((u16)0x1023) + +/* + * HV_STATUS_COMMITTED_PAGES_REMAIN + * Committed pages remain in the section. + */ +#define HV_STATUS_COMMITTED_PAGES_REMAIN ((u16)0x1024) + +/* + * HV_STATUS_NO_REMAINING_COMMITTED_PAGES + * No additional committed pages beyond the specified page exist in the + * section. + */ +#define HV_STATUS_NO_REMAINING_COMMITTED_PAGES ((u16)0x1025) + +/* + * HV_STATUS_INSUFFICIENT_COMPARTMENT_VA + * The VA space of the compartment is exhausted. + */ +#define HV_STATUS_INSUFFICIENT_COMPARTMENT_VA ((u16)0x1026) + +/* + * HV_STATUS_DEREF_SPA_LIST_FULL + * The SPA dereference list is full, and there are additional entries to be + * added to it. + */ +#define HV_STATUS_DEREF_SPA_LIST_FULL ((u16)0x1027) + +/* + * HV_STATUS_GPA_OUT_OF_RANGE + * The supplied GPA is out of range. + */ +#define HV_STATUS_GPA_OUT_OF_RANGE ((u16)0x1027) + +/* + * HV_STATUS_NONVOLATILE_XMM_STALE + * The XMM register that was being accessed is stale. + */ +#define HV_STATUS_NONVOLATILE_XMM_STALE ((u16)0x1028) + +/* HV_STATUS_UNSUPPORTED_PROCESSOR + * The hypervisor does not support the processors in this system. + */ +#define HV_STATUS_UNSUPPORTED_PROCESSOR ((u16)0x1029) + +/* + * HV_STATUS_INSUFFICIENT_CROM_SPACE + * Insufficient space existed for copying over the CROM contents. + */ +#define HV_STATUS_INSUFFICIENT_CROM_SPACE ((u16)0x2000) + +/* + * HV_STATUS_BAD_CROM_FORMAT + * The contents of the CROM failed validation attempts. + */ +#define HV_STATUS_BAD_CROM_FORMAT ((u16)0x2001) + +/* + * HV_STATUS_UNSUPPORTED_CROM_FORMAT + * The contents of the CROM contain contents the parser doesn't support. + */ +#define HV_STATUS_UNSUPPORTED_CROM_FORMAT ((u16)0x2002) + +/* + * HV_STATUS_UNSUPPORTED_CONTROLLER + * The register format of the OHCI controller specified for debugging is not + * supported. + */ +#define HV_STATUS_UNSUPPORTED_CONTROLLER ((u16)0x2003) + +/* + * HV_STATUS_CROM_TOO_LARGE + * The CROM contents were to large to copy over. + */ +#define HV_STATUS_CROM_TOO_LARGE ((u16)0x2004) + +/* + * HV_STATUS_CONTROLLER_IN_USE + * The OHCI controller specified for debugging cannot be used as it is already + * in use. + */ +#define HV_STATUS_CONTROLLER_IN_USE ((u16)0x2005) + + +/* + * The below CPUID leaves are present if VersionAndFeatures.HypervisorPresent + * is set by CPUID(HvCpuIdFunctionVersionAndFeatures). + */ +enum hv_cpuid_function { + HvCpuIdFunctionVersionAndFeatures = 0x00000001, + HvCpuIdFunctionHvVendorAndMaxFunction = 0x40000000, + HvCpuIdFunctionHvInterface = 0x40000001, + + /* + * The remaining functions depend on the value of + * HvCpuIdFunctionInterface + */ + HvCpuIdFunctionMsHvVersion = 0x40000002, + HvCpuIdFunctionMsHvFeatures = 0x40000003, + HvCpuIdFunctionMsHvEnlightenmentInformation = 0x40000004, + HvCpuIdFunctionMsHvImplementationLimits = 0x40000005, +}; + +/* Define the virtual APIC registers */ +#define HV_X64_MSR_EOI (0x40000070) +#define HV_X64_MSR_ICR (0x40000071) +#define HV_X64_MSR_TPR (0x40000072) +#define HV_X64_MSR_APIC_ASSIST_PAGE (0x40000073) + +/* Define version of the synthetic interrupt controller. */ +#define HV_SYNIC_VERSION (1) + +/* Define synthetic interrupt controller model specific registers. */ +#define HV_X64_MSR_SCONTROL (0x40000080) +#define HV_X64_MSR_SVERSION (0x40000081) +#define HV_X64_MSR_SIEFP (0x40000082) +#define HV_X64_MSR_SIMP (0x40000083) +#define HV_X64_MSR_EOM (0x40000084) +#define HV_X64_MSR_SINT0 (0x40000090) +#define HV_X64_MSR_SINT1 (0x40000091) +#define HV_X64_MSR_SINT2 (0x40000092) +#define HV_X64_MSR_SINT3 (0x40000093) +#define HV_X64_MSR_SINT4 (0x40000094) +#define HV_X64_MSR_SINT5 (0x40000095) +#define HV_X64_MSR_SINT6 (0x40000096) +#define HV_X64_MSR_SINT7 (0x40000097) +#define HV_X64_MSR_SINT8 (0x40000098) +#define HV_X64_MSR_SINT9 (0x40000099) +#define HV_X64_MSR_SINT10 (0x4000009A) +#define HV_X64_MSR_SINT11 (0x4000009B) +#define HV_X64_MSR_SINT12 (0x4000009C) +#define HV_X64_MSR_SINT13 (0x4000009D) +#define HV_X64_MSR_SINT14 (0x4000009E) +#define HV_X64_MSR_SINT15 (0x4000009F) + +/* Define the expected SynIC version. */ +#define HV_SYNIC_VERSION_1 (0x1) + +/* Define synthetic interrupt controller message constants. */ +#define HV_MESSAGE_SIZE (256) +#define HV_MESSAGE_PAYLOAD_BYTE_COUNT (240) +#define HV_MESSAGE_PAYLOAD_QWORD_COUNT (30) +#define HV_ANY_VP (0xFFFFFFFF) + +/* Define synthetic interrupt controller flag constants. */ +#define HV_EVENT_FLAGS_COUNT (256 * 8) +#define HV_EVENT_FLAGS_BYTE_COUNT (256) +#define HV_EVENT_FLAGS_DWORD_COUNT (256 / sizeof(u32)) + +/* Define hypervisor message types. */ +enum hv_message_type { + HvMessageTypeNone = 0x00000000, + + /* Memory access messages. */ + HvMessageTypeUnmappedGpa = 0x80000000, + HvMessageTypeGpaIntercept = 0x80000001, + + /* Timer notification messages. */ + HvMessageTimerExpired = 0x80000010, + + /* Error messages. */ + HvMessageTypeInvalidVpRegisterValue = 0x80000020, + HvMessageTypeUnrecoverableException = 0x80000021, + HvMessageTypeUnsupportedFeature = 0x80000022, + + /* Trace buffer complete messages. */ + HvMessageTypeEventLogBufferComplete = 0x80000040, + + /* Platform-specific processor intercept messages. */ + HvMessageTypeX64IoPortIntercept = 0x80010000, + HvMessageTypeX64MsrIntercept = 0x80010001, + HvMessageTypeX64CpuidIntercept = 0x80010002, + HvMessageTypeX64ExceptionIntercept = 0x80010003, + HvMessageTypeX64ApicEoi = 0x80010004, + HvMessageTypeX64LegacyFpError = 0x80010005 +}; + +/* Define the number of synthetic interrupt sources. */ +#define HV_SYNIC_SINT_COUNT (16) +#define HV_SYNIC_STIMER_COUNT (4) + +/* Define invalid partition identifier. */ +#define HV_PARTITION_ID_INVALID ((u64)0x0) + +/* Define connection identifier type. */ +union hv_connection_id { + u32 Asu32; + struct { + u32 Id:24; + u32 Reserved:8; + } u; +}; + +/* Define port identifier type. */ +union hv_port_id { + u32 Asu32; + struct { + u32 Id:24; + u32 Reserved:8; + } u ; +}; + +/* Define port type. */ +enum hv_port_type { + HvPortTypeMessage = 1, + HvPortTypeEvent = 2, + HvPortTypeMonitor = 3 +}; + +/* Define port information structure. */ +struct hv_port_info { + enum hv_port_type PortType; + u32 Padding; + union { + struct { + u32 TargetSint; + u32 TargetVp; + u64 RsvdZ; + } MessagePortInfo; + struct { + u32 TargetSint; + u32 TargetVp; + u16 BaseFlagNumber; + u16 FlagCount; + u32 RsvdZ; + } EventPortInfo; + struct { + u64 MonitorAddress; + u64 RsvdZ; + } MonitorPortInfo; + }; +}; + +struct hv_connection_info { + enum hv_port_type PortType; + u32 Padding; + union { + struct { + u64 RsvdZ; + } MessageConnectionInfo; + struct { + u64 RsvdZ; + } EventConnectionInfo; + struct { + u64 MonitorAddress; + } MonitorConnectionInfo; + }; +}; + +/* Define synthetic interrupt controller message flags. */ +union hv_message_flags { + u8 Asu8; + struct { + u8 MessagePending:1; + u8 Reserved:7; + }; +}; + +/* Define synthetic interrupt controller message header. */ +struct hv_message_header { + enum hv_message_type MessageType; + u8 PayloadSize; + union hv_message_flags MessageFlags; + u8 Reserved[2]; + union { + u64 Sender; + union hv_port_id Port; + }; +}; + +/* Define timer message payload structure. */ +struct hv_timer_message_payload { + u32 TimerIndex; + u32 Reserved; + u64 ExpirationTime; /* When the timer expired */ + u64 DeliveryTime; /* When the message was delivered */ +}; + +/* Define synthetic interrupt controller message format. */ +struct hv_message { + struct hv_message_header Header; + union { + u64 Payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT]; + } u ; +}; + +/* Define the number of message buffers associated with each port. */ +#define HV_PORT_MESSAGE_BUFFER_COUNT (16) + +/* Define the synthetic interrupt message page layout. */ +struct hv_message_page { + struct hv_message SintMessage[HV_SYNIC_SINT_COUNT]; +}; + +/* Define the synthetic interrupt controller event flags format. */ +union hv_synic_event_flags { + u8 Flags8[HV_EVENT_FLAGS_BYTE_COUNT]; + u32 Flags32[HV_EVENT_FLAGS_DWORD_COUNT]; +}; + +/* Define the synthetic interrupt flags page layout. */ +struct hv_synic_event_flags_page { + union hv_synic_event_flags SintEventFlags[HV_SYNIC_SINT_COUNT]; +}; + +/* Define SynIC control register. */ +union hv_synic_scontrol { + u64 AsUINT64; + struct { + u64 Enable:1; + u64 Reserved:63; + }; +}; + +/* Define synthetic interrupt source. */ +union hv_synic_sint { + u64 AsUINT64; + struct { + u64 Vector:8; + u64 Reserved1:8; + u64 Masked:1; + u64 AutoEoi:1; + u64 Reserved2:46; + }; +}; + +/* Define the format of the SIMP register */ +union hv_synic_simp { + u64 AsUINT64; + struct { + u64 SimpEnabled:1; + u64 Preserved:11; + u64 BaseSimpGpa:52; + }; +}; + +/* Define the format of the SIEFP register */ +union hv_synic_siefp { + u64 AsUINT64; + struct { + u64 SiefpEnabled:1; + u64 Preserved:11; + u64 BaseSiefpGpa:52; + }; +}; + +/* Definitions for the monitored notification facility */ +union hv_monitor_trigger_group { + u64 AsUINT64; + struct { + u32 Pending; + u32 Armed; + }; +}; + +struct hv_monitor_parameter { + union hv_connection_id ConnectionId; + u16 FlagNumber; + u16 RsvdZ; +}; + +union hv_monitor_trigger_state { + u32 Asu32; + + struct { + u32 GroupEnable:4; + u32 RsvdZ:28; + }; +}; + +/* struct hv_monitor_page Layout */ +/* ------------------------------------------------------ */ +/* | 0 | TriggerState (4 bytes) | Rsvd1 (4 bytes) | */ +/* | 8 | TriggerGroup[0] | */ +/* | 10 | TriggerGroup[1] | */ +/* | 18 | TriggerGroup[2] | */ +/* | 20 | TriggerGroup[3] | */ +/* | 28 | Rsvd2[0] | */ +/* | 30 | Rsvd2[1] | */ +/* | 38 | Rsvd2[2] | */ +/* | 40 | NextCheckTime[0][0] | NextCheckTime[0][1] | */ +/* | ... | */ +/* | 240 | Latency[0][0..3] | */ +/* | 340 | Rsvz3[0] | */ +/* | 440 | Parameter[0][0] | */ +/* | 448 | Parameter[0][1] | */ +/* | ... | */ +/* | 840 | Rsvd4[0] | */ +/* ------------------------------------------------------ */ +struct hv_monitor_page { + union hv_monitor_trigger_state TriggerState; + u32 RsvdZ1; + + union hv_monitor_trigger_group TriggerGroup[4]; + u64 RsvdZ2[3]; + + s32 NextCheckTime[4][32]; + + u16 Latency[4][32]; + u64 RsvdZ3[32]; + + struct hv_monitor_parameter Parameter[4][32]; + + u8 RsvdZ4[1984]; +}; + +/* Declare the various hypercall operations. */ +enum hv_call_code { + HvCallPostMessage = 0x005c, + HvCallSignalEvent = 0x005d, +}; + +/* Definition of the HvPostMessage hypercall input structure. */ +struct hv_input_post_message { + union hv_connection_id ConnectionId; + u32 Reserved; + enum hv_message_type MessageType; + u32 PayloadSize; + u64 Payload[HV_MESSAGE_PAYLOAD_QWORD_COUNT]; +}; + +/* Definition of the HvSignalEvent hypercall input structure. */ +struct hv_input_signal_event { + union hv_connection_id ConnectionId; + u16 FlagNumber; + u16 RsvdZ; +}; + +/* + * Versioning definitions used for guests reporting themselves to the + * hypervisor, and visa versa. + */ + +/* Version info reported by guest OS's */ +enum hv_guest_os_vendor { + HvGuestOsVendorMicrosoft = 0x0001 +}; + +enum hv_guest_os_microsoft_ids { + HvGuestOsMicrosoftUndefined = 0x00, + HvGuestOsMicrosoftMSDOS = 0x01, + HvGuestOsMicrosoftWindows3x = 0x02, + HvGuestOsMicrosoftWindows9x = 0x03, + HvGuestOsMicrosoftWindowsNT = 0x04, + HvGuestOsMicrosoftWindowsCE = 0x05 +}; + +/* + * Declare the MSR used to identify the guest OS. + */ +#define HV_X64_MSR_GUEST_OS_ID 0x40000000 + +union hv_x64_msr_guest_os_id_contents { + u64 AsUINT64; + struct { + u64 BuildNumber:16; + u64 ServiceVersion:8; /* Service Pack, etc. */ + u64 MinorVersion:8; + u64 MajorVersion:8; + u64 OsId:8; /* enum hv_guest_os_microsoft_ids (if Vendor=MS) */ + u64 VendorId:16; /* enum hv_guest_os_vendor */ + }; +}; + +/* + * Declare the MSR used to setup pages used to communicate with the hypervisor. + */ +#define HV_X64_MSR_HYPERCALL 0x40000001 + +union hv_x64_msr_hypercall_contents { + u64 AsUINT64; + struct { + u64 Enable:1; + u64 Reserved:11; + u64 GuestPhysicalAddress:52; + }; +}; + +#endif diff --git a/drivers/staging/hv/logging.h b/drivers/staging/hv/logging.h new file mode 100644 index 00000000000..9e55617bd67 --- /dev/null +++ b/drivers/staging/hv/logging.h @@ -0,0 +1,119 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _LOGGING_H_ +#define _LOGGING_H_ + +/* #include <linux/init.h> */ +/* #include <linux/module.h> */ + + +#define VMBUS 0x0001 +#define STORVSC 0x0002 +#define NETVSC 0x0004 +#define INPUTVSC 0x0008 +#define BLKVSC 0x0010 +#define VMBUS_DRV 0x0100 +#define STORVSC_DRV 0x0200 +#define NETVSC_DRV 0x0400 +#define INPUTVSC_DRV 0x0800 +#define BLKVSC_DRV 0x1000 + +#define ALL_MODULES (VMBUS |\ + STORVSC |\ + NETVSC |\ + INPUTVSC |\ + BLKVSC |\ + VMBUS_DRV |\ + STORVSC_DRV |\ + NETVSC_DRV |\ + INPUTVSC_DRV|\ + BLKVSC_DRV) + +/* Logging Level */ +#define ERROR_LVL 3 +#define WARNING_LVL 4 +#define INFO_LVL 6 +#define DEBUG_LVL 7 +#define DEBUG_LVL_ENTEREXIT 8 +#define DEBUG_RING_LVL 9 + +extern unsigned int vmbus_loglevel; + +#define ASSERT(expr) \ + if (!(expr)) { \ + printk(KERN_CRIT "Assertion failed! %s,%s,%s,line=%d\n", \ + #expr, __FILE__, __func__, __LINE__); \ + __asm__ __volatile__("int3"); \ + } + +#define DPRINT(mod, lvl, fmt, args...) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (lvl <= LOWORD(vmbus_loglevel))) \ + printk(KERN_DEBUG #mod": %s() " fmt "\n", __func__, ## args);\ + } while (0) + +#define DPRINT_DBG(mod, fmt, args...) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (DEBUG_LVL <= LOWORD(vmbus_loglevel))) \ + printk(KERN_DEBUG #mod": %s() " fmt "\n", __func__, ## args);\ + } while (0) + +#define DPRINT_INFO(mod, fmt, args...) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (INFO_LVL <= LOWORD(vmbus_loglevel))) \ + printk(KERN_INFO #mod": " fmt "\n", ## args);\ + } while (0) + +#define DPRINT_WARN(mod, fmt, args...) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (WARNING_LVL <= LOWORD(vmbus_loglevel))) \ + printk(KERN_WARNING #mod": WARNING! " fmt "\n", ## args);\ + } while (0) + +#define DPRINT_ERR(mod, fmt, args...) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (ERROR_LVL <= LOWORD(vmbus_loglevel))) \ + printk(KERN_ERR #mod": %s() ERROR!! " fmt "\n", \ + __func__, ## args);\ + } while (0) + +#ifdef DEBUG +#define DPRINT_ENTER(mod) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (DEBUG_LVL_ENTEREXIT <= LOWORD(vmbus_loglevel))) \ + printk(KERN_DEBUG "["#mod"]: %s() enter\n", __func__);\ + } while (0) + +#define DPRINT_EXIT(mod) do {\ + if ((mod & (HIWORD(vmbus_loglevel))) && \ + (DEBUG_LVL_ENTEREXIT <= LOWORD(vmbus_loglevel))) \ + printk(KERN_DEBUG "["#mod"]: %s() exit\n", __func__);\ + } while (0) +#else +#define DPRINT_ENTER(mod) +#define DPRINT_EXIT(mod) +#endif + +#endif /* _LOGGING_H_ */ diff --git a/drivers/staging/hv/netvsc_drv.c b/drivers/staging/hv/netvsc_drv.c new file mode 100644 index 00000000000..3192d50f725 --- /dev/null +++ b/drivers/staging/hv/netvsc_drv.c @@ -0,0 +1,618 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/highmem.h> +#include <linux/device.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/inetdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/in.h> +#include <net/arp.h> +#include <net/route.h> +#include <net/sock.h> +#include <net/pkt_sched.h> +#include "osd.h" +#include "logging.h" +#include "vmbus.h" +#include "NetVscApi.h" + +MODULE_LICENSE("GPL"); + +struct net_device_context { + /* point back to our device context */ + struct device_context *device_ctx; + struct net_device_stats stats; +}; + +struct netvsc_driver_context { + /* !! These must be the first 2 fields !! */ + /* Which is a bug FIXME! */ + struct driver_context drv_ctx; + struct netvsc_driver drv_obj; +}; + +static int netvsc_ringbuffer_size = NETVSC_DEVICE_RING_BUFFER_SIZE; + +/* The one and only one */ +static struct netvsc_driver_context g_netvsc_drv; + +static struct net_device_stats *netvsc_get_stats(struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + + return &net_device_ctx->stats; +} + +static void netvsc_set_multicast_list(struct net_device *net) +{ +} + +static int netvsc_open(struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct driver_context *driver_ctx = + driver_to_driver_context(net_device_ctx->device_ctx->device.driver); + struct netvsc_driver_context *net_drv_ctx = + (struct netvsc_driver_context *)driver_ctx; + struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; + struct hv_device *device_obj = &net_device_ctx->device_ctx->device_obj; + int ret = 0; + + DPRINT_ENTER(NETVSC_DRV); + + if (netif_carrier_ok(net)) { + memset(&net_device_ctx->stats, 0, + sizeof(struct net_device_stats)); + + /* Open up the device */ + ret = net_drv_obj->OnOpen(device_obj); + if (ret != 0) { + DPRINT_ERR(NETVSC_DRV, + "unable to open device (ret %d).", ret); + return ret; + } + + netif_start_queue(net); + } else { + DPRINT_ERR(NETVSC_DRV, "unable to open device...link is down."); + } + + DPRINT_EXIT(NETVSC_DRV); + return ret; +} + +static int netvsc_close(struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct driver_context *driver_ctx = + driver_to_driver_context(net_device_ctx->device_ctx->device.driver); + struct netvsc_driver_context *net_drv_ctx = + (struct netvsc_driver_context *)driver_ctx; + struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; + struct hv_device *device_obj = &net_device_ctx->device_ctx->device_obj; + int ret; + + DPRINT_ENTER(NETVSC_DRV); + + netif_stop_queue(net); + + ret = net_drv_obj->OnClose(device_obj); + if (ret != 0) + DPRINT_ERR(NETVSC_DRV, "unable to close device (ret %d).", ret); + + DPRINT_EXIT(NETVSC_DRV); + + return ret; +} + +static void netvsc_xmit_completion(void *context) +{ + struct hv_netvsc_packet *packet = (struct hv_netvsc_packet *)context; + struct sk_buff *skb = (struct sk_buff *) + (unsigned long)packet->Completion.Send.SendCompletionTid; + struct net_device *net; + + DPRINT_ENTER(NETVSC_DRV); + + kfree(packet); + + if (skb) { + net = skb->dev; + dev_kfree_skb_any(skb); + + if (netif_queue_stopped(net)) { + DPRINT_INFO(NETVSC_DRV, "net device (%p) waking up...", + net); + + netif_wake_queue(net); + } + } + + DPRINT_EXIT(NETVSC_DRV); +} + +static int netvsc_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct net_device_context *net_device_ctx = netdev_priv(net); + struct driver_context *driver_ctx = + driver_to_driver_context(net_device_ctx->device_ctx->device.driver); + struct netvsc_driver_context *net_drv_ctx = + (struct netvsc_driver_context *)driver_ctx; + struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; + struct hv_netvsc_packet *packet; + int i; + int ret; + int num_frags; + int retries = 0; + + DPRINT_ENTER(NETVSC_DRV); + + /* Support only 1 chain of frags */ + ASSERT(skb_shinfo(skb)->frag_list == NULL); + ASSERT(skb->dev == net); + + DPRINT_DBG(NETVSC_DRV, "xmit packet - len %d data_len %d", + skb->len, skb->data_len); + + /* Add 1 for skb->data and any additional ones requested */ + num_frags = skb_shinfo(skb)->nr_frags + 1 + + net_drv_obj->AdditionalRequestPageBufferCount; + + /* Allocate a netvsc packet based on # of frags. */ + packet = kzalloc(sizeof(struct hv_netvsc_packet) + + (num_frags * sizeof(struct hv_page_buffer)) + + net_drv_obj->RequestExtSize, GFP_ATOMIC); + if (!packet) { + DPRINT_ERR(NETVSC_DRV, "unable to allocate hv_netvsc_packet"); + return -1; + } + + packet->Extension = (void *)(unsigned long)packet + + sizeof(struct hv_netvsc_packet) + + (num_frags * sizeof(struct hv_page_buffer)); + + /* Setup the rndis header */ + packet->PageBufferCount = num_frags; + + /* TODO: Flush all write buffers/ memory fence ??? */ + /* wmb(); */ + + /* Initialize it from the skb */ + ASSERT(skb->data); + packet->TotalDataBufferLength = skb->len; + + /* + * Start filling in the page buffers starting at + * AdditionalRequestPageBufferCount offset + */ + packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Pfn = virt_to_phys(skb->data) >> PAGE_SHIFT; + packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Offset = (unsigned long)skb->data & (PAGE_SIZE - 1); + packet->PageBuffers[net_drv_obj->AdditionalRequestPageBufferCount].Length = skb->len - skb->data_len; + + ASSERT((skb->len - skb->data_len) <= PAGE_SIZE); + + for (i = net_drv_obj->AdditionalRequestPageBufferCount + 1; + i < num_frags; i++) { + packet->PageBuffers[i].Pfn = + page_to_pfn(skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].page); + packet->PageBuffers[i].Offset = + skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].page_offset; + packet->PageBuffers[i].Length = + skb_shinfo(skb)->frags[i-(net_drv_obj->AdditionalRequestPageBufferCount+1)].size; + } + + /* Set the completion routine */ + packet->Completion.Send.OnSendCompletion = netvsc_xmit_completion; + packet->Completion.Send.SendCompletionContext = packet; + packet->Completion.Send.SendCompletionTid = (unsigned long)skb; + +retry_send: + ret = net_drv_obj->OnSend(&net_device_ctx->device_ctx->device_obj, + packet); + + if (ret == 0) { + ret = NETDEV_TX_OK; + net_device_ctx->stats.tx_bytes += skb->len; + net_device_ctx->stats.tx_packets++; + } else { + retries++; + if (retries < 4) { + DPRINT_ERR(NETVSC_DRV, "unable to send..." + "retrying %d...", retries); + udelay(100); + goto retry_send; + } + + /* no more room or we are shutting down */ + DPRINT_ERR(NETVSC_DRV, "unable to send (%d)..." + "marking net device (%p) busy", ret, net); + DPRINT_INFO(NETVSC_DRV, "net device (%p) stopping", net); + + ret = NETDEV_TX_BUSY; + net_device_ctx->stats.tx_dropped++; + + netif_stop_queue(net); + + /* + * Null it since the caller will free it instead of the + * completion routine + */ + packet->Completion.Send.SendCompletionTid = 0; + + /* + * Release the resources since we will not get any send + * completion + */ + netvsc_xmit_completion((void *)packet); + } + + DPRINT_DBG(NETVSC_DRV, "# of xmits %lu total size %lu", + net_device_ctx->stats.tx_packets, + net_device_ctx->stats.tx_bytes); + + DPRINT_EXIT(NETVSC_DRV); + return ret; +} + +/** + * netvsc_linkstatus_callback - Link up/down notification + */ +static void netvsc_linkstatus_callback(struct hv_device *device_obj, + unsigned int status) +{ + struct device_context *device_ctx = to_device_context(device_obj); + struct net_device *net = dev_get_drvdata(&device_ctx->device); + + DPRINT_ENTER(NETVSC_DRV); + + if (!net) { + DPRINT_ERR(NETVSC_DRV, "got link status but net device " + "not initialized yet"); + return; + } + + if (status == 1) { + netif_carrier_on(net); + netif_wake_queue(net); + } else { + netif_carrier_off(net); + netif_stop_queue(net); + } + DPRINT_EXIT(NETVSC_DRV); +} + +/** + * netvsc_recv_callback - Callback when we receive a packet from the "wire" on the specified device. + */ +static int netvsc_recv_callback(struct hv_device *device_obj, + struct hv_netvsc_packet *packet) +{ + struct device_context *device_ctx = to_device_context(device_obj); + struct net_device *net = dev_get_drvdata(&device_ctx->device); + struct net_device_context *net_device_ctx; + struct sk_buff *skb; + void *data; + int ret; + int i; + unsigned long flags; + + DPRINT_ENTER(NETVSC_DRV); + + if (!net) { + DPRINT_ERR(NETVSC_DRV, "got receive callback but net device " + "not initialized yet"); + return 0; + } + + net_device_ctx = netdev_priv(net); + + /* Allocate a skb - TODO preallocate this */ + /* Pad 2-bytes to align IP header to 16 bytes */ + skb = dev_alloc_skb(packet->TotalDataBufferLength + 2); + ASSERT(skb); + skb_reserve(skb, 2); + skb->dev = net; + + /* for kmap_atomic */ + local_irq_save(flags); + + /* + * Copy to skb. This copy is needed here since the memory pointed by + * hv_netvsc_packet cannot be deallocated + */ + for (i = 0; i < packet->PageBufferCount; i++) { + data = kmap_atomic(pfn_to_page(packet->PageBuffers[i].Pfn), + KM_IRQ1); + data = (void *)(unsigned long)data + + packet->PageBuffers[i].Offset; + + memcpy(skb_put(skb, packet->PageBuffers[i].Length), data, + packet->PageBuffers[i].Length); + + kunmap_atomic((void *)((unsigned long)data - + packet->PageBuffers[i].Offset), KM_IRQ1); + } + + local_irq_restore(flags); + + skb->protocol = eth_type_trans(skb, net); + + skb->ip_summed = CHECKSUM_NONE; + + /* + * Pass the skb back up. Network stack will deallocate the skb when it + * is done + */ + ret = netif_rx(skb); + + switch (ret) { + case NET_RX_DROP: + net_device_ctx->stats.rx_dropped++; + break; + default: + net_device_ctx->stats.rx_packets++; + net_device_ctx->stats.rx_bytes += skb->len; + break; + + } + DPRINT_DBG(NETVSC_DRV, "# of recvs %lu total size %lu", + net_device_ctx->stats.rx_packets, + net_device_ctx->stats.rx_bytes); + + DPRINT_EXIT(NETVSC_DRV); + + return 0; +} + +static const struct net_device_ops device_ops = { + .ndo_open = netvsc_open, + .ndo_stop = netvsc_close, + .ndo_start_xmit = netvsc_start_xmit, + .ndo_get_stats = netvsc_get_stats, + .ndo_set_multicast_list = netvsc_set_multicast_list, +}; + +static int netvsc_probe(struct device *device) +{ + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct netvsc_driver_context *net_drv_ctx = + (struct netvsc_driver_context *)driver_ctx; + struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct hv_device *device_obj = &device_ctx->device_obj; + struct net_device *net = NULL; + struct net_device_context *net_device_ctx; + struct netvsc_device_info device_info; + int ret; + + DPRINT_ENTER(NETVSC_DRV); + + if (!net_drv_obj->Base.OnDeviceAdd) + return -1; + + net = alloc_netdev(sizeof(struct net_device_context), "seth%d", + ether_setup); + if (!net) + return -1; + + /* Set initial state */ + netif_carrier_off(net); + netif_stop_queue(net); + + net_device_ctx = netdev_priv(net); + net_device_ctx->device_ctx = device_ctx; + dev_set_drvdata(device, net); + + /* Notify the netvsc driver of the new device */ + ret = net_drv_obj->Base.OnDeviceAdd(device_obj, &device_info); + if (ret != 0) { + free_netdev(net); + dev_set_drvdata(device, NULL); + + DPRINT_ERR(NETVSC_DRV, "unable to add netvsc device (ret %d)", + ret); + return ret; + } + + /* + * If carrier is still off ie we did not get a link status callback, + * update it if necessary + */ + /* + * FIXME: We should use a atomic or test/set instead to avoid getting + * out of sync with the device's link status + */ + if (!netif_carrier_ok(net)) + if (!device_info.LinkState) + netif_carrier_on(net); + + memcpy(net->dev_addr, device_info.MacAddr, ETH_ALEN); + + net->netdev_ops = &device_ops; + + SET_NETDEV_DEV(net, device); + + ret = register_netdev(net); + if (ret != 0) { + /* Remove the device and release the resource */ + net_drv_obj->Base.OnDeviceRemove(device_obj); + free_netdev(net); + } + + DPRINT_EXIT(NETVSC_DRV); + return ret; +} + +static int netvsc_remove(struct device *device) +{ + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct netvsc_driver_context *net_drv_ctx = + (struct netvsc_driver_context *)driver_ctx; + struct netvsc_driver *net_drv_obj = &net_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct net_device *net = dev_get_drvdata(&device_ctx->device); + struct hv_device *device_obj = &device_ctx->device_obj; + int ret; + + DPRINT_ENTER(NETVSC_DRV); + + if (net == NULL) { + DPRINT_INFO(NETVSC, "no net device to remove"); + DPRINT_EXIT(NETVSC_DRV); + return 0; + } + + if (!net_drv_obj->Base.OnDeviceRemove) { + DPRINT_EXIT(NETVSC_DRV); + return -1; + } + + /* Stop outbound asap */ + netif_stop_queue(net); + /* netif_carrier_off(net); */ + + unregister_netdev(net); + + /* + * Call to the vsc driver to let it know that the device is being + * removed + */ + ret = net_drv_obj->Base.OnDeviceRemove(device_obj); + if (ret != 0) { + /* TODO: */ + DPRINT_ERR(NETVSC, "unable to remove vsc device (ret %d)", ret); + } + + free_netdev(net); + DPRINT_EXIT(NETVSC_DRV); + return ret; +} + +static int netvsc_drv_exit_cb(struct device *dev, void *data) +{ + struct device **curr = (struct device **)data; + + *curr = dev; + /* stop iterating */ + return 1; +} + +static void netvsc_drv_exit(void) +{ + struct netvsc_driver *netvsc_drv_obj = &g_netvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_netvsc_drv.drv_ctx; + struct device *current_dev; + int ret; + + DPRINT_ENTER(NETVSC_DRV); + + while (1) { + current_dev = NULL; + + /* Get the device */ + ret = driver_for_each_device(&drv_ctx->driver, NULL, + ¤t_dev, netvsc_drv_exit_cb); + if (ret) + DPRINT_WARN(NETVSC_DRV, + "driver_for_each_device returned %d", ret); + + if (current_dev == NULL) + break; + + /* Initiate removal from the top-down */ + DPRINT_INFO(NETVSC_DRV, "unregistering device (%p)...", + current_dev); + + device_unregister(current_dev); + } + + if (netvsc_drv_obj->Base.OnCleanup) + netvsc_drv_obj->Base.OnCleanup(&netvsc_drv_obj->Base); + + vmbus_child_driver_unregister(drv_ctx); + + DPRINT_EXIT(NETVSC_DRV); + + return; +} + +static int netvsc_drv_init(int (*drv_init)(struct hv_driver *drv)) +{ + struct netvsc_driver *net_drv_obj = &g_netvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_netvsc_drv.drv_ctx; + int ret; + + DPRINT_ENTER(NETVSC_DRV); + + vmbus_get_interface(&net_drv_obj->Base.VmbusChannelInterface); + + net_drv_obj->RingBufferSize = netvsc_ringbuffer_size; + net_drv_obj->OnReceiveCallback = netvsc_recv_callback; + net_drv_obj->OnLinkStatusChanged = netvsc_linkstatus_callback; + + /* Callback to client driver to complete the initialization */ + drv_init(&net_drv_obj->Base); + + drv_ctx->driver.name = net_drv_obj->Base.name; + memcpy(&drv_ctx->class_id, &net_drv_obj->Base.deviceType, + sizeof(struct hv_guid)); + + drv_ctx->probe = netvsc_probe; + drv_ctx->remove = netvsc_remove; + + /* The driver belongs to vmbus */ + ret = vmbus_child_driver_register(drv_ctx); + + DPRINT_EXIT(NETVSC_DRV); + + return ret; +} + +static int __init netvsc_init(void) +{ + int ret; + + DPRINT_ENTER(NETVSC_DRV); + DPRINT_INFO(NETVSC_DRV, "Netvsc initializing...."); + + ret = netvsc_drv_init(NetVscInitialize); + + DPRINT_EXIT(NETVSC_DRV); + + return ret; +} + +static void __exit netvsc_exit(void) +{ + DPRINT_ENTER(NETVSC_DRV); + netvsc_drv_exit(); + DPRINT_EXIT(NETVSC_DRV); +} + +module_param(netvsc_ringbuffer_size, int, S_IRUGO); + +module_init(netvsc_init); +module_exit(netvsc_exit); diff --git a/drivers/staging/hv/osd.c b/drivers/staging/hv/osd.c new file mode 100644 index 00000000000..8fe543bd991 --- /dev/null +++ b/drivers/staging/hv/osd.c @@ -0,0 +1,156 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/mm.h> +#include <linux/highmem.h> +#include <linux/vmalloc.h> +#include <linux/ioport.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/jiffies.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include "osd.h" + +struct osd_callback_struct { + struct work_struct work; + void (*callback)(void *); + void *data; +}; + +void *osd_VirtualAllocExec(unsigned int size) +{ +#ifdef __x86_64__ + return __vmalloc(size, GFP_KERNEL, PAGE_KERNEL_EXEC); +#else + return __vmalloc(size, GFP_KERNEL, + __pgprot(__PAGE_KERNEL & (~_PAGE_NX))); +#endif +} + +void *osd_PageAlloc(unsigned int count) +{ + void *p; + + p = (void *)__get_free_pages(GFP_KERNEL, get_order(count * PAGE_SIZE)); + if (p) + memset(p, 0, count * PAGE_SIZE); + return p; + + /* struct page* page = alloc_page(GFP_KERNEL|__GFP_ZERO); */ + /* void *p; */ + + /* BUGBUG: We need to use kmap in case we are in HIMEM region */ + /* p = page_address(page); */ + /* if (p) memset(p, 0, PAGE_SIZE); */ + /* return p; */ +} +EXPORT_SYMBOL_GPL(osd_PageAlloc); + +void osd_PageFree(void *page, unsigned int count) +{ + free_pages((unsigned long)page, get_order(count * PAGE_SIZE)); + /*struct page* p = virt_to_page(page); + __free_page(p);*/ +} +EXPORT_SYMBOL_GPL(osd_PageFree); + +struct osd_waitevent *osd_WaitEventCreate(void) +{ + struct osd_waitevent *wait = kmalloc(sizeof(struct osd_waitevent), + GFP_KERNEL); + if (!wait) + return NULL; + + wait->condition = 0; + init_waitqueue_head(&wait->event); + return wait; +} +EXPORT_SYMBOL_GPL(osd_WaitEventCreate); + +void osd_WaitEventSet(struct osd_waitevent *waitEvent) +{ + waitEvent->condition = 1; + wake_up_interruptible(&waitEvent->event); +} +EXPORT_SYMBOL_GPL(osd_WaitEventSet); + +int osd_WaitEventWait(struct osd_waitevent *waitEvent) +{ + int ret = 0; + + ret = wait_event_interruptible(waitEvent->event, + waitEvent->condition); + waitEvent->condition = 0; + return ret; +} +EXPORT_SYMBOL_GPL(osd_WaitEventWait); + +int osd_WaitEventWaitEx(struct osd_waitevent *waitEvent, u32 TimeoutInMs) +{ + int ret = 0; + + ret = wait_event_interruptible_timeout(waitEvent->event, + waitEvent->condition, + msecs_to_jiffies(TimeoutInMs)); + waitEvent->condition = 0; + return ret; +} +EXPORT_SYMBOL_GPL(osd_WaitEventWaitEx); + +static void osd_callback_work(struct work_struct *work) +{ + struct osd_callback_struct *cb = container_of(work, + struct osd_callback_struct, + work); + (cb->callback)(cb->data); + kfree(cb); +} + +int osd_schedule_callback(struct workqueue_struct *wq, + void (*func)(void *), + void *data) +{ + struct osd_callback_struct *cb; + + cb = kmalloc(sizeof(*cb), GFP_KERNEL); + if (!cb) { + printk(KERN_ERR "unable to allocate memory in osd_schedule_callback\n"); + return -1; + } + + cb->callback = func; + cb->data = data; + INIT_WORK(&cb->work, osd_callback_work); + return queue_work(wq, &cb->work); +} + diff --git a/drivers/staging/hv/osd.h b/drivers/staging/hv/osd.h new file mode 100644 index 00000000000..9504604c72b --- /dev/null +++ b/drivers/staging/hv/osd.h @@ -0,0 +1,69 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _OSD_H_ +#define _OSD_H_ + + +/* Defines */ +#define ALIGN_UP(value, align) (((value) & (align-1)) ? \ + (((value) + (align-1)) & ~(align-1)) : \ + (value)) +#define ALIGN_DOWN(value, align) ((value) & ~(align-1)) +#define NUM_PAGES_SPANNED(addr, len) ((ALIGN_UP(addr+len, PAGE_SIZE) - \ + ALIGN_DOWN(addr, PAGE_SIZE)) >> \ + PAGE_SHIFT) + +#define LOWORD(dw) ((unsigned short)(dw)) +#define HIWORD(dw) ((unsigned short)(((unsigned int) (dw) >> 16) & 0xFFFF)) + +struct hv_guid { + unsigned char data[16]; +}; + +struct osd_waitevent { + int condition; + wait_queue_head_t event; +}; + +/* Osd routines */ + +extern void *osd_VirtualAllocExec(unsigned int size); + +extern void *osd_PageAlloc(unsigned int count); +extern void osd_PageFree(void *page, unsigned int count); + +extern struct osd_waitevent *osd_WaitEventCreate(void); +extern void osd_WaitEventSet(struct osd_waitevent *waitEvent); +extern int osd_WaitEventWait(struct osd_waitevent *waitEvent); + +/* If >0, waitEvent got signaled. If ==0, timeout. If < 0, error */ +extern int osd_WaitEventWaitEx(struct osd_waitevent *waitEvent, + u32 TimeoutInMs); + +int osd_schedule_callback(struct workqueue_struct *wq, + void (*func)(void *), + void *data); + +#endif /* _OSD_H_ */ diff --git a/drivers/staging/hv/rndis.h b/drivers/staging/hv/rndis.h new file mode 100644 index 00000000000..7c73277c1f9 --- /dev/null +++ b/drivers/staging/hv/rndis.h @@ -0,0 +1,652 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +#ifndef _RNDIS_H_ +#define _RNDIS_H_ + +/* Status codes */ + + +#ifndef STATUS_SUCCESS +#define STATUS_SUCCESS (0x00000000L) +#endif + +#ifndef STATUS_UNSUCCESSFUL +#define STATUS_UNSUCCESSFUL (0xC0000001L) +#endif + +#ifndef STATUS_PENDING +#define STATUS_PENDING (0x00000103L) +#endif + +#ifndef STATUS_INSUFFICIENT_RESOURCES +#define STATUS_INSUFFICIENT_RESOURCES (0xC000009AL) +#endif + +#ifndef STATUS_BUFFER_OVERFLOW +#define STATUS_BUFFER_OVERFLOW (0x80000005L) +#endif + +#ifndef STATUS_NOT_SUPPORTED +#define STATUS_NOT_SUPPORTED (0xC00000BBL) +#endif + +#define RNDIS_STATUS_SUCCESS (STATUS_SUCCESS) +#define RNDIS_STATUS_PENDING (STATUS_PENDING) +#define RNDIS_STATUS_NOT_RECOGNIZED (0x00010001L) +#define RNDIS_STATUS_NOT_COPIED (0x00010002L) +#define RNDIS_STATUS_NOT_ACCEPTED (0x00010003L) +#define RNDIS_STATUS_CALL_ACTIVE (0x00010007L) + +#define RNDIS_STATUS_ONLINE (0x40010003L) +#define RNDIS_STATUS_RESET_START (0x40010004L) +#define RNDIS_STATUS_RESET_END (0x40010005L) +#define RNDIS_STATUS_RING_STATUS (0x40010006L) +#define RNDIS_STATUS_CLOSED (0x40010007L) +#define RNDIS_STATUS_WAN_LINE_UP (0x40010008L) +#define RNDIS_STATUS_WAN_LINE_DOWN (0x40010009L) +#define RNDIS_STATUS_WAN_FRAGMENT (0x4001000AL) +#define RNDIS_STATUS_MEDIA_CONNECT (0x4001000BL) +#define RNDIS_STATUS_MEDIA_DISCONNECT (0x4001000CL) +#define RNDIS_STATUS_HARDWARE_LINE_UP (0x4001000DL) +#define RNDIS_STATUS_HARDWARE_LINE_DOWN (0x4001000EL) +#define RNDIS_STATUS_INTERFACE_UP (0x4001000FL) +#define RNDIS_STATUS_INTERFACE_DOWN (0x40010010L) +#define RNDIS_STATUS_MEDIA_BUSY (0x40010011L) +#define RNDIS_STATUS_MEDIA_SPECIFIC_INDICATION (0x40010012L) +#define RNDIS_STATUS_WW_INDICATION RDIA_SPECIFIC_INDICATION +#define RNDIS_STATUS_LINK_SPEED_CHANGE (0x40010013L) + +#define RNDIS_STATUS_NOT_RESETTABLE (0x80010001L) +#define RNDIS_STATUS_SOFT_ERRORS (0x80010003L) +#define RNDIS_STATUS_HARD_ERRORS (0x80010004L) +#define RNDIS_STATUS_BUFFER_OVERFLOW (STATUS_BUFFER_OVERFLOW) + +#define RNDIS_STATUS_FAILURE (STATUS_UNSUCCESSFUL) +#define RNDIS_STATUS_RESOURCES (STATUS_INSUFFICIENT_RESOURCES) +#define RNDIS_STATUS_CLOSING (0xC0010002L) +#define RNDIS_STATUS_BAD_VERSION (0xC0010004L) +#define RNDIS_STATUS_BAD_CHARACTERISTICS (0xC0010005L) +#define RNDIS_STATUS_ADAPTER_NOT_FOUND (0xC0010006L) +#define RNDIS_STATUS_OPEN_FAILED (0xC0010007L) +#define RNDIS_STATUS_DEVICE_FAILED (0xC0010008L) +#define RNDIS_STATUS_MULTICAST_FULL (0xC0010009L) +#define RNDIS_STATUS_MULTICAST_EXISTS (0xC001000AL) +#define RNDIS_STATUS_MULTICAST_NOT_FOUND (0xC001000BL) +#define RNDIS_STATUS_REQUEST_ABORTED (0xC001000CL) +#define RNDIS_STATUS_RESET_IN_PROGRESS (0xC001000DL) +#define RNDIS_STATUS_CLOSING_INDICATING (0xC001000EL) +#define RNDIS_STATUS_NOT_SUPPORTED (STATUS_NOT_SUPPORTED) +#define RNDIS_STATUS_INVALID_PACKET (0xC001000FL) +#define RNDIS_STATUS_OPEN_LIST_FULL (0xC0010010L) +#define RNDIS_STATUS_ADAPTER_NOT_READY (0xC0010011L) +#define RNDIS_STATUS_ADAPTER_NOT_OPEN (0xC0010012L) +#define RNDIS_STATUS_NOT_INDICATING (0xC0010013L) +#define RNDIS_STATUS_INVALID_LENGTH (0xC0010014L) +#define RNDIS_STATUS_INVALID_DATA (0xC0010015L) +#define RNDIS_STATUS_BUFFER_TOO_SHORT (0xC0010016L) +#define RNDIS_STATUS_INVALID_OID (0xC0010017L) +#define RNDIS_STATUS_ADAPTER_REMOVED (0xC0010018L) +#define RNDIS_STATUS_UNSUPPORTED_MEDIA (0xC0010019L) +#define RNDIS_STATUS_GROUP_ADDRESS_IN_USE (0xC001001AL) +#define RNDIS_STATUS_FILE_NOT_FOUND (0xC001001BL) +#define RNDIS_STATUS_ERROR_READING_FILE (0xC001001CL) +#define RNDIS_STATUS_ALREADY_MAPPED (0xC001001DL) +#define RNDIS_STATUS_RESOURCE_CONFLICT (0xC001001EL) +#define RNDIS_STATUS_NO_CABLE (0xC001001FL) + +#define RNDIS_STATUS_INVALID_SAP (0xC0010020L) +#define RNDIS_STATUS_SAP_IN_USE (0xC0010021L) +#define RNDIS_STATUS_INVALID_ADDRESS (0xC0010022L) +#define RNDIS_STATUS_VC_NOT_ACTIVATED (0xC0010023L) +#define RNDIS_STATUS_DEST_OUT_OF_ORDER (0xC0010024L) +#define RNDIS_STATUS_VC_NOT_AVAILABLE (0xC0010025L) +#define RNDIS_STATUS_CELLRATE_NOT_AVAILABLE (0xC0010026L) +#define RNDIS_STATUS_INCOMPATABLE_QOS (0xC0010027L) +#define RNDIS_STATUS_AAL_PARAMS_UNSUPPORTED (0xC0010028L) +#define RNDIS_STATUS_NO_ROUTE_TO_DESTINATION (0xC0010029L) + +#define RNDIS_STATUS_TOKEN_RING_OPEN_ERROR (0xC0011000L) + +/* Object Identifiers used by NdisRequest Query/Set Information */ +/* General Objects */ +#define RNDIS_OID_GEN_SUPPORTED_LIST 0x00010101 +#define RNDIS_OID_GEN_HARDWARE_STATUS 0x00010102 +#define RNDIS_OID_GEN_MEDIA_SUPPORTED 0x00010103 +#define RNDIS_OID_GEN_MEDIA_IN_USE 0x00010104 +#define RNDIS_OID_GEN_MAXIMUM_LOOKAHEAD 0x00010105 +#define RNDIS_OID_GEN_MAXIMUM_FRAME_SIZE 0x00010106 +#define RNDIS_OID_GEN_LINK_SPEED 0x00010107 +#define RNDIS_OID_GEN_TRANSMIT_BUFFER_SPACE 0x00010108 +#define RNDIS_OID_GEN_RECEIVE_BUFFER_SPACE 0x00010109 +#define RNDIS_OID_GEN_TRANSMIT_BLOCK_SIZE 0x0001010A +#define RNDIS_OID_GEN_RECEIVE_BLOCK_SIZE 0x0001010B +#define RNDIS_OID_GEN_VENDOR_ID 0x0001010C +#define RNDIS_OID_GEN_VENDOR_DESCRIPTION 0x0001010D +#define RNDIS_OID_GEN_CURRENT_PACKET_FILTER 0x0001010E +#define RNDIS_OID_GEN_CURRENT_LOOKAHEAD 0x0001010F +#define RNDIS_OID_GEN_DRIVER_VERSION 0x00010110 +#define RNDIS_OID_GEN_MAXIMUM_TOTAL_SIZE 0x00010111 +#define RNDIS_OID_GEN_PROTOCOL_OPTIONS 0x00010112 +#define RNDIS_OID_GEN_MAC_OPTIONS 0x00010113 +#define RNDIS_OID_GEN_MEDIA_CONNECT_STATUS 0x00010114 +#define RNDIS_OID_GEN_MAXIMUM_SEND_PACKETS 0x00010115 +#define RNDIS_OID_GEN_VENDOR_DRIVER_VERSION 0x00010116 +#define RNDIS_OID_GEN_NETWORK_LAYER_ADDRESSES 0x00010118 +#define RNDIS_OID_GEN_TRANSPORT_HEADER_OFFSET 0x00010119 +#define RNDIS_OID_GEN_MACHINE_NAME 0x0001021A +#define RNDIS_OID_GEN_RNDIS_CONFIG_PARAMETER 0x0001021B + +#define RNDIS_OID_GEN_XMIT_OK 0x00020101 +#define RNDIS_OID_GEN_RCV_OK 0x00020102 +#define RNDIS_OID_GEN_XMIT_ERROR 0x00020103 +#define RNDIS_OID_GEN_RCV_ERROR 0x00020104 +#define RNDIS_OID_GEN_RCV_NO_BUFFER 0x00020105 + +#define RNDIS_OID_GEN_DIRECTED_BYTES_XMIT 0x00020201 +#define RNDIS_OID_GEN_DIRECTED_FRAMES_XMIT 0x00020202 +#define RNDIS_OID_GEN_MULTICAST_BYTES_XMIT 0x00020203 +#define RNDIS_OID_GEN_MULTICAST_FRAMES_XMIT 0x00020204 +#define RNDIS_OID_GEN_BROADCAST_BYTES_XMIT 0x00020205 +#define RNDIS_OID_GEN_BROADCAST_FRAMES_XMIT 0x00020206 +#define RNDIS_OID_GEN_DIRECTED_BYTES_RCV 0x00020207 +#define RNDIS_OID_GEN_DIRECTED_FRAMES_RCV 0x00020208 +#define RNDIS_OID_GEN_MULTICAST_BYTES_RCV 0x00020209 +#define RNDIS_OID_GEN_MULTICAST_FRAMES_RCV 0x0002020A +#define RNDIS_OID_GEN_BROADCAST_BYTES_RCV 0x0002020B +#define RNDIS_OID_GEN_BROADCAST_FRAMES_RCV 0x0002020C + +#define RNDIS_OID_GEN_RCV_CRC_ERROR 0x0002020D +#define RNDIS_OID_GEN_TRANSMIT_QUEUE_LENGTH 0x0002020E + +#define RNDIS_OID_GEN_GET_TIME_CAPS 0x0002020F +#define RNDIS_OID_GEN_GET_NETCARD_TIME 0x00020210 + +/* These are connection-oriented general OIDs. */ +/* These replace the above OIDs for connection-oriented media. */ +#define RNDIS_OID_GEN_CO_SUPPORTED_LIST 0x00010101 +#define RNDIS_OID_GEN_CO_HARDWARE_STATUS 0x00010102 +#define RNDIS_OID_GEN_CO_MEDIA_SUPPORTED 0x00010103 +#define RNDIS_OID_GEN_CO_MEDIA_IN_USE 0x00010104 +#define RNDIS_OID_GEN_CO_LINK_SPEED 0x00010105 +#define RNDIS_OID_GEN_CO_VENDOR_ID 0x00010106 +#define RNDIS_OID_GEN_CO_VENDOR_DESCRIPTION 0x00010107 +#define RNDIS_OID_GEN_CO_DRIVER_VERSION 0x00010108 +#define RNDIS_OID_GEN_CO_PROTOCOL_OPTIONS 0x00010109 +#define RNDIS_OID_GEN_CO_MAC_OPTIONS 0x0001010A +#define RNDIS_OID_GEN_CO_MEDIA_CONNECT_STATUS 0x0001010B +#define RNDIS_OID_GEN_CO_VENDOR_DRIVER_VERSION 0x0001010C +#define RNDIS_OID_GEN_CO_MINIMUM_LINK_SPEED 0x0001010D + +#define RNDIS_OID_GEN_CO_GET_TIME_CAPS 0x00010201 +#define RNDIS_OID_GEN_CO_GET_NETCARD_TIME 0x00010202 + +/* These are connection-oriented statistics OIDs. */ +#define RNDIS_OID_GEN_CO_XMIT_PDUS_OK 0x00020101 +#define RNDIS_OID_GEN_CO_RCV_PDUS_OK 0x00020102 +#define RNDIS_OID_GEN_CO_XMIT_PDUS_ERROR 0x00020103 +#define RNDIS_OID_GEN_CO_RCV_PDUS_ERROR 0x00020104 +#define RNDIS_OID_GEN_CO_RCV_PDUS_NO_BUFFER 0x00020105 + + +#define RNDIS_OID_GEN_CO_RCV_CRC_ERROR 0x00020201 +#define RNDIS_OID_GEN_CO_TRANSMIT_QUEUE_LENGTH 0x00020202 +#define RNDIS_OID_GEN_CO_BYTES_XMIT 0x00020203 +#define RNDIS_OID_GEN_CO_BYTES_RCV 0x00020204 +#define RNDIS_OID_GEN_CO_BYTES_XMIT_OUTSTANDING 0x00020205 +#define RNDIS_OID_GEN_CO_NETCARD_LOAD 0x00020206 + +/* These are objects for Connection-oriented media call-managers. */ +#define RNDIS_OID_CO_ADD_PVC 0xFF000001 +#define RNDIS_OID_CO_DELETE_PVC 0xFF000002 +#define RNDIS_OID_CO_GET_CALL_INFORMATION 0xFF000003 +#define RNDIS_OID_CO_ADD_ADDRESS 0xFF000004 +#define RNDIS_OID_CO_DELETE_ADDRESS 0xFF000005 +#define RNDIS_OID_CO_GET_ADDRESSES 0xFF000006 +#define RNDIS_OID_CO_ADDRESS_CHANGE 0xFF000007 +#define RNDIS_OID_CO_SIGNALING_ENABLED 0xFF000008 +#define RNDIS_OID_CO_SIGNALING_DISABLED 0xFF000009 + +/* 802.3 Objects (Ethernet) */ +#define RNDIS_OID_802_3_PERMANENT_ADDRESS 0x01010101 +#define RNDIS_OID_802_3_CURRENT_ADDRESS 0x01010102 +#define RNDIS_OID_802_3_MULTICAST_LIST 0x01010103 +#define RNDIS_OID_802_3_MAXIMUM_LIST_SIZE 0x01010104 +#define RNDIS_OID_802_3_MAC_OPTIONS 0x01010105 + +#define NDIS_802_3_MAC_OPTION_PRIORITY 0x00000001 + +#define RNDIS_OID_802_3_RCV_ERROR_ALIGNMENT 0x01020101 +#define RNDIS_OID_802_3_XMIT_ONE_COLLISION 0x01020102 +#define RNDIS_OID_802_3_XMIT_MORE_COLLISIONS 0x01020103 + +#define RNDIS_OID_802_3_XMIT_DEFERRED 0x01020201 +#define RNDIS_OID_802_3_XMIT_MAX_COLLISIONS 0x01020202 +#define RNDIS_OID_802_3_RCV_OVERRUN 0x01020203 +#define RNDIS_OID_802_3_XMIT_UNDERRUN 0x01020204 +#define RNDIS_OID_802_3_XMIT_HEARTBEAT_FAILURE 0x01020205 +#define RNDIS_OID_802_3_XMIT_TIMES_CRS_LOST 0x01020206 +#define RNDIS_OID_802_3_XMIT_LATE_COLLISIONS 0x01020207 + +/* Remote NDIS message types */ +#define REMOTE_NDIS_PACKET_MSG 0x00000001 +#define REMOTE_NDIS_INITIALIZE_MSG 0x00000002 +#define REMOTE_NDIS_HALT_MSG 0x00000003 +#define REMOTE_NDIS_QUERY_MSG 0x00000004 +#define REMOTE_NDIS_SET_MSG 0x00000005 +#define REMOTE_NDIS_RESET_MSG 0x00000006 +#define REMOTE_NDIS_INDICATE_STATUS_MSG 0x00000007 +#define REMOTE_NDIS_KEEPALIVE_MSG 0x00000008 + +#define REMOTE_CONDIS_MP_CREATE_VC_MSG 0x00008001 +#define REMOTE_CONDIS_MP_DELETE_VC_MSG 0x00008002 +#define REMOTE_CONDIS_MP_ACTIVATE_VC_MSG 0x00008005 +#define REMOTE_CONDIS_MP_DEACTIVATE_VC_MSG 0x00008006 +#define REMOTE_CONDIS_INDICATE_STATUS_MSG 0x00008007 + +/* Remote NDIS message completion types */ +#define REMOTE_NDIS_INITIALIZE_CMPLT 0x80000002 +#define REMOTE_NDIS_QUERY_CMPLT 0x80000004 +#define REMOTE_NDIS_SET_CMPLT 0x80000005 +#define REMOTE_NDIS_RESET_CMPLT 0x80000006 +#define REMOTE_NDIS_KEEPALIVE_CMPLT 0x80000008 + +#define REMOTE_CONDIS_MP_CREATE_VC_CMPLT 0x80008001 +#define REMOTE_CONDIS_MP_DELETE_VC_CMPLT 0x80008002 +#define REMOTE_CONDIS_MP_ACTIVATE_VC_CMPLT 0x80008005 +#define REMOTE_CONDIS_MP_DEACTIVATE_VC_CMPLT 0x80008006 + +/* + * Reserved message type for private communication between lower-layer host + * driver and remote device, if necessary. + */ +#define REMOTE_NDIS_BUS_MSG 0xff000001 + +/* Defines for DeviceFlags in struct rndis_initialize_complete */ +#define RNDIS_DF_CONNECTIONLESS 0x00000001 +#define RNDIS_DF_CONNECTION_ORIENTED 0x00000002 +#define RNDIS_DF_RAW_DATA 0x00000004 + +/* Remote NDIS medium types. */ +#define RNdisMedium802_3 0x00000000 +#define RNdisMedium802_5 0x00000001 +#define RNdisMediumFddi 0x00000002 +#define RNdisMediumWan 0x00000003 +#define RNdisMediumLocalTalk 0x00000004 +#define RNdisMediumArcnetRaw 0x00000006 +#define RNdisMediumArcnet878_2 0x00000007 +#define RNdisMediumAtm 0x00000008 +#define RNdisMediumWirelessWan 0x00000009 +#define RNdisMediumIrda 0x0000000a +#define RNdisMediumCoWan 0x0000000b +/* Not a real medium, defined as an upper-bound */ +#define RNdisMediumMax 0x0000000d + + +/* Remote NDIS medium connection states. */ +#define RNdisMediaStateConnected 0x00000000 +#define RNdisMediaStateDisconnected 0x00000001 + +/* Remote NDIS version numbers */ +#define RNDIS_MAJOR_VERSION 0x00000001 +#define RNDIS_MINOR_VERSION 0x00000000 + + +/* NdisInitialize message */ +struct rndis_initialize_request { + u32 RequestId; + u32 MajorVersion; + u32 MinorVersion; + u32 MaxTransferSize; +}; + +/* Response to NdisInitialize */ +struct rndis_initialize_complete { + u32 RequestId; + u32 Status; + u32 MajorVersion; + u32 MinorVersion; + u32 DeviceFlags; + u32 Medium; + u32 MaxPacketsPerMessage; + u32 MaxTransferSize; + u32 PacketAlignmentFactor; + u32 AFListOffset; + u32 AFListSize; +}; + +/* Call manager devices only: Information about an address family */ +/* supported by the device is appended to the response to NdisInitialize. */ +struct rndis_co_address_family { + u32 AddressFamily; + u32 MajorVersion; + u32 MinorVersion; +}; + +/* NdisHalt message */ +struct rndis_halt_request { + u32 RequestId; +}; + +/* NdisQueryRequest message */ +struct rndis_query_request { + u32 RequestId; + u32 Oid; + u32 InformationBufferLength; + u32 InformationBufferOffset; + u32 DeviceVcHandle; +}; + +/* Response to NdisQueryRequest */ +struct rndis_query_complete { + u32 RequestId; + u32 Status; + u32 InformationBufferLength; + u32 InformationBufferOffset; +}; + +/* NdisSetRequest message */ +struct rndis_set_request { + u32 RequestId; + u32 Oid; + u32 InformationBufferLength; + u32 InformationBufferOffset; + u32 DeviceVcHandle; +}; + +/* Response to NdisSetRequest */ +struct rndis_set_complete { + u32 RequestId; + u32 Status; +}; + +/* NdisReset message */ +struct rndis_reset_request { + u32 Reserved; +}; + +/* Response to NdisReset */ +struct rndis_reset_complete { + u32 Status; + u32 AddressingReset; +}; + +/* NdisMIndicateStatus message */ +struct rndis_indicate_status { + u32 Status; + u32 StatusBufferLength; + u32 StatusBufferOffset; +}; + +/* Diagnostic information passed as the status buffer in */ +/* struct rndis_indicate_status messages signifying error conditions. */ +struct rndis_diagnostic_info { + u32 DiagStatus; + u32 ErrorOffset; +}; + +/* NdisKeepAlive message */ +struct rndis_keepalive_request { + u32 RequestId; +}; + +/* Response to NdisKeepAlive */ +struct rndis_keepalive_complete { + u32 RequestId; + u32 Status; +}; + +/* + * Data message. All Offset fields contain byte offsets from the beginning of + * struct rndis_packet. All Length fields are in bytes. VcHandle is set + * to 0 for connectionless data, otherwise it contains the VC handle. + */ +struct rndis_packet { + u32 DataOffset; + u32 DataLength; + u32 OOBDataOffset; + u32 OOBDataLength; + u32 NumOOBDataElements; + u32 PerPacketInfoOffset; + u32 PerPacketInfoLength; + u32 VcHandle; + u32 Reserved; +}; + +/* Optional Out of Band data associated with a Data message. */ +struct rndis_oobd { + u32 Size; + u32 Type; + u32 ClassInformationOffset; +}; + +/* Packet extension field contents associated with a Data message. */ +struct rndis_per_packet_info { + u32 Size; + u32 Type; + u32 PerPacketInformationOffset; +}; + +/* Format of Information buffer passed in a SetRequest for the OID */ +/* OID_GEN_RNDIS_CONFIG_PARAMETER. */ +struct rndis_config_parameter_info { + u32 ParameterNameOffset; + u32 ParameterNameLength; + u32 ParameterType; + u32 ParameterValueOffset; + u32 ParameterValueLength; +}; + +/* Values for ParameterType in struct rndis_config_parameter_info */ +#define RNDIS_CONFIG_PARAM_TYPE_INTEGER 0 +#define RNDIS_CONFIG_PARAM_TYPE_STRING 2 + +/* CONDIS Miniport messages for connection oriented devices */ +/* that do not implement a call manager. */ + +/* CoNdisMiniportCreateVc message */ +struct rcondis_mp_create_vc { + u32 RequestId; + u32 NdisVcHandle; +}; + +/* Response to CoNdisMiniportCreateVc */ +struct rcondis_mp_create_vc_complete { + u32 RequestId; + u32 DeviceVcHandle; + u32 Status; +}; + +/* CoNdisMiniportDeleteVc message */ +struct rcondis_mp_delete_vc { + u32 RequestId; + u32 DeviceVcHandle; +}; + +/* Response to CoNdisMiniportDeleteVc */ +struct rcondis_mp_delete_vc_complete { + u32 RequestId; + u32 Status; +}; + +/* CoNdisMiniportQueryRequest message */ +struct rcondis_mp_query_request { + u32 RequestId; + u32 RequestType; + u32 Oid; + u32 DeviceVcHandle; + u32 InformationBufferLength; + u32 InformationBufferOffset; +}; + +/* CoNdisMiniportSetRequest message */ +struct rcondis_mp_set_request { + u32 RequestId; + u32 RequestType; + u32 Oid; + u32 DeviceVcHandle; + u32 InformationBufferLength; + u32 InformationBufferOffset; +}; + +/* CoNdisIndicateStatus message */ +struct rcondis_indicate_status { + u32 NdisVcHandle; + u32 Status; + u32 StatusBufferLength; + u32 StatusBufferOffset; +}; + +/* CONDIS Call/VC parameters */ +struct rcondis_specific_parameters { + u32 ParameterType; + u32 ParameterLength; + u32 ParameterOffset; +}; + +struct rcondis_media_parameters { + u32 Flags; + u32 Reserved1; + u32 Reserved2; + struct rcondis_specific_parameters MediaSpecific; +}; + +struct rndis_flowspec { + u32 TokenRate; + u32 TokenBucketSize; + u32 PeakBandwidth; + u32 Latency; + u32 DelayVariation; + u32 ServiceType; + u32 MaxSduSize; + u32 MinimumPolicedSize; +}; + +struct rcondis_call_manager_parameters { + struct rndis_flowspec Transmit; + struct rndis_flowspec Receive; + struct rcondis_specific_parameters CallMgrSpecific; +}; + +/* CoNdisMiniportActivateVc message */ +struct rcondis_mp_activate_vc_request { + u32 RequestId; + u32 Flags; + u32 DeviceVcHandle; + u32 MediaParamsOffset; + u32 MediaParamsLength; + u32 CallMgrParamsOffset; + u32 CallMgrParamsLength; +}; + +/* Response to CoNdisMiniportActivateVc */ +struct rcondis_mp_activate_vc_complete { + u32 RequestId; + u32 Status; +}; + +/* CoNdisMiniportDeactivateVc message */ +struct rcondis_mp_deactivate_vc_request { + u32 RequestId; + u32 Flags; + u32 DeviceVcHandle; +}; + +/* Response to CoNdisMiniportDeactivateVc */ +struct rcondis_mp_deactivate_vc_complete { + u32 RequestId; + u32 Status; +}; + + +/* union with all of the RNDIS messages */ +union rndis_message_container { + struct rndis_packet Packet; + struct rndis_initialize_request InitializeRequest; + struct rndis_halt_request HaltRequest; + struct rndis_query_request QueryRequest; + struct rndis_set_request SetRequest; + struct rndis_reset_request ResetRequest; + struct rndis_keepalive_request KeepaliveRequest; + struct rndis_indicate_status IndicateStatus; + struct rndis_initialize_complete InitializeComplete; + struct rndis_query_complete QueryComplete; + struct rndis_set_complete SetComplete; + struct rndis_reset_complete ResetComplete; + struct rndis_keepalive_complete KeepaliveComplete; + struct rcondis_mp_create_vc CoMiniportCreateVc; + struct rcondis_mp_delete_vc CoMiniportDeleteVc; + struct rcondis_indicate_status CoIndicateStatus; + struct rcondis_mp_activate_vc_request CoMiniportActivateVc; + struct rcondis_mp_deactivate_vc_request CoMiniportDeactivateVc; + struct rcondis_mp_create_vc_complete CoMiniportCreateVcComplete; + struct rcondis_mp_delete_vc_complete CoMiniportDeleteVcComplete; + struct rcondis_mp_activate_vc_complete CoMiniportActivateVcComplete; + struct rcondis_mp_deactivate_vc_complete CoMiniportDeactivateVcComplete; +}; + +/* Remote NDIS message format */ +struct rndis_message { + u32 NdisMessageType; + + /* Total length of this message, from the beginning */ + /* of the sruct rndis_message, in bytes. */ + u32 MessageLength; + + /* Actual message */ + union rndis_message_container Message; +}; + +/* Handy macros */ + +/* get the size of an RNDIS message. Pass in the message type, */ +/* struct rndis_set_request, struct rndis_packet for example */ +#define RNDIS_MESSAGE_SIZE(Message) \ + (sizeof(Message) + (sizeof(struct rndis_message) - \ + sizeof(union rndis_message_container))) + +/* get pointer to info buffer with message pointer */ +#define MESSAGE_TO_INFO_BUFFER(Message) \ + (((unsigned char *)(Message)) + Message->InformationBufferOffset) + +/* get pointer to status buffer with message pointer */ +#define MESSAGE_TO_STATUS_BUFFER(Message) \ + (((unsigned char *)(Message)) + Message->StatusBufferOffset) + +/* get pointer to OOBD buffer with message pointer */ +#define MESSAGE_TO_OOBD_BUFFER(Message) \ + (((unsigned char *)(Message)) + Message->OOBDataOffset) + +/* get pointer to data buffer with message pointer */ +#define MESSAGE_TO_DATA_BUFFER(Message) \ + (((unsigned char *)(Message)) + Message->PerPacketInfoOffset) + +/* get pointer to contained message from NDIS_MESSAGE pointer */ +#define RNDIS_MESSAGE_PTR_TO_MESSAGE_PTR(RndisMessage) \ + ((void *) &RndisMessage->Message) + +/* get pointer to contained message from NDIS_MESSAGE pointer */ +#define RNDIS_MESSAGE_RAW_PTR_TO_MESSAGE_PTR(RndisMessage) \ + ((void *) RndisMessage) + +#endif /* _RNDIS_H_ */ diff --git a/drivers/staging/hv/storvsc_drv.c b/drivers/staging/hv/storvsc_drv.c new file mode 100644 index 00000000000..d49dc21d4cb --- /dev/null +++ b/drivers/staging/hv/storvsc_drv.c @@ -0,0 +1,1208 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/blkdev.h> +#include <scsi/scsi.h> +#include <scsi/scsi_cmnd.h> +#include <scsi/scsi_host.h> +#include <scsi/scsi_device.h> +#include <scsi/scsi_tcq.h> +#include <scsi/scsi_eh.h> +#include <scsi/scsi_devinfo.h> +#include <scsi/scsi_dbg.h> +#include "osd.h" +#include "logging.h" +#include "vmbus.h" +#include "StorVscApi.h" + + +struct host_device_context { + /* must be 1st field + * FIXME this is a bug */ + struct work_struct host_rescan_work; + + /* point back to our device context */ + struct device_context *device_ctx; + struct kmem_cache *request_pool; + unsigned int port; + unsigned char path; + unsigned char target; +}; + +struct storvsc_cmd_request { + struct list_head entry; + struct scsi_cmnd *cmd; + + unsigned int bounce_sgl_count; + struct scatterlist *bounce_sgl; + + struct hv_storvsc_request request; + /* !!!DO NOT ADD ANYTHING BELOW HERE!!! */ + /* The extension buffer falls right here and is pointed to by + * request.Extension; + * Which sounds like a very bad design... */ +}; + +struct storvsc_driver_context { + /* !! These must be the first 2 fields !! */ + /* FIXME this is a bug... */ + struct driver_context drv_ctx; + struct storvsc_driver_object drv_obj; +}; + +/* Static decl */ +static int storvsc_probe(struct device *dev); +static int storvsc_queuecommand(struct scsi_cmnd *scmnd, + void (*done)(struct scsi_cmnd *)); +static int storvsc_device_alloc(struct scsi_device *); +static int storvsc_device_configure(struct scsi_device *); +static int storvsc_host_reset_handler(struct scsi_cmnd *scmnd); +static void storvsc_host_rescan_callback(struct work_struct *work); +static void storvsc_host_rescan(struct hv_device *device_obj); +static int storvsc_remove(struct device *dev); + +static struct scatterlist *create_bounce_buffer(struct scatterlist *sgl, + unsigned int sg_count, + unsigned int len); +static void destroy_bounce_buffer(struct scatterlist *sgl, + unsigned int sg_count); +static int do_bounce_buffer(struct scatterlist *sgl, unsigned int sg_count); +static unsigned int copy_from_bounce_buffer(struct scatterlist *orig_sgl, + struct scatterlist *bounce_sgl, + unsigned int orig_sgl_count); +static unsigned int copy_to_bounce_buffer(struct scatterlist *orig_sgl, + struct scatterlist *bounce_sgl, + unsigned int orig_sgl_count); + +static int storvsc_report_luns(struct scsi_device *sdev, unsigned int luns[], + unsigned int *lun_count); +static int storvsc_get_chs(struct scsi_device *sdev, struct block_device *bdev, + sector_t capacity, int *info); + + +static int storvsc_ringbuffer_size = STORVSC_RING_BUFFER_SIZE; + +/* The one and only one */ +static struct storvsc_driver_context g_storvsc_drv; + +/* Scsi driver */ +static struct scsi_host_template scsi_driver = { + .module = THIS_MODULE, + .name = "storvsc_host_t", + .bios_param = storvsc_get_chs, + .queuecommand = storvsc_queuecommand, + .eh_host_reset_handler = storvsc_host_reset_handler, + .slave_alloc = storvsc_device_alloc, + .slave_configure = storvsc_device_configure, + .cmd_per_lun = 1, + /* 64 max_queue * 1 target */ + .can_queue = STORVSC_MAX_IO_REQUESTS*STORVSC_MAX_TARGETS, + .this_id = -1, + /* no use setting to 0 since ll_blk_rw reset it to 1 */ + /* currently 32 */ + .sg_tablesize = MAX_MULTIPAGE_BUFFER_COUNT, + /* + * ENABLE_CLUSTERING allows mutiple physically contig bio_vecs to merge + * into 1 sg element. If set, we must limit the max_segment_size to + * PAGE_SIZE, otherwise we may get 1 sg element that represents + * multiple + */ + /* physically contig pfns (ie sg[x].length > PAGE_SIZE). */ + .use_clustering = ENABLE_CLUSTERING, + /* Make sure we dont get a sg segment crosses a page boundary */ + .dma_boundary = PAGE_SIZE-1, +}; + + +/** + * storvsc_drv_init - StorVsc driver initialization. + */ +static int storvsc_drv_init(int (*drv_init)(struct hv_driver *drv)) +{ + int ret; + struct storvsc_driver_object *storvsc_drv_obj = &g_storvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_storvsc_drv.drv_ctx; + + DPRINT_ENTER(STORVSC_DRV); + + vmbus_get_interface(&storvsc_drv_obj->Base.VmbusChannelInterface); + + storvsc_drv_obj->RingBufferSize = storvsc_ringbuffer_size; + storvsc_drv_obj->OnHostRescan = storvsc_host_rescan; + + /* Callback to client driver to complete the initialization */ + drv_init(&storvsc_drv_obj->Base); + + DPRINT_INFO(STORVSC_DRV, + "request extension size %u, max outstanding reqs %u", + storvsc_drv_obj->RequestExtSize, + storvsc_drv_obj->MaxOutstandingRequestsPerChannel); + + if (storvsc_drv_obj->MaxOutstandingRequestsPerChannel < + STORVSC_MAX_IO_REQUESTS) { + DPRINT_ERR(STORVSC_DRV, + "The number of outstanding io requests (%d) " + "is larger than that supported (%d) internally.", + STORVSC_MAX_IO_REQUESTS, + storvsc_drv_obj->MaxOutstandingRequestsPerChannel); + return -1; + } + + drv_ctx->driver.name = storvsc_drv_obj->Base.name; + memcpy(&drv_ctx->class_id, &storvsc_drv_obj->Base.deviceType, + sizeof(struct hv_guid)); + + drv_ctx->probe = storvsc_probe; + drv_ctx->remove = storvsc_remove; + + /* The driver belongs to vmbus */ + ret = vmbus_child_driver_register(drv_ctx); + + DPRINT_EXIT(STORVSC_DRV); + + return ret; +} + +static int storvsc_drv_exit_cb(struct device *dev, void *data) +{ + struct device **curr = (struct device **)data; + *curr = dev; + return 1; /* stop iterating */ +} + +static void storvsc_drv_exit(void) +{ + struct storvsc_driver_object *storvsc_drv_obj = &g_storvsc_drv.drv_obj; + struct driver_context *drv_ctx = &g_storvsc_drv.drv_ctx; + struct device *current_dev = NULL; + int ret; + + DPRINT_ENTER(STORVSC_DRV); + + while (1) { + current_dev = NULL; + + /* Get the device */ + ret = driver_for_each_device(&drv_ctx->driver, NULL, + (void *) ¤t_dev, + storvsc_drv_exit_cb); + + if (ret) + DPRINT_WARN(STORVSC_DRV, + "driver_for_each_device returned %d", ret); + + if (current_dev == NULL) + break; + + /* Initiate removal from the top-down */ + device_unregister(current_dev); + } + + if (storvsc_drv_obj->Base.OnCleanup) + storvsc_drv_obj->Base.OnCleanup(&storvsc_drv_obj->Base); + + vmbus_child_driver_unregister(drv_ctx); + + DPRINT_EXIT(STORVSC_DRV); + + return; +} + +/** + * storvsc_probe - Add a new device for this driver + */ +static int storvsc_probe(struct device *device) +{ + int ret; + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct storvsc_driver_context *storvsc_drv_ctx = + (struct storvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &storvsc_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct hv_device *device_obj = &device_ctx->device_obj; + struct Scsi_Host *host; + struct host_device_context *host_device_ctx; + struct storvsc_device_info device_info; + + DPRINT_ENTER(STORVSC_DRV); + + if (!storvsc_drv_obj->Base.OnDeviceAdd) + return -1; + + host = scsi_host_alloc(&scsi_driver, + sizeof(struct host_device_context)); + if (!host) { + DPRINT_ERR(STORVSC_DRV, "unable to allocate scsi host object"); + return -ENOMEM; + } + + dev_set_drvdata(device, host); + + host_device_ctx = (struct host_device_context *)host->hostdata; + memset(host_device_ctx, 0, sizeof(struct host_device_context)); + + host_device_ctx->port = host->host_no; + host_device_ctx->device_ctx = device_ctx; + + INIT_WORK(&host_device_ctx->host_rescan_work, + storvsc_host_rescan_callback); + + host_device_ctx->request_pool = + kmem_cache_create(dev_name(&device_ctx->device), + sizeof(struct storvsc_cmd_request) + + storvsc_drv_obj->RequestExtSize, 0, + SLAB_HWCACHE_ALIGN, NULL); + + if (!host_device_ctx->request_pool) { + scsi_host_put(host); + DPRINT_EXIT(STORVSC_DRV); + + return -ENOMEM; + } + + device_info.PortNumber = host->host_no; + /* Call to the vsc driver to add the device */ + ret = storvsc_drv_obj->Base.OnDeviceAdd(device_obj, + (void *)&device_info); + if (ret != 0) { + DPRINT_ERR(STORVSC_DRV, "unable to add scsi vsc device"); + kmem_cache_destroy(host_device_ctx->request_pool); + scsi_host_put(host); + DPRINT_EXIT(STORVSC_DRV); + + return -1; + } + + /* host_device_ctx->port = device_info.PortNumber; */ + host_device_ctx->path = device_info.PathId; + host_device_ctx->target = device_info.TargetId; + + /* max # of devices per target */ + host->max_lun = STORVSC_MAX_LUNS_PER_TARGET; + /* max # of targets per channel */ + host->max_id = STORVSC_MAX_TARGETS; + /* max # of channels */ + host->max_channel = STORVSC_MAX_CHANNELS - 1; + + /* Register the HBA and start the scsi bus scan */ + ret = scsi_add_host(host, device); + if (ret != 0) { + DPRINT_ERR(STORVSC_DRV, "unable to add scsi host device"); + + storvsc_drv_obj->Base.OnDeviceRemove(device_obj); + + kmem_cache_destroy(host_device_ctx->request_pool); + scsi_host_put(host); + DPRINT_EXIT(STORVSC_DRV); + + return -1; + } + + scsi_scan_host(host); + + DPRINT_EXIT(STORVSC_DRV); + + return ret; +} + +/** + * storvsc_remove - Callback when our device is removed + */ +static int storvsc_remove(struct device *device) +{ + int ret; + struct driver_context *driver_ctx = + driver_to_driver_context(device->driver); + struct storvsc_driver_context *storvsc_drv_ctx = + (struct storvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &storvsc_drv_ctx->drv_obj; + struct device_context *device_ctx = device_to_device_context(device); + struct hv_device *device_obj = &device_ctx->device_obj; + struct Scsi_Host *host = dev_get_drvdata(device); + struct host_device_context *host_device_ctx = + (struct host_device_context *)host->hostdata; + + + DPRINT_ENTER(STORVSC_DRV); + + if (!storvsc_drv_obj->Base.OnDeviceRemove) { + DPRINT_EXIT(STORVSC_DRV); + return -1; + } + + /* + * Call to the vsc driver to let it know that the device is being + * removed + */ + ret = storvsc_drv_obj->Base.OnDeviceRemove(device_obj); + if (ret != 0) { + /* TODO: */ + DPRINT_ERR(STORVSC, "unable to remove vsc device (ret %d)", + ret); + } + + if (host_device_ctx->request_pool) { + kmem_cache_destroy(host_device_ctx->request_pool); + host_device_ctx->request_pool = NULL; + } + + DPRINT_INFO(STORVSC, "removing host adapter (%p)...", host); + scsi_remove_host(host); + + DPRINT_INFO(STORVSC, "releasing host adapter (%p)...", host); + scsi_host_put(host); + + DPRINT_EXIT(STORVSC_DRV); + + return ret; +} + +/** + * storvsc_commmand_completion - Command completion processing + */ +static void storvsc_commmand_completion(struct hv_storvsc_request *request) +{ + struct storvsc_cmd_request *cmd_request = + (struct storvsc_cmd_request *)request->Context; + struct scsi_cmnd *scmnd = cmd_request->cmd; + struct host_device_context *host_device_ctx = + (struct host_device_context *)scmnd->device->host->hostdata; + void (*scsi_done_fn)(struct scsi_cmnd *); + struct scsi_sense_hdr sense_hdr; + + ASSERT(request == &cmd_request->request); + ASSERT((unsigned long)scmnd->host_scribble == + (unsigned long)cmd_request); + ASSERT(scmnd); + ASSERT(scmnd->scsi_done); + + DPRINT_ENTER(STORVSC_DRV); + + if (cmd_request->bounce_sgl_count) { + /* using bounce buffer */ + /* printk("copy_from_bounce_buffer\n"); */ + + /* FIXME: We can optimize on writes by just skipping this */ + copy_from_bounce_buffer(scsi_sglist(scmnd), + cmd_request->bounce_sgl, + scsi_sg_count(scmnd)); + destroy_bounce_buffer(cmd_request->bounce_sgl, + cmd_request->bounce_sgl_count); + } + + scmnd->result = request->Status; + + if (scmnd->result) { + if (scsi_normalize_sense(scmnd->sense_buffer, + request->SenseBufferSize, &sense_hdr)) + scsi_print_sense_hdr("storvsc", &sense_hdr); + } + + ASSERT(request->BytesXfer <= request->DataBuffer.Length); + scsi_set_resid(scmnd, request->DataBuffer.Length - request->BytesXfer); + + scsi_done_fn = scmnd->scsi_done; + + scmnd->host_scribble = NULL; + scmnd->scsi_done = NULL; + + /* !!DO NOT MODIFY the scmnd after this call */ + scsi_done_fn(scmnd); + + kmem_cache_free(host_device_ctx->request_pool, cmd_request); + + DPRINT_EXIT(STORVSC_DRV); +} + +static int do_bounce_buffer(struct scatterlist *sgl, unsigned int sg_count) +{ + int i; + + /* No need to check */ + if (sg_count < 2) + return -1; + + /* We have at least 2 sg entries */ + for (i = 0; i < sg_count; i++) { + if (i == 0) { + /* make sure 1st one does not have hole */ + if (sgl[i].offset + sgl[i].length != PAGE_SIZE) + return i; + } else if (i == sg_count - 1) { + /* make sure last one does not have hole */ + if (sgl[i].offset != 0) + return i; + } else { + /* make sure no hole in the middle */ + if (sgl[i].length != PAGE_SIZE || sgl[i].offset != 0) + return i; + } + } + return -1; +} + +static struct scatterlist *create_bounce_buffer(struct scatterlist *sgl, + unsigned int sg_count, + unsigned int len) +{ + int i; + int num_pages; + struct scatterlist *bounce_sgl; + struct page *page_buf; + + num_pages = ALIGN_UP(len, PAGE_SIZE) >> PAGE_SHIFT; + + bounce_sgl = kcalloc(num_pages, sizeof(struct scatterlist), GFP_ATOMIC); + if (!bounce_sgl) + return NULL; + + for (i = 0; i < num_pages; i++) { + page_buf = alloc_page(GFP_ATOMIC); + if (!page_buf) + goto cleanup; + sg_set_page(&bounce_sgl[i], page_buf, 0, 0); + } + + return bounce_sgl; + +cleanup: + destroy_bounce_buffer(bounce_sgl, num_pages); + return NULL; +} + +static void destroy_bounce_buffer(struct scatterlist *sgl, + unsigned int sg_count) +{ + int i; + struct page *page_buf; + + for (i = 0; i < sg_count; i++) { + page_buf = sg_page((&sgl[i])); + if (page_buf != NULL) + __free_page(page_buf); + } + + kfree(sgl); +} + +/* Assume the bounce_sgl has enough room ie using the create_bounce_buffer() */ +static unsigned int copy_to_bounce_buffer(struct scatterlist *orig_sgl, + struct scatterlist *bounce_sgl, + unsigned int orig_sgl_count) +{ + int i; + int j = 0; + unsigned long src, dest; + unsigned int srclen, destlen, copylen; + unsigned int total_copied = 0; + unsigned long bounce_addr = 0; + unsigned long src_addr = 0; + unsigned long flags; + + local_irq_save(flags); + + for (i = 0; i < orig_sgl_count; i++) { + src_addr = (unsigned long)kmap_atomic(sg_page((&orig_sgl[i])), + KM_IRQ0) + orig_sgl[i].offset; + src = src_addr; + srclen = orig_sgl[i].length; + + ASSERT(orig_sgl[i].offset + orig_sgl[i].length <= PAGE_SIZE); + + if (j == 0) + bounce_addr = (unsigned long)kmap_atomic(sg_page((&bounce_sgl[j])), KM_IRQ0); + + while (srclen) { + /* assume bounce offset always == 0 */ + dest = bounce_addr + bounce_sgl[j].length; + destlen = PAGE_SIZE - bounce_sgl[j].length; + + copylen = min(srclen, destlen); + memcpy((void *)dest, (void *)src, copylen); + + total_copied += copylen; + bounce_sgl[j].length += copylen; + srclen -= copylen; + src += copylen; + + if (bounce_sgl[j].length == PAGE_SIZE) { + /* full..move to next entry */ + kunmap_atomic((void *)bounce_addr, KM_IRQ0); + j++; + + /* if we need to use another bounce buffer */ + if (srclen || i != orig_sgl_count - 1) + bounce_addr = (unsigned long)kmap_atomic(sg_page((&bounce_sgl[j])), KM_IRQ0); + } else if (srclen == 0 && i == orig_sgl_count - 1) { + /* unmap the last bounce that is < PAGE_SIZE */ + kunmap_atomic((void *)bounce_addr, KM_IRQ0); + } + } + + kunmap_atomic((void *)(src_addr - orig_sgl[i].offset), KM_IRQ0); + } + + local_irq_restore(flags); + + return total_copied; +} + +/* Assume the original sgl has enough room */ +static unsigned int copy_from_bounce_buffer(struct scatterlist *orig_sgl, + struct scatterlist *bounce_sgl, + unsigned int orig_sgl_count) +{ + int i; + int j = 0; + unsigned long src, dest; + unsigned int srclen, destlen, copylen; + unsigned int total_copied = 0; + unsigned long bounce_addr = 0; + unsigned long dest_addr = 0; + unsigned long flags; + + local_irq_save(flags); + + for (i = 0; i < orig_sgl_count; i++) { + dest_addr = (unsigned long)kmap_atomic(sg_page((&orig_sgl[i])), + KM_IRQ0) + orig_sgl[i].offset; + dest = dest_addr; + destlen = orig_sgl[i].length; + ASSERT(orig_sgl[i].offset + orig_sgl[i].length <= PAGE_SIZE); + + if (j == 0) + bounce_addr = (unsigned long)kmap_atomic(sg_page((&bounce_sgl[j])), KM_IRQ0); + + while (destlen) { + src = bounce_addr + bounce_sgl[j].offset; + srclen = bounce_sgl[j].length - bounce_sgl[j].offset; + + copylen = min(srclen, destlen); + memcpy((void *)dest, (void *)src, copylen); + + total_copied += copylen; + bounce_sgl[j].offset += copylen; + destlen -= copylen; + dest += copylen; + + if (bounce_sgl[j].offset == bounce_sgl[j].length) { + /* full */ + kunmap_atomic((void *)bounce_addr, KM_IRQ0); + j++; + + /* if we need to use another bounce buffer */ + if (destlen || i != orig_sgl_count - 1) + bounce_addr = (unsigned long)kmap_atomic(sg_page((&bounce_sgl[j])), KM_IRQ0); + } else if (destlen == 0 && i == orig_sgl_count - 1) { + /* unmap the last bounce that is < PAGE_SIZE */ + kunmap_atomic((void *)bounce_addr, KM_IRQ0); + } + } + + kunmap_atomic((void *)(dest_addr - orig_sgl[i].offset), + KM_IRQ0); + } + + local_irq_restore(flags); + + return total_copied; +} + +/** + * storvsc_queuecommand - Initiate command processing + */ +static int storvsc_queuecommand(struct scsi_cmnd *scmnd, + void (*done)(struct scsi_cmnd *)) +{ + int ret; + struct host_device_context *host_device_ctx = + (struct host_device_context *)scmnd->device->host->hostdata; + struct device_context *device_ctx = host_device_ctx->device_ctx; + struct driver_context *driver_ctx = + driver_to_driver_context(device_ctx->device.driver); + struct storvsc_driver_context *storvsc_drv_ctx = + (struct storvsc_driver_context *)driver_ctx; + struct storvsc_driver_object *storvsc_drv_obj = + &storvsc_drv_ctx->drv_obj; + struct hv_storvsc_request *request; + struct storvsc_cmd_request *cmd_request; + unsigned int request_size = 0; + int i; + struct scatterlist *sgl; + + DPRINT_ENTER(STORVSC_DRV); + + DPRINT_DBG(STORVSC_DRV, "scmnd %p dir %d, use_sg %d buf %p len %d " + "queue depth %d tagged %d", scmnd, scmnd->sc_data_direction, + scsi_sg_count(scmnd), scsi_sglist(scmnd), + scsi_bufflen(scmnd), scmnd->device->queue_depth, + scmnd->device->tagged_supported); + + /* If retrying, no need to prep the cmd */ + if (scmnd->host_scribble) { + ASSERT(scmnd->scsi_done != NULL); + + cmd_request = + (struct storvsc_cmd_request *)scmnd->host_scribble; + DPRINT_INFO(STORVSC_DRV, "retrying scmnd %p cmd_request %p", + scmnd, cmd_request); + + goto retry_request; + } + + ASSERT(scmnd->scsi_done == NULL); + ASSERT(scmnd->host_scribble == NULL); + + scmnd->scsi_done = done; + + request_size = sizeof(struct storvsc_cmd_request); + + cmd_request = kmem_cache_alloc(host_device_ctx->request_pool, + GFP_ATOMIC); + if (!cmd_request) { + DPRINT_ERR(STORVSC_DRV, "scmnd (%p) - unable to allocate " + "storvsc_cmd_request...marking queue busy", scmnd); + scmnd->scsi_done = NULL; + return SCSI_MLQUEUE_DEVICE_BUSY; + } + + /* Setup the cmd request */ + cmd_request->bounce_sgl_count = 0; + cmd_request->bounce_sgl = NULL; + cmd_request->cmd = scmnd; + + scmnd->host_scribble = (unsigned char *)cmd_request; + + request = &cmd_request->request; + + request->Extension = + (void *)((unsigned long)cmd_request + request_size); + DPRINT_DBG(STORVSC_DRV, "req %p size %d ext %d", request, request_size, + storvsc_drv_obj->RequestExtSize); + + /* Build the SRB */ + switch (scmnd->sc_data_direction) { + case DMA_TO_DEVICE: + request->Type = WRITE_TYPE; + break; + case DMA_FROM_DEVICE: + request->Type = READ_TYPE; + break; + default: + request->Type = UNKNOWN_TYPE; + break; + } + + request->OnIOCompletion = storvsc_commmand_completion; + request->Context = cmd_request;/* scmnd; */ + + /* request->PortId = scmnd->device->channel; */ + request->Host = host_device_ctx->port; + request->Bus = scmnd->device->channel; + request->TargetId = scmnd->device->id; + request->LunId = scmnd->device->lun; + + ASSERT(scmnd->cmd_len <= 16); + request->CdbLen = scmnd->cmd_len; + request->Cdb = scmnd->cmnd; + + request->SenseBuffer = scmnd->sense_buffer; + request->SenseBufferSize = SCSI_SENSE_BUFFERSIZE; + + + request->DataBuffer.Length = scsi_bufflen(scmnd); + if (scsi_sg_count(scmnd)) { + sgl = (struct scatterlist *)scsi_sglist(scmnd); + + /* check if we need to bounce the sgl */ + if (do_bounce_buffer(sgl, scsi_sg_count(scmnd)) != -1) { + DPRINT_INFO(STORVSC_DRV, + "need to bounce buffer for this scmnd %p", + scmnd); + cmd_request->bounce_sgl = + create_bounce_buffer(sgl, scsi_sg_count(scmnd), + scsi_bufflen(scmnd)); + if (!cmd_request->bounce_sgl) { + DPRINT_ERR(STORVSC_DRV, + "unable to create bounce buffer for " + "this scmnd %p", scmnd); + + scmnd->scsi_done = NULL; + scmnd->host_scribble = NULL; + kmem_cache_free(host_device_ctx->request_pool, + cmd_request); + + return SCSI_MLQUEUE_HOST_BUSY; + } + + cmd_request->bounce_sgl_count = + ALIGN_UP(scsi_bufflen(scmnd), PAGE_SIZE) >> + PAGE_SHIFT; + + /* + * FIXME: We can optimize on reads by just skipping + * this + */ + copy_to_bounce_buffer(sgl, cmd_request->bounce_sgl, + scsi_sg_count(scmnd)); + + sgl = cmd_request->bounce_sgl; + } + + request->DataBuffer.Offset = sgl[0].offset; + + for (i = 0; i < scsi_sg_count(scmnd); i++) { + DPRINT_DBG(STORVSC_DRV, "sgl[%d] len %d offset %d \n", + i, sgl[i].length, sgl[i].offset); + request->DataBuffer.PfnArray[i] = + page_to_pfn(sg_page((&sgl[i]))); + } + } else if (scsi_sglist(scmnd)) { + ASSERT(scsi_bufflen(scmnd) <= PAGE_SIZE); + request->DataBuffer.Offset = + virt_to_phys(scsi_sglist(scmnd)) & (PAGE_SIZE-1); + request->DataBuffer.PfnArray[0] = + virt_to_phys(scsi_sglist(scmnd)) >> PAGE_SHIFT; + } else { + ASSERT(scsi_bufflen(scmnd) == 0); + } + +retry_request: + /* Invokes the vsc to start an IO */ + ret = storvsc_drv_obj->OnIORequest(&device_ctx->device_obj, + &cmd_request->request); + if (ret == -1) { + /* no more space */ + DPRINT_ERR(STORVSC_DRV, + "scmnd (%p) - queue FULL...marking queue busy", + scmnd); + + if (cmd_request->bounce_sgl_count) { + /* + * FIXME: We can optimize on writes by just skipping + * this + */ + copy_from_bounce_buffer(scsi_sglist(scmnd), + cmd_request->bounce_sgl, + scsi_sg_count(scmnd)); + destroy_bounce_buffer(cmd_request->bounce_sgl, + cmd_request->bounce_sgl_count); + } + + kmem_cache_free(host_device_ctx->request_pool, cmd_request); + + scmnd->scsi_done = NULL; + scmnd->host_scribble = NULL; + + ret = SCSI_MLQUEUE_DEVICE_BUSY; + } + + DPRINT_EXIT(STORVSC_DRV); + + return ret; +} + +static int storvsc_merge_bvec(struct request_queue *q, + struct bvec_merge_data *bmd, struct bio_vec *bvec) +{ + /* checking done by caller. */ + return bvec->bv_len; +} + +/** + * storvsc_device_configure - Configure the specified scsi device + */ +static int storvsc_device_alloc(struct scsi_device *sdevice) +{ + DPRINT_DBG(STORVSC_DRV, "sdev (%p) - setting device flag to %d", + sdevice, BLIST_SPARSELUN); + /* + * This enables luns to be located sparsely. Otherwise, we may not + * discovered them. + */ + sdevice->sdev_bflags |= BLIST_SPARSELUN | BLIST_LARGELUN; + return 0; +} + +static int storvsc_device_configure(struct scsi_device *sdevice) +{ + DPRINT_INFO(STORVSC_DRV, "sdev (%p) - curr queue depth %d", sdevice, + sdevice->queue_depth); + + DPRINT_INFO(STORVSC_DRV, "sdev (%p) - setting queue depth to %d", + sdevice, STORVSC_MAX_IO_REQUESTS); + scsi_adjust_queue_depth(sdevice, MSG_SIMPLE_TAG, + STORVSC_MAX_IO_REQUESTS); + + DPRINT_INFO(STORVSC_DRV, "sdev (%p) - setting max segment size to %ld", + sdevice, PAGE_SIZE); + blk_queue_max_segment_size(sdevice->request_queue, PAGE_SIZE); + + DPRINT_INFO(STORVSC_DRV, "sdev (%p) - adding merge bio vec routine", + sdevice); + blk_queue_merge_bvec(sdevice->request_queue, storvsc_merge_bvec); + + blk_queue_bounce_limit(sdevice->request_queue, BLK_BOUNCE_ANY); + /* sdevice->timeout = (2000 * HZ);//(75 * HZ); */ + + return 0; +} + +/** + * storvsc_host_reset_handler - Reset the scsi HBA + */ +static int storvsc_host_reset_handler(struct scsi_cmnd *scmnd) +{ + int ret; + struct host_device_context *host_device_ctx = + (struct host_device_context *)scmnd->device->host->hostdata; + struct device_context *device_ctx = host_device_ctx->device_ctx; + struct driver_context *driver_ctx = + driver_to_driver_context(device_ctx->device.driver); + struct storvsc_driver_context *storvsc_drv_ctx = + (struct storvsc_driver_context *)driver_ctx; + + struct storvsc_driver_object *storvsc_drv_obj = + &storvsc_drv_ctx->drv_obj; + + DPRINT_ENTER(STORVSC_DRV); + + DPRINT_INFO(STORVSC_DRV, "sdev (%p) dev obj (%p) - host resetting...", + scmnd->device, &device_ctx->device_obj); + + /* Invokes the vsc to reset the host/bus */ + ASSERT(storvsc_drv_obj->OnHostReset); + ret = storvsc_drv_obj->OnHostReset(&device_ctx->device_obj); + if (ret != 0) { + DPRINT_EXIT(STORVSC_DRV); + return ret; + } + + DPRINT_INFO(STORVSC_DRV, "sdev (%p) dev obj (%p) - host reseted", + scmnd->device, &device_ctx->device_obj); + + DPRINT_EXIT(STORVSC_DRV); + + return ret; +} + +/** + * storvsc_host_rescan - Rescan the scsi HBA + */ +static void storvsc_host_rescan_callback(struct work_struct *work) +{ + struct hv_device *device_obj = + &((struct host_device_context *)work)->device_ctx->device_obj; + struct device_context *device_ctx = to_device_context(device_obj); + struct Scsi_Host *host = dev_get_drvdata(&device_ctx->device); + struct scsi_device *sdev; + struct host_device_context *host_device_ctx; + struct scsi_device **sdevs_remove_list; + unsigned int sdevs_count = 0; + unsigned int found; + unsigned int i; + unsigned int lun_count = 0; + unsigned int *lun_list; + + DPRINT_ENTER(STORVSC_DRV); + + host_device_ctx = (struct host_device_context *)host->hostdata; + lun_list = kcalloc(STORVSC_MAX_LUNS_PER_TARGET, sizeof(unsigned int), + GFP_ATOMIC); + if (!lun_list) { + DPRINT_ERR(STORVSC_DRV, "unable to allocate lun list"); + return; + } + + sdevs_remove_list = kcalloc(STORVSC_MAX_LUNS_PER_TARGET, + sizeof(void *), GFP_ATOMIC); + if (!sdevs_remove_list) { + kfree(lun_list); + DPRINT_ERR(STORVSC_DRV, "unable to allocate lun remove list"); + return; + } + + DPRINT_INFO(STORVSC_DRV, "rescanning host for new scsi devices..."); + + /* Rescan for new device */ + scsi_scan_target(&host->shost_gendev, host_device_ctx->path, + host_device_ctx->target, SCAN_WILD_CARD, 1); + + DPRINT_INFO(STORVSC_DRV, "rescanning host for removed scsi device..."); + + /* Use the 1st device to send the report luns cmd */ + shost_for_each_device(sdev, host) { + lun_count = STORVSC_MAX_LUNS_PER_TARGET; + storvsc_report_luns(sdev, lun_list, &lun_count); + + DPRINT_INFO(STORVSC_DRV, + "report luns on scsi device (%p) found %u luns ", + sdev, lun_count); + DPRINT_INFO(STORVSC_DRV, + "existing luns on scsi device (%p) host (%d)", + sdev, host->host_no); + + scsi_device_put(sdev); + break; + } + + for (i = 0; i < lun_count; i++) + DPRINT_INFO(STORVSC_DRV, "%d) lun %u", i, lun_list[i]); + + /* Rescan for devices that may have been removed. + * We do not have to worry that new devices may have been added since + * this callback is serialized by the workqueue ie add/remove are done + * here. + */ + shost_for_each_device(sdev, host) { + /* See if this device is still here */ + found = 0; + for (i = 0; i < lun_count; i++) { + if (sdev->lun == lun_list[i]) { + found = 1; + break; + } + } + if (!found) { + DPRINT_INFO(STORVSC_DRV, "lun (%u) does not exists", + sdev->lun); + sdevs_remove_list[sdevs_count++] = sdev; + } + } + + /* Now remove the devices */ + for (i = 0; i < sdevs_count; i++) { + DPRINT_INFO(STORVSC_DRV, + "removing scsi device (%p) lun (%u)...", + sdevs_remove_list[i], sdevs_remove_list[i]->lun); + + /* make sure it is not removed from underneath us */ + if (!scsi_device_get(sdevs_remove_list[i])) { + scsi_remove_device(sdevs_remove_list[i]); + scsi_device_put(sdevs_remove_list[i]); + } + } + + DPRINT_INFO(STORVSC_DRV, "rescan completed on dev obj (%p) " + "target (%u) bus (%u)", device_obj, + host_device_ctx->target, host_device_ctx->path); + + kfree(lun_list); + kfree(sdevs_remove_list); + + DPRINT_EXIT(STORVSC_DRV); +} + +static int storvsc_report_luns(struct scsi_device *sdev, unsigned int luns[], + unsigned int *lun_count) +{ + int i, j; + unsigned int lun = 0; + unsigned int num_luns; + int result; + unsigned char *data; + struct scsi_sense_hdr sshdr; + unsigned char cmd[16] = {0}; + /* Add 1 to cover the report_lun header */ + unsigned int report_len = 8 * (STORVSC_MAX_LUNS_PER_TARGET+1); + unsigned long long *report_luns; + const unsigned int in_lun_count = *lun_count; + + *lun_count = 0; + + report_luns = kzalloc(report_len, GFP_ATOMIC); + if (!report_luns) + return -ENOMEM; + + cmd[0] = REPORT_LUNS; + + /* cmd length */ + *(unsigned int *)&cmd[6] = cpu_to_be32(report_len); + + result = scsi_execute_req(sdev, cmd, DMA_FROM_DEVICE, + (unsigned char *)report_luns, report_len, + &sshdr, 30 * HZ, 3, NULL); + if (result != 0) { + kfree(report_luns); + return -EBUSY; + } + + /* get the length from the first four bytes */ + report_len = be32_to_cpu(*(unsigned int *)&report_luns[0]); + + num_luns = (report_len / sizeof(unsigned long long)); + if (num_luns > in_lun_count) { + kfree(report_luns); + return -EINVAL; + } + + *lun_count = num_luns; + + DPRINT_DBG(STORVSC_DRV, + "report luns on scsi device (%p) found %u luns ", + sdev, num_luns); + + /* lun id starts at 1 */ + for (i = 1; i < num_luns + 1; i++) { + lun = 0; + data = (unsigned char *)&report_luns[i]; + for (j = 0; j < sizeof(lun); j += 2) { + lun = lun | (((data[j] << 8) | data[j + 1]) << + (j * 8)); + } + + luns[i-1] = lun; + } + + kfree(report_luns); + return 0; +} + +static void storvsc_host_rescan(struct hv_device *device_obj) +{ + struct device_context *device_ctx = to_device_context(device_obj); + struct Scsi_Host *host = dev_get_drvdata(&device_ctx->device); + struct host_device_context *host_device_ctx; + + DPRINT_ENTER(STORVSC_DRV); + + host_device_ctx = (struct host_device_context *)host->hostdata; + + DPRINT_INFO(STORVSC_DRV, "initiating rescan on dev obj (%p) " + "target (%u) bus (%u)...", device_obj, + host_device_ctx->target, host_device_ctx->path); + + /* + * We need to queue this since the scanning may block and the caller + * may be in an intr context + */ + /* scsi_queue_work(host, &host_device_ctx->host_rescan_work); */ + schedule_work(&host_device_ctx->host_rescan_work); + DPRINT_EXIT(STORVSC_DRV); +} + +static int storvsc_get_chs(struct scsi_device *sdev, struct block_device * bdev, + sector_t capacity, int *info) +{ + sector_t total_sectors = capacity; + sector_t cylinder_times_heads = 0; + sector_t temp = 0; + + int sectors_per_track = 0; + int heads = 0; + int cylinders = 0; + int rem = 0; + + if (total_sectors > (65535 * 16 * 255)) + total_sectors = (65535 * 16 * 255); + + if (total_sectors >= (65535 * 16 * 63)) { + sectors_per_track = 255; + heads = 16; + + cylinder_times_heads = total_sectors; + /* sector_div stores the quotient in cylinder_times_heads */ + rem = sector_div(cylinder_times_heads, sectors_per_track); + } else { + sectors_per_track = 17; + + cylinder_times_heads = total_sectors; + /* sector_div stores the quotient in cylinder_times_heads */ + rem = sector_div(cylinder_times_heads, sectors_per_track); + + temp = cylinder_times_heads + 1023; + /* sector_div stores the quotient in temp */ + rem = sector_div(temp, 1024); + + heads = temp; + + if (heads < 4) + heads = 4; + + if (cylinder_times_heads >= (heads * 1024) || (heads > 16)) { + sectors_per_track = 31; + heads = 16; + + cylinder_times_heads = total_sectors; + /* + * sector_div stores the quotient in + * cylinder_times_heads + */ + rem = sector_div(cylinder_times_heads, + sectors_per_track); + } + + if (cylinder_times_heads >= (heads * 1024)) { + sectors_per_track = 63; + heads = 16; + + cylinder_times_heads = total_sectors; + /* + * sector_div stores the quotient in + * cylinder_times_heads + */ + rem = sector_div(cylinder_times_heads, + sectors_per_track); + } + } + + temp = cylinder_times_heads; + /* sector_div stores the quotient in temp */ + rem = sector_div(temp, heads); + cylinders = temp; + + info[0] = heads; + info[1] = sectors_per_track; + info[2] = cylinders; + + DPRINT_INFO(STORVSC_DRV, "CHS (%d, %d, %d)", cylinders, heads, + sectors_per_track); + + return 0; +} + +static int __init storvsc_init(void) +{ + int ret; + + DPRINT_ENTER(STORVSC_DRV); + DPRINT_INFO(STORVSC_DRV, "Storvsc initializing...."); + ret = storvsc_drv_init(StorVscInitialize); + DPRINT_EXIT(STORVSC_DRV); + return ret; +} + +static void __exit storvsc_exit(void) +{ + DPRINT_ENTER(STORVSC_DRV); + storvsc_drv_exit(); + DPRINT_ENTER(STORVSC_DRV); +} + +MODULE_LICENSE("GPL"); +module_param(storvsc_ringbuffer_size, int, S_IRUGO); +module_init(storvsc_init); +module_exit(storvsc_exit); diff --git a/drivers/staging/hv/vmbus.h b/drivers/staging/hv/vmbus.h new file mode 100644 index 00000000000..ae0a896eb39 --- /dev/null +++ b/drivers/staging/hv/vmbus.h @@ -0,0 +1,77 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + + +#ifndef _VMBUS_H_ +#define _VMBUS_H_ + +#include <linux/device.h> +#include "VmbusApi.h" + +struct driver_context { + struct hv_guid class_id; + + struct device_driver driver; + + /* + * Use these methods instead of the struct device_driver so 2.6 kernel + * stops complaining + * TODO - fix this! + */ + int (*probe)(struct device *); + int (*remove)(struct device *); + void (*shutdown)(struct device *); +}; + +struct device_context { + struct work_struct probe_failed_work_item; + struct hv_guid class_id; + struct hv_guid device_id; + int probe_error; + struct device device; + struct hv_device device_obj; +}; + +static inline struct device_context *to_device_context(struct hv_device *d) +{ + return container_of(d, struct device_context, device_obj); +} + +static inline struct device_context *device_to_device_context(struct device *d) +{ + return container_of(d, struct device_context, device); +} + +static inline struct driver_context *driver_to_driver_context(struct device_driver *d) +{ + return container_of(d, struct driver_context, driver); +} + + +/* Vmbus interface */ + +int vmbus_child_driver_register(struct driver_context *driver_ctx); +void vmbus_child_driver_unregister(struct driver_context *driver_ctx); +void vmbus_get_interface(struct vmbus_channel_interface *interface); + +#endif /* _VMBUS_H_ */ diff --git a/drivers/staging/hv/vmbus_drv.c b/drivers/staging/hv/vmbus_drv.c new file mode 100644 index 00000000000..582318f1022 --- /dev/null +++ b/drivers/staging/hv/vmbus_drv.c @@ -0,0 +1,999 @@ +/* + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + */ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/sysctl.h> +#include "osd.h" +#include "logging.h" +#include "vmbus.h" + + +/* FIXME! We need to do this dynamically for PIC and APIC system */ +#define VMBUS_IRQ 0x5 +#define VMBUS_IRQ_VECTOR IRQ5_VECTOR + +/* Main vmbus driver data structure */ +struct vmbus_driver_context { + /* !! These must be the first 2 fields !! */ + /* FIXME, this is a bug */ + /* The driver field is not used in here. Instead, the bus field is */ + /* used to represent the driver */ + struct driver_context drv_ctx; + struct vmbus_driver drv_obj; + + struct bus_type bus; + struct tasklet_struct msg_dpc; + struct tasklet_struct event_dpc; + + /* The bus root device */ + struct device_context device_ctx; +}; + +static int vmbus_match(struct device *device, struct device_driver *driver); +static int vmbus_probe(struct device *device); +static int vmbus_remove(struct device *device); +static void vmbus_shutdown(struct device *device); +static int vmbus_uevent(struct device *device, struct kobj_uevent_env *env); +static void vmbus_msg_dpc(unsigned long data); +static void vmbus_event_dpc(unsigned long data); + +static irqreturn_t vmbus_isr(int irq, void *dev_id); + +static void vmbus_device_release(struct device *device); +static void vmbus_bus_release(struct device *device); + +static struct hv_device *vmbus_child_device_create(struct hv_guid *type, + struct hv_guid *instance, + void *context); +static void vmbus_child_device_destroy(struct hv_device *device_obj); +static int vmbus_child_device_register(struct hv_device *root_device_obj, + struct hv_device *child_device_obj); +static void vmbus_child_device_unregister(struct hv_device *child_device_obj); +static void vmbus_child_device_get_info(struct hv_device *device_obj, + struct hv_device_info *device_info); +static ssize_t vmbus_show_device_attr(struct device *dev, + struct device_attribute *dev_attr, + char *buf); + + +unsigned int vmbus_loglevel = (ALL_MODULES << 16 | INFO_LVL); +EXPORT_SYMBOL(vmbus_loglevel); + /* (ALL_MODULES << 16 | DEBUG_LVL_ENTEREXIT); */ + /* (((VMBUS | VMBUS_DRV)<<16) | DEBUG_LVL_ENTEREXIT); */ + +static int vmbus_irq = VMBUS_IRQ; + +/* Set up per device attributes in /sys/bus/vmbus/devices/<bus device> */ +static struct device_attribute vmbus_device_attrs[] = { + __ATTR(id, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(state, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(class_id, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(device_id, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(monitor_id, S_IRUGO, vmbus_show_device_attr, NULL), + + __ATTR(server_monitor_pending, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(server_monitor_latency, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(server_monitor_conn_id, S_IRUGO, vmbus_show_device_attr, NULL), + + __ATTR(client_monitor_pending, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(client_monitor_latency, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(client_monitor_conn_id, S_IRUGO, vmbus_show_device_attr, NULL), + + __ATTR(out_intr_mask, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(out_read_index, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(out_write_index, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(out_read_bytes_avail, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(out_write_bytes_avail, S_IRUGO, vmbus_show_device_attr, NULL), + + __ATTR(in_intr_mask, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(in_read_index, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(in_write_index, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(in_read_bytes_avail, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR(in_write_bytes_avail, S_IRUGO, vmbus_show_device_attr, NULL), + __ATTR_NULL +}; + +/* The one and only one */ +static struct vmbus_driver_context g_vmbus_drv = { + .bus.name = "vmbus", + .bus.match = vmbus_match, + .bus.shutdown = vmbus_shutdown, + .bus.remove = vmbus_remove, + .bus.probe = vmbus_probe, + .bus.uevent = vmbus_uevent, + .bus.dev_attrs = vmbus_device_attrs, +}; + +/** + * vmbus_show_device_attr - Show the device attribute in sysfs. + * + * This is invoked when user does a + * "cat /sys/bus/vmbus/devices/<busdevice>/<attr name>" + */ +static ssize_t vmbus_show_device_attr(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct device_context *device_ctx = device_to_device_context(dev); + struct hv_device_info device_info; + + memset(&device_info, 0, sizeof(struct hv_device_info)); + + vmbus_child_device_get_info(&device_ctx->device_obj, &device_info); + + if (!strcmp(dev_attr->attr.name, "class_id")) { + return sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}\n", + device_info.ChannelType.data[3], + device_info.ChannelType.data[2], + device_info.ChannelType.data[1], + device_info.ChannelType.data[0], + device_info.ChannelType.data[5], + device_info.ChannelType.data[4], + device_info.ChannelType.data[7], + device_info.ChannelType.data[6], + device_info.ChannelType.data[8], + device_info.ChannelType.data[9], + device_info.ChannelType.data[10], + device_info.ChannelType.data[11], + device_info.ChannelType.data[12], + device_info.ChannelType.data[13], + device_info.ChannelType.data[14], + device_info.ChannelType.data[15]); + } else if (!strcmp(dev_attr->attr.name, "device_id")) { + return sprintf(buf, "{%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}\n", + device_info.ChannelInstance.data[3], + device_info.ChannelInstance.data[2], + device_info.ChannelInstance.data[1], + device_info.ChannelInstance.data[0], + device_info.ChannelInstance.data[5], + device_info.ChannelInstance.data[4], + device_info.ChannelInstance.data[7], + device_info.ChannelInstance.data[6], + device_info.ChannelInstance.data[8], + device_info.ChannelInstance.data[9], + device_info.ChannelInstance.data[10], + device_info.ChannelInstance.data[11], + device_info.ChannelInstance.data[12], + device_info.ChannelInstance.data[13], + device_info.ChannelInstance.data[14], + device_info.ChannelInstance.data[15]); + } else if (!strcmp(dev_attr->attr.name, "state")) { + return sprintf(buf, "%d\n", device_info.ChannelState); + } else if (!strcmp(dev_attr->attr.name, "id")) { + return sprintf(buf, "%d\n", device_info.ChannelId); + } else if (!strcmp(dev_attr->attr.name, "out_intr_mask")) { + return sprintf(buf, "%d\n", device_info.Outbound.InterruptMask); + } else if (!strcmp(dev_attr->attr.name, "out_read_index")) { + return sprintf(buf, "%d\n", device_info.Outbound.ReadIndex); + } else if (!strcmp(dev_attr->attr.name, "out_write_index")) { + return sprintf(buf, "%d\n", device_info.Outbound.WriteIndex); + } else if (!strcmp(dev_attr->attr.name, "out_read_bytes_avail")) { + return sprintf(buf, "%d\n", + device_info.Outbound.BytesAvailToRead); + } else if (!strcmp(dev_attr->attr.name, "out_write_bytes_avail")) { + return sprintf(buf, "%d\n", + device_info.Outbound.BytesAvailToWrite); + } else if (!strcmp(dev_attr->attr.name, "in_intr_mask")) { + return sprintf(buf, "%d\n", device_info.Inbound.InterruptMask); + } else if (!strcmp(dev_attr->attr.name, "in_read_index")) { + return sprintf(buf, "%d\n", device_info.Inbound.ReadIndex); + } else if (!strcmp(dev_attr->attr.name, "in_write_index")) { + return sprintf(buf, "%d\n", device_info.Inbound.WriteIndex); + } else if (!strcmp(dev_attr->attr.name, "in_read_bytes_avail")) { + return sprintf(buf, "%d\n", + device_info.Inbound.BytesAvailToRead); + } else if (!strcmp(dev_attr->attr.name, "in_write_bytes_avail")) { + return sprintf(buf, "%d\n", + device_info.Inbound.BytesAvailToWrite); + } else if (!strcmp(dev_attr->attr.name, "monitor_id")) { + return sprintf(buf, "%d\n", device_info.MonitorId); + } else if (!strcmp(dev_attr->attr.name, "server_monitor_pending")) { + return sprintf(buf, "%d\n", device_info.ServerMonitorPending); + } else if (!strcmp(dev_attr->attr.name, "server_monitor_latency")) { + return sprintf(buf, "%d\n", device_info.ServerMonitorLatency); + } else if (!strcmp(dev_attr->attr.name, "server_monitor_conn_id")) { + return sprintf(buf, "%d\n", + device_info.ServerMonitorConnectionId); + } else if (!strcmp(dev_attr->attr.name, "client_monitor_pending")) { + return sprintf(buf, "%d\n", device_info.ClientMonitorPending); + } else if (!strcmp(dev_attr->attr.name, "client_monitor_latency")) { + return sprintf(buf, "%d\n", device_info.ClientMonitorLatency); + } else if (!strcmp(dev_attr->attr.name, "client_monitor_conn_id")) { + return sprintf(buf, "%d\n", + device_info.ClientMonitorConnectionId); + } else { + return 0; + } +} + +/** + * vmbus_bus_init -Main vmbus driver initialization routine. + * + * Here, we + * - initialize the vmbus driver context + * - setup various driver entry points + * - invoke the vmbus hv main init routine + * - get the irq resource + * - invoke the vmbus to add the vmbus root device + * - setup the vmbus root device + * - retrieve the channel offers + */ +static int vmbus_bus_init(int (*drv_init)(struct hv_driver *drv)) +{ + struct vmbus_driver_context *vmbus_drv_ctx = &g_vmbus_drv; + struct vmbus_driver *vmbus_drv_obj = &g_vmbus_drv.drv_obj; + struct device_context *dev_ctx = &g_vmbus_drv.device_ctx; + int ret; + unsigned int vector; + + DPRINT_ENTER(VMBUS_DRV); + + /* + * Set this up to allow lower layer to callback to add/remove child + * devices on the bus + */ + vmbus_drv_obj->OnChildDeviceCreate = vmbus_child_device_create; + vmbus_drv_obj->OnChildDeviceDestroy = vmbus_child_device_destroy; + vmbus_drv_obj->OnChildDeviceAdd = vmbus_child_device_register; + vmbus_drv_obj->OnChildDeviceRemove = vmbus_child_device_unregister; + + /* Call to bus driver to initialize */ + ret = drv_init(&vmbus_drv_obj->Base); + if (ret != 0) { + DPRINT_ERR(VMBUS_DRV, "Unable to initialize vmbus (%d)", ret); + goto cleanup; + } + + /* Sanity checks */ + if (!vmbus_drv_obj->Base.OnDeviceAdd) { + DPRINT_ERR(VMBUS_DRV, "OnDeviceAdd() routine not set"); + ret = -1; + goto cleanup; + } + + vmbus_drv_ctx->bus.name = vmbus_drv_obj->Base.name; + + /* Initialize the bus context */ + tasklet_init(&vmbus_drv_ctx->msg_dpc, vmbus_msg_dpc, + (unsigned long)vmbus_drv_obj); + tasklet_init(&vmbus_drv_ctx->event_dpc, vmbus_event_dpc, + (unsigned long)vmbus_drv_obj); + + /* Now, register the bus driver with LDM */ + ret = bus_register(&vmbus_drv_ctx->bus); + if (ret) { + ret = -1; + goto cleanup; + } + + /* Get the interrupt resource */ + ret = request_irq(vmbus_irq, vmbus_isr, IRQF_SAMPLE_RANDOM, + vmbus_drv_obj->Base.name, NULL); + + if (ret != 0) { + DPRINT_ERR(VMBUS_DRV, "ERROR - Unable to request IRQ %d", + vmbus_irq); + + bus_unregister(&vmbus_drv_ctx->bus); + + ret = -1; + goto cleanup; + } + vector = VMBUS_IRQ_VECTOR; + + DPRINT_INFO(VMBUS_DRV, "irq 0x%x vector 0x%x", vmbus_irq, vector); + + /* Call to bus driver to add the root device */ + memset(dev_ctx, 0, sizeof(struct device_context)); + + ret = vmbus_drv_obj->Base.OnDeviceAdd(&dev_ctx->device_obj, &vector); + if (ret != 0) { + DPRINT_ERR(VMBUS_DRV, + "ERROR - Unable to add vmbus root device"); + + free_irq(vmbus_irq, NULL); + + bus_unregister(&vmbus_drv_ctx->bus); + + ret = -1; + goto cleanup; + } + /* strcpy(dev_ctx->device.bus_id, dev_ctx->device_obj.name); */ + dev_set_name(&dev_ctx->device, "vmbus_0_0"); + memcpy(&dev_ctx->class_id, &dev_ctx->device_obj.deviceType, + sizeof(struct hv_guid)); + memcpy(&dev_ctx->device_id, &dev_ctx->device_obj.deviceInstance, + sizeof(struct hv_guid)); + + /* No need to bind a driver to the root device. */ + dev_ctx->device.parent = NULL; + /* NULL; vmbus_remove() does not get invoked */ + dev_ctx->device.bus = &vmbus_drv_ctx->bus; + + /* Setup the device dispatch table */ + dev_ctx->device.release = vmbus_bus_release; + + /* Setup the bus as root device */ + ret = device_register(&dev_ctx->device); + if (ret) { + DPRINT_ERR(VMBUS_DRV, + "ERROR - Unable to register vmbus root device"); + + free_irq(vmbus_irq, NULL); + bus_unregister(&vmbus_drv_ctx->bus); + + ret = -1; + goto cleanup; + } + + + vmbus_drv_obj->GetChannelOffers(); + +cleanup: + DPRINT_EXIT(VMBUS_DRV); + + return ret; +} + +/** + * vmbus_bus_exit - Terminate the vmbus driver. + * + * This routine is opposite of vmbus_bus_init() + */ +static void vmbus_bus_exit(void) +{ + struct vmbus_driver *vmbus_drv_obj = &g_vmbus_drv.drv_obj; + struct vmbus_driver_context *vmbus_drv_ctx = &g_vmbus_drv; + + struct device_context *dev_ctx = &g_vmbus_drv.device_ctx; + + DPRINT_ENTER(VMBUS_DRV); + + /* Remove the root device */ + if (vmbus_drv_obj->Base.OnDeviceRemove) + vmbus_drv_obj->Base.OnDeviceRemove(&dev_ctx->device_obj); + + if (vmbus_drv_obj->Base.OnCleanup) + vmbus_drv_obj->Base.OnCleanup(&vmbus_drv_obj->Base); + + /* Unregister the root bus device */ + device_unregister(&dev_ctx->device); + + bus_unregister(&vmbus_drv_ctx->bus); + + free_irq(vmbus_irq, NULL); + + tasklet_kill(&vmbus_drv_ctx->msg_dpc); + tasklet_kill(&vmbus_drv_ctx->event_dpc); + + DPRINT_EXIT(VMBUS_DRV); + + return; +} + +/** + * vmbus_child_driver_register - Register a vmbus's child driver + */ +int vmbus_child_driver_register(struct driver_context *driver_ctx) +{ + struct vmbus_driver *vmbus_drv_obj = &g_vmbus_drv.drv_obj; + int ret; + + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_INFO(VMBUS_DRV, "child driver (%p) registering - name %s", + driver_ctx, driver_ctx->driver.name); + + /* The child driver on this vmbus */ + driver_ctx->driver.bus = &g_vmbus_drv.bus; + + ret = driver_register(&driver_ctx->driver); + + vmbus_drv_obj->GetChannelOffers(); + + DPRINT_EXIT(VMBUS_DRV); + + return ret; +} +EXPORT_SYMBOL(vmbus_child_driver_register); + +/** + * vmbus_child_driver_unregister Unregister a vmbus's child driver + */ +void vmbus_child_driver_unregister(struct driver_context *driver_ctx) +{ + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_INFO(VMBUS_DRV, "child driver (%p) unregistering - name %s", + driver_ctx, driver_ctx->driver.name); + + driver_unregister(&driver_ctx->driver); + + driver_ctx->driver.bus = NULL; + + DPRINT_EXIT(VMBUS_DRV); +} +EXPORT_SYMBOL(vmbus_child_driver_unregister); + +/** + * vmbus_get_interface - Get the vmbus channel interface. + * + * This is invoked by child/client driver that sits above vmbus + */ +void vmbus_get_interface(struct vmbus_channel_interface *interface) +{ + struct vmbus_driver *vmbus_drv_obj = &g_vmbus_drv.drv_obj; + + vmbus_drv_obj->GetChannelInterface(interface); +} +EXPORT_SYMBOL(vmbus_get_interface); + +/** + * vmbus_child_device_get_info - Get the vmbus child device info. + * + * This is invoked to display various device attributes in sysfs. + */ +static void vmbus_child_device_get_info(struct hv_device *device_obj, + struct hv_device_info *device_info) +{ + struct vmbus_driver *vmbus_drv_obj = &g_vmbus_drv.drv_obj; + + vmbus_drv_obj->GetChannelInfo(device_obj, device_info); +} + +/** + * vmbus_child_device_create - Creates and registers a new child device on the vmbus. + */ +static struct hv_device *vmbus_child_device_create(struct hv_guid *type, + struct hv_guid *instance, + void *context) +{ + struct device_context *child_device_ctx; + struct hv_device *child_device_obj; + + DPRINT_ENTER(VMBUS_DRV); + + /* Allocate the new child device */ + child_device_ctx = kzalloc(sizeof(struct device_context), GFP_KERNEL); + if (!child_device_ctx) { + DPRINT_ERR(VMBUS_DRV, + "unable to allocate device_context for child device"); + DPRINT_EXIT(VMBUS_DRV); + + return NULL; + } + + DPRINT_DBG(VMBUS_DRV, "child device (%p) allocated - " + "type {%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}," + "id {%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}", + &child_device_ctx->device, + type->data[3], type->data[2], type->data[1], type->data[0], + type->data[5], type->data[4], type->data[7], type->data[6], + type->data[8], type->data[9], type->data[10], type->data[11], + type->data[12], type->data[13], type->data[14], type->data[15], + instance->data[3], instance->data[2], + instance->data[1], instance->data[0], + instance->data[5], instance->data[4], + instance->data[7], instance->data[6], + instance->data[8], instance->data[9], + instance->data[10], instance->data[11], + instance->data[12], instance->data[13], + instance->data[14], instance->data[15]); + + child_device_obj = &child_device_ctx->device_obj; + child_device_obj->context = context; + memcpy(&child_device_obj->deviceType, &type, sizeof(struct hv_guid)); + memcpy(&child_device_obj->deviceInstance, &instance, + sizeof(struct hv_guid)); + + memcpy(&child_device_ctx->class_id, &type, sizeof(struct hv_guid)); + memcpy(&child_device_ctx->device_id, &instance, sizeof(struct hv_guid)); + + DPRINT_EXIT(VMBUS_DRV); + + return child_device_obj; +} + +/** + * vmbus_child_device_register - Register the child device on the specified bus + */ +static int vmbus_child_device_register(struct hv_device *root_device_obj, + struct hv_device *child_device_obj) +{ + int ret = 0; + struct device_context *root_device_ctx = + to_device_context(root_device_obj); + struct device_context *child_device_ctx = + to_device_context(child_device_obj); + static atomic_t device_num = ATOMIC_INIT(0); + + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_DBG(VMBUS_DRV, "child device (%p) registering", + child_device_ctx); + + /* Make sure we are not registered already */ + if (strlen(dev_name(&child_device_ctx->device)) != 0) { + DPRINT_ERR(VMBUS_DRV, + "child device (%p) already registered - busid %s", + child_device_ctx, + dev_name(&child_device_ctx->device)); + + ret = -1; + goto Cleanup; + } + + /* Set the device bus id. Otherwise, device_register()will fail. */ + dev_set_name(&child_device_ctx->device, "vmbus_0_%d", + atomic_inc_return(&device_num)); + + /* The new device belongs to this bus */ + child_device_ctx->device.bus = &g_vmbus_drv.bus; /* device->dev.bus; */ + child_device_ctx->device.parent = &root_device_ctx->device; + child_device_ctx->device.release = vmbus_device_release; + + /* + * Register with the LDM. This will kick off the driver/device + * binding...which will eventually call vmbus_match() and vmbus_probe() + */ + ret = device_register(&child_device_ctx->device); + + /* vmbus_probe() error does not get propergate to device_register(). */ + ret = child_device_ctx->probe_error; + + if (ret) + DPRINT_ERR(VMBUS_DRV, "unable to register child device (%p)", + &child_device_ctx->device); + else + DPRINT_INFO(VMBUS_DRV, "child device (%p) registered", + &child_device_ctx->device); + +Cleanup: + DPRINT_EXIT(VMBUS_DRV); + + return ret; +} + +/** + * vmbus_child_device_unregister - Remove the specified child device from the vmbus. + */ +static void vmbus_child_device_unregister(struct hv_device *device_obj) +{ + struct device_context *device_ctx = to_device_context(device_obj); + + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_INFO(VMBUS_DRV, "unregistering child device (%p)", + &device_ctx->device); + + /* + * Kick off the process of unregistering the device. + * This will call vmbus_remove() and eventually vmbus_device_release() + */ + device_unregister(&device_ctx->device); + + DPRINT_INFO(VMBUS_DRV, "child device (%p) unregistered", + &device_ctx->device); + + DPRINT_EXIT(VMBUS_DRV); +} + +/** + * vmbus_child_device_destroy - Destroy the specified child device on the vmbus. + */ +static void vmbus_child_device_destroy(struct hv_device *device_obj) +{ + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_EXIT(VMBUS_DRV); +} + +/** + * vmbus_uevent - add uevent for our device + * + * This routine is invoked when a device is added or removed on the vmbus to + * generate a uevent to udev in the userspace. The udev will then look at its + * rule and the uevent generated here to load the appropriate driver + */ +static int vmbus_uevent(struct device *device, struct kobj_uevent_env *env) +{ + struct device_context *device_ctx = device_to_device_context(device); + int i = 0; + int len = 0; + int ret; + + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_INFO(VMBUS_DRV, "generating uevent - VMBUS_DEVICE_CLASS_GUID={" + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}", + device_ctx->class_id.data[3], device_ctx->class_id.data[2], + device_ctx->class_id.data[1], device_ctx->class_id.data[0], + device_ctx->class_id.data[5], device_ctx->class_id.data[4], + device_ctx->class_id.data[7], device_ctx->class_id.data[6], + device_ctx->class_id.data[8], device_ctx->class_id.data[9], + device_ctx->class_id.data[10], + device_ctx->class_id.data[11], + device_ctx->class_id.data[12], + device_ctx->class_id.data[13], + device_ctx->class_id.data[14], + device_ctx->class_id.data[15]); + + env->envp_idx = i; + env->buflen = len; + ret = add_uevent_var(env, "VMBUS_DEVICE_CLASS_GUID={" + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}", + device_ctx->class_id.data[3], + device_ctx->class_id.data[2], + device_ctx->class_id.data[1], + device_ctx->class_id.data[0], + device_ctx->class_id.data[5], + device_ctx->class_id.data[4], + device_ctx->class_id.data[7], + device_ctx->class_id.data[6], + device_ctx->class_id.data[8], + device_ctx->class_id.data[9], + device_ctx->class_id.data[10], + device_ctx->class_id.data[11], + device_ctx->class_id.data[12], + device_ctx->class_id.data[13], + device_ctx->class_id.data[14], + device_ctx->class_id.data[15]); + + if (ret) + return ret; + + ret = add_uevent_var(env, "VMBUS_DEVICE_DEVICE_GUID={" + "%02x%02x%02x%02x-%02x%02x-%02x%02x-" + "%02x%02x%02x%02x%02x%02x%02x%02x}", + device_ctx->device_id.data[3], + device_ctx->device_id.data[2], + device_ctx->device_id.data[1], + device_ctx->device_id.data[0], + device_ctx->device_id.data[5], + device_ctx->device_id.data[4], + device_ctx->device_id.data[7], + device_ctx->device_id.data[6], + device_ctx->device_id.data[8], + device_ctx->device_id.data[9], + device_ctx->device_id.data[10], + device_ctx->device_id.data[11], + device_ctx->device_id.data[12], + device_ctx->device_id.data[13], + device_ctx->device_id.data[14], + device_ctx->device_id.data[15]); + if (ret) + return ret; + + env->envp[env->envp_idx] = NULL; + + DPRINT_EXIT(VMBUS_DRV); + + return 0; +} + +/** + * vmbus_match - Attempt to match the specified device to the specified driver + */ +static int vmbus_match(struct device *device, struct device_driver *driver) +{ + int match = 0; + struct driver_context *driver_ctx = driver_to_driver_context(driver); + struct device_context *device_ctx = device_to_device_context(device); + + DPRINT_ENTER(VMBUS_DRV); + + /* We found our driver ? */ + if (memcmp(&device_ctx->class_id, &driver_ctx->class_id, + sizeof(struct hv_guid)) == 0) { + /* + * !! NOTE: The driver_ctx is not a vmbus_drv_ctx. We typecast + * it here to access the struct hv_driver field + */ + struct vmbus_driver_context *vmbus_drv_ctx = + (struct vmbus_driver_context *)driver_ctx; + + device_ctx->device_obj.Driver = &vmbus_drv_ctx->drv_obj.Base; + DPRINT_INFO(VMBUS_DRV, + "device object (%p) set to driver object (%p)", + &device_ctx->device_obj, + device_ctx->device_obj.Driver); + + match = 1; + } + + DPRINT_EXIT(VMBUS_DRV); + + return match; +} + +/** + * vmbus_probe_failed_cb - Callback when a driver probe failed in vmbus_probe() + * + * We need a callback because we cannot invoked device_unregister() inside + * vmbus_probe() since vmbus_probe() may be invoked inside device_register() + * i.e. we cannot call device_unregister() inside device_register() + */ +static void vmbus_probe_failed_cb(struct work_struct *context) +{ + struct device_context *device_ctx = (struct device_context *)context; + + DPRINT_ENTER(VMBUS_DRV); + + /* + * Kick off the process of unregistering the device. + * This will call vmbus_remove() and eventually vmbus_device_release() + */ + device_unregister(&device_ctx->device); + + /* put_device(&device_ctx->device); */ + DPRINT_EXIT(VMBUS_DRV); +} + +/** + * vmbus_probe - Add the new vmbus's child device + */ +static int vmbus_probe(struct device *child_device) +{ + int ret = 0; + struct driver_context *driver_ctx = + driver_to_driver_context(child_device->driver); + struct device_context *device_ctx = + device_to_device_context(child_device); + + DPRINT_ENTER(VMBUS_DRV); + + /* Let the specific open-source driver handles the probe if it can */ + if (driver_ctx->probe) { + ret = device_ctx->probe_error = driver_ctx->probe(child_device); + if (ret != 0) { + DPRINT_ERR(VMBUS_DRV, "probe() failed for device %s " + "(%p) on driver %s (%d)...", + dev_name(child_device), child_device, + child_device->driver->name, ret); + + INIT_WORK(&device_ctx->probe_failed_work_item, + vmbus_probe_failed_cb); + schedule_work(&device_ctx->probe_failed_work_item); + } + } else { + DPRINT_ERR(VMBUS_DRV, "probe() method not set for driver - %s", + child_device->driver->name); + ret = -1; + } + + DPRINT_EXIT(VMBUS_DRV); + return ret; +} + +/** + * vmbus_remove - Remove a vmbus device + */ +static int vmbus_remove(struct device *child_device) +{ + int ret; + struct driver_context *driver_ctx; + + DPRINT_ENTER(VMBUS_DRV); + + /* Special case root bus device */ + if (child_device->parent == NULL) { + /* + * No-op since it is statically defined and handle in + * vmbus_bus_exit() + */ + DPRINT_EXIT(VMBUS_DRV); + return 0; + } + + if (child_device->driver) { + driver_ctx = driver_to_driver_context(child_device->driver); + + /* + * Let the specific open-source driver handles the removal if + * it can + */ + if (driver_ctx->remove) { + ret = driver_ctx->remove(child_device); + } else { + DPRINT_ERR(VMBUS_DRV, + "remove() method not set for driver - %s", + child_device->driver->name); + ret = -1; + } + } + + DPRINT_EXIT(VMBUS_DRV); + + return 0; +} + +/** + * vmbus_shutdown - Shutdown a vmbus device + */ +static void vmbus_shutdown(struct device *child_device) +{ + struct driver_context *driver_ctx; + + DPRINT_ENTER(VMBUS_DRV); + + /* Special case root bus device */ + if (child_device->parent == NULL) { + /* + * No-op since it is statically defined and handle in + * vmbus_bus_exit() + */ + DPRINT_EXIT(VMBUS_DRV); + return; + } + + /* The device may not be attached yet */ + if (!child_device->driver) { + DPRINT_EXIT(VMBUS_DRV); + return; + } + + driver_ctx = driver_to_driver_context(child_device->driver); + + /* Let the specific open-source driver handles the removal if it can */ + if (driver_ctx->shutdown) + driver_ctx->shutdown(child_device); + + DPRINT_EXIT(VMBUS_DRV); + + return; +} + +/** + * vmbus_bus_release - Final callback release of the vmbus root device + */ +static void vmbus_bus_release(struct device *device) +{ + DPRINT_ENTER(VMBUS_DRV); + /* FIXME */ + /* Empty release functions are a bug, or a major sign + * of a problem design, this MUST BE FIXED! */ + dev_err(device, "%s needs to be fixed!\n", __func__); + WARN_ON(1); + DPRINT_EXIT(VMBUS_DRV); +} + +/** + * vmbus_device_release - Final callback release of the vmbus child device + */ +static void vmbus_device_release(struct device *device) +{ + struct device_context *device_ctx = device_to_device_context(device); + + DPRINT_ENTER(VMBUS_DRV); + + /* vmbus_child_device_destroy(&device_ctx->device_obj); */ + kfree(device_ctx); + + /* !!DO NOT REFERENCE device_ctx anymore at this point!! */ + DPRINT_EXIT(VMBUS_DRV); + + return; +} + +/** + * vmbus_msg_dpc - Tasklet routine to handle hypervisor messages + */ +static void vmbus_msg_dpc(unsigned long data) +{ + struct vmbus_driver *vmbus_drv_obj = (struct vmbus_driver *)data; + + DPRINT_ENTER(VMBUS_DRV); + + ASSERT(vmbus_drv_obj->OnMsgDpc != NULL); + + /* Call to bus driver to handle interrupt */ + vmbus_drv_obj->OnMsgDpc(&vmbus_drv_obj->Base); + + DPRINT_EXIT(VMBUS_DRV); +} + +/** + * vmbus_msg_dpc - Tasklet routine to handle hypervisor events + */ +static void vmbus_event_dpc(unsigned long data) +{ + struct vmbus_driver *vmbus_drv_obj = (struct vmbus_driver *)data; + + DPRINT_ENTER(VMBUS_DRV); + + ASSERT(vmbus_drv_obj->OnEventDpc != NULL); + + /* Call to bus driver to handle interrupt */ + vmbus_drv_obj->OnEventDpc(&vmbus_drv_obj->Base); + + DPRINT_EXIT(VMBUS_DRV); +} + +static irqreturn_t vmbus_isr(int irq, void *dev_id) +{ + struct vmbus_driver *vmbus_driver_obj = &g_vmbus_drv.drv_obj; + int ret; + + DPRINT_ENTER(VMBUS_DRV); + + ASSERT(vmbus_driver_obj->OnIsr != NULL); + + /* Call to bus driver to handle interrupt */ + ret = vmbus_driver_obj->OnIsr(&vmbus_driver_obj->Base); + + /* Schedules a dpc if necessary */ + if (ret > 0) { + if (test_bit(0, (unsigned long *)&ret)) + tasklet_schedule(&g_vmbus_drv.msg_dpc); + + if (test_bit(1, (unsigned long *)&ret)) + tasklet_schedule(&g_vmbus_drv.event_dpc); + + DPRINT_EXIT(VMBUS_DRV); + return IRQ_HANDLED; + } else { + DPRINT_EXIT(VMBUS_DRV); + return IRQ_NONE; + } +} + +static int __init vmbus_init(void) +{ + int ret = 0; + + DPRINT_ENTER(VMBUS_DRV); + + DPRINT_INFO(VMBUS_DRV, + "Vmbus initializing.... current log level 0x%x (%x,%x)", + vmbus_loglevel, HIWORD(vmbus_loglevel), LOWORD(vmbus_loglevel)); + /* Todo: it is used for loglevel, to be ported to new kernel. */ + + ret = vmbus_bus_init(VmbusInitialize); + + DPRINT_EXIT(VMBUS_DRV); + return ret; +} + +static void __exit vmbus_exit(void) +{ + DPRINT_ENTER(VMBUS_DRV); + + vmbus_bus_exit(); + /* Todo: it is used for loglevel, to be ported to new kernel. */ + DPRINT_EXIT(VMBUS_DRV); + return; +} + +MODULE_LICENSE("GPL"); +module_param(vmbus_irq, int, S_IRUGO); +module_param(vmbus_loglevel, int, S_IRUGO); + +module_init(vmbus_init); +module_exit(vmbus_exit); diff --git a/drivers/staging/hv/vstorage.h b/drivers/staging/hv/vstorage.h new file mode 100644 index 00000000000..6d160a53914 --- /dev/null +++ b/drivers/staging/hv/vstorage.h @@ -0,0 +1,192 @@ +/* + * + * Copyright (c) 2009, Microsoft Corporation. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 59 Temple + * Place - Suite 330, Boston, MA 02111-1307 USA. + * + * Authors: + * Haiyang Zhang <haiyangz@microsoft.com> + * Hank Janssen <hjanssen@microsoft.com> + * + */ + +/* vstorage.w revision number. This is used in the case of a version match, */ +/* to alert the user that structure sizes may be mismatched even though the */ +/* protocol versions match. */ + +#define REVISION_STRING(REVISION_) #REVISION_ +#define FILL_VMSTOR_REVISION(RESULT_LVALUE_) \ +{ \ + char *revisionString = REVISION_STRING($Revision: 6 $) + 11; \ + RESULT_LVALUE_ = 0; \ + while (*revisionString >= '0' && *revisionString <= '9') { \ + RESULT_LVALUE_ *= 10; \ + RESULT_LVALUE_ += *revisionString - '0'; \ + revisionString++; \ + } \ +} + +/* Major/minor macros. Minor version is in LSB, meaning that earlier flat */ +/* version numbers will be interpreted as "0.x" (i.e., 1 becomes 0.1). */ +#define VMSTOR_PROTOCOL_MAJOR(VERSION_) (((VERSION_) >> 8) & 0xff) +#define VMSTOR_PROTOCOL_MINOR(VERSION_) (((VERSION_)) & 0xff) +#define VMSTOR_PROTOCOL_VERSION(MAJOR_, MINOR_) ((((MAJOR_) & 0xff) << 8) | \ + (((MINOR_) & 0xff))) +#define VMSTOR_INVALID_PROTOCOL_VERSION (-1) + +/* Version history: */ +/* V1 Beta 0.1 */ +/* V1 RC < 2008/1/31 1.0 */ +/* V1 RC > 2008/1/31 2.0 */ +#define VMSTOR_PROTOCOL_VERSION_CURRENT VMSTOR_PROTOCOL_VERSION(2, 0) + + + + +/* This will get replaced with the max transfer length that is possible on */ +/* the host adapter. */ +/* The max transfer length will be published when we offer a vmbus channel. */ +#define MAX_TRANSFER_LENGTH 0x40000 +#define DEFAULT_PACKET_SIZE (sizeof(struct vmdata_gpa_direct) + \ + sizeof(struct vstor_packet) + \ + sizesizeof(u64) * (MAX_TRANSFER_LENGTH / PAGE_SIZE))) + + +/* Packet structure describing virtual storage requests. */ +enum vstor_packet_operation { + VStorOperationCompleteIo = 1, + VStorOperationRemoveDevice = 2, + VStorOperationExecuteSRB = 3, + VStorOperationResetLun = 4, + VStorOperationResetAdapter = 5, + VStorOperationResetBus = 6, + VStorOperationBeginInitialization = 7, + VStorOperationEndInitialization = 8, + VStorOperationQueryProtocolVersion = 9, + VStorOperationQueryProperties = 10, + VStorOperationMaximum = 10 +}; + +/* + * Platform neutral description of a scsi request - + * this remains the same across the write regardless of 32/64 bit + * note: it's patterned off the SCSI_PASS_THROUGH structure + */ +#define CDB16GENERIC_LENGTH 0x10 + +#ifndef SENSE_BUFFER_SIZE +#define SENSE_BUFFER_SIZE 0x12 +#endif + +#define MAX_DATA_BUFFER_LENGTH_WITH_PADDING 0x14 + +struct vmscsi_request { + unsigned short Length; + unsigned char SrbStatus; + unsigned char ScsiStatus; + + unsigned char PortNumber; + unsigned char PathId; + unsigned char TargetId; + unsigned char Lun; + + unsigned char CdbLength; + unsigned char SenseInfoLength; + unsigned char DataIn; + unsigned char Reserved; + + unsigned int DataTransferLength; + + union { + unsigned char Cdb[CDB16GENERIC_LENGTH]; + + unsigned char SenseData[SENSE_BUFFER_SIZE]; + + unsigned char ReservedArray[MAX_DATA_BUFFER_LENGTH_WITH_PADDING]; + }; +} __attribute((packed)); + + +/* + * This structure is sent during the intialization phase to get the different + * properties of the channel. + */ +struct vmstorage_channel_properties { + unsigned short ProtocolVersion; + unsigned char PathId; + unsigned char TargetId; + + /* Note: port number is only really known on the client side */ + unsigned int PortNumber; + unsigned int Flags; + unsigned int MaxTransferBytes; + + /* This id is unique for each channel and will correspond with */ + /* vendor specific data in the inquirydata */ + unsigned long long UniqueId; +} __attribute__((packed)); + +/* This structure is sent during the storage protocol negotiations. */ +struct vmstorage_protocol_version { + /* Major (MSW) and minor (LSW) version numbers. */ + unsigned short MajorMinor; + + /* + * Revision number is auto-incremented whenever this file is changed + * (See FILL_VMSTOR_REVISION macro above). Mismatch does not + * definitely indicate incompatibility--but it does indicate mismatched + * builds. + */ + unsigned short Revision; +} __attribute__((packed)); + +/* Channel Property Flags */ +#define STORAGE_CHANNEL_REMOVABLE_FLAG 0x1 +#define STORAGE_CHANNEL_EMULATED_IDE_FLAG 0x2 + +struct vstor_packet { + /* Requested operation type */ + enum vstor_packet_operation Operation; + + /* Flags - see below for values */ + unsigned int Flags; + + /* Status of the request returned from the server side. */ + unsigned int Status; + + /* Data payload area */ + union { + /* + * Structure used to forward SCSI commands from the + * client to the server. + */ + struct vmscsi_request VmSrb; + + /* Structure used to query channel properties. */ + struct vmstorage_channel_properties StorageChannelProperties; + + /* Used during version negotiations. */ + struct vmstorage_protocol_version Version; + }; +} __attribute__((packed)); + +/* Packet flags */ +/* + * This flag indicates that the server should send back a completion for this + * packet. + */ +#define REQUEST_COMPLETION_FLAG 0x1 + +/* This is the set of flags that the vsc can set in any packets it sends */ +#define VSC_LEGAL_FLAGS (REQUEST_COMPLETION_FLAG) |