static void vmbus_onoffer_rescind()

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();
}