linux-stable/drivers/staging/hv/ChannelMgmt.c
Haiyang Zhang 5996b3ddc4 Staging: hv: Fix vmbus event handler bug
The flag ENABLE_POLLING is always enabled in original Makefile, but
accidently removed during porting to mainline kernel. The patch fixes
this bug which can cause stalled network communication.  Credit needs to
go to Eric Sesterhenn <eric.sesterhenn@lsexperts.de> For pointing out a
typo in the original code as well.

Signed-off-by: Hank Janssen <hjanssen@microsoft.com>
Signed-off-by: Haiyang Zhang <haiyangz@microsoft.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2009-11-30 16:39:29 -08:00

686 lines
20 KiB
C

/*
* 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_sync(&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 */