in channel_mgmt.c [1115:1250]
static void vmbus_onoffer_rescind(struct vmbus_channel_message_header *hdr)
{
struct vmbus_channel_rescind_offer *rescind;
struct vmbus_channel *channel;
struct device *dev;
bool clean_up_chan_for_suspend;
rescind = (struct vmbus_channel_rescind_offer *)hdr;
trace_vmbus_onoffer_rescind(rescind);
/*
* The offer msg and the corresponding rescind msg
* from the host are guranteed to be ordered -
* offer comes in first and then the rescind.
* Since we process these events in work elements,
* and with preemption, we may end up processing
* the events out of order. We rely on the synchronization
* provided by offer_in_progress and by channel_mutex for
* ordering these events:
*
* { Initially: offer_in_progress = 1 }
*
* CPU1 CPU2
*
* [vmbus_onoffer()] [vmbus_onoffer_rescind()]
*
* LOCK channel_mutex WAIT_ON offer_in_progress == 0
* DECREMENT offer_in_progress LOCK channel_mutex
* STORE channels[] LOAD channels[]
* UNLOCK channel_mutex UNLOCK channel_mutex
*
* Forbids: CPU2's LOAD from *not* seeing CPU1's STORE
*/
while (atomic_read(&vmbus_connection.offer_in_progress) != 0) {
/*
* We wait here until any channel offer is currently
* being processed.
*/
msleep(1);
}
mutex_lock(&vmbus_connection.channel_mutex);
channel = relid2channel(rescind->child_relid);
if (channel != NULL) {
/*
* Guarantee that no other instance of vmbus_onoffer_rescind()
* has got a reference to the channel object. Synchronize on
* &vmbus_connection.channel_mutex.
*/
if (channel->rescind_ref) {
mutex_unlock(&vmbus_connection.channel_mutex);
return;
}
channel->rescind_ref = true;
}
mutex_unlock(&vmbus_connection.channel_mutex);
if (channel == NULL) {
/*
* We failed in processing the offer message;
* we would have cleaned up the relid in that
* failure path.
*/
return;
}
clean_up_chan_for_suspend = is_hvsock_channel(channel) ||
is_sub_channel(channel);
/*
* Before setting channel->rescind in vmbus_rescind_cleanup(), we
* should make sure the channel callback is not running any more.
*/
vmbus_reset_channel_cb(channel);
/*
* Now wait for offer handling to complete.
*/
vmbus_rescind_cleanup(channel);
while (READ_ONCE(channel->probe_done) == false) {
/*
* We wait here until any channel offer is currently
* being processed.
*/
msleep(1);
}
/*
* At this point, the rescind handling can proceed safely.
*/
if (channel->device_obj) {
if (channel->chn_rescind_callback) {
channel->chn_rescind_callback(channel);
if (clean_up_chan_for_suspend)
check_ready_for_suspend_event();
return;
}
/*
* We will have to unregister this device from the
* driver core.
*/
dev = get_device(&channel->device_obj->device);
if (dev) {
vmbus_device_unregister(channel->device_obj);
put_device(dev);
}
} else if (channel->primary_channel != NULL) {
/*
* Sub-channel is being rescinded. Following is the channel
* close sequence when initiated from the driveri (refer to
* vmbus_close() for details):
* 1. Close all sub-channels first
* 2. Then close the primary channel.
*/
mutex_lock(&vmbus_connection.channel_mutex);
if (channel->state == CHANNEL_OPEN_STATE) {
/*
* The channel is currently not open;
* it is safe for us to cleanup the channel.
*/
hv_process_channel_removal(channel);
} else {
complete(&channel->rescind_event);
}
mutex_unlock(&vmbus_connection.channel_mutex);
}
/* The "channel" may have been freed. Do not access it any longer. */
if (clean_up_chan_for_suspend)
check_ready_for_suspend_event();
}