static ssize_t target_cpu_store()

in vmbus_drv.c [1791:1889]


static ssize_t target_cpu_store(struct vmbus_channel *channel,
				const char *buf, size_t count)
{
	u32 target_cpu, origin_cpu;
	ssize_t ret = count;

	if (vmbus_proto_version < VERSION_WIN10_V4_1)
		return -EIO;

	if (sscanf(buf, "%uu", &target_cpu) != 1)
		return -EIO;

	/* Validate target_cpu for the cpumask_test_cpu() operation below. */
	if (target_cpu >= nr_cpumask_bits)
		return -EINVAL;

	/* No CPUs should come up or down during this. */
	cpus_read_lock();

	if (!cpu_online(target_cpu)) {
		cpus_read_unlock();
		return -EINVAL;
	}

	/*
	 * Synchronizes target_cpu_store() and channel closure:
	 *
	 * { Initially: state = CHANNEL_OPENED }
	 *
	 * CPU1				CPU2
	 *
	 * [target_cpu_store()]		[vmbus_disconnect_ring()]
	 *
	 * LOCK channel_mutex		LOCK channel_mutex
	 * LOAD r1 = state		LOAD r2 = state
	 * IF (r1 == CHANNEL_OPENED)	IF (r2 == CHANNEL_OPENED)
	 *   SEND MODIFYCHANNEL		  STORE state = CHANNEL_OPEN
	 *   [...]			  SEND CLOSECHANNEL
	 * UNLOCK channel_mutex		UNLOCK channel_mutex
	 *
	 * Forbids: r1 == r2 == CHANNEL_OPENED (i.e., CPU1's LOCK precedes
	 * 		CPU2's LOCK) && CPU2's SEND precedes CPU1's SEND
	 *
	 * Note.  The host processes the channel messages "sequentially", in
	 * the order in which they are received on a per-partition basis.
	 */
	mutex_lock(&vmbus_connection.channel_mutex);

	/*
	 * Hyper-V will ignore MODIFYCHANNEL messages for "non-open" channels;
	 * avoid sending the message and fail here for such channels.
	 */
	if (channel->state != CHANNEL_OPENED_STATE) {
		ret = -EIO;
		goto cpu_store_unlock;
	}

	origin_cpu = channel->target_cpu;
	if (target_cpu == origin_cpu)
		goto cpu_store_unlock;

	if (vmbus_send_modifychannel(channel,
				     hv_cpu_number_to_vp_number(target_cpu))) {
		ret = -EIO;
		goto cpu_store_unlock;
	}

	/*
	 * For version before VERSION_WIN10_V5_3, the following warning holds:
	 *
	 * Warning.  At this point, there is *no* guarantee that the host will
	 * have successfully processed the vmbus_send_modifychannel() request.
	 * See the header comment of vmbus_send_modifychannel() for more info.
	 *
	 * Lags in the processing of the above vmbus_send_modifychannel() can
	 * result in missed interrupts if the "old" target CPU is taken offline
	 * before Hyper-V starts sending interrupts to the "new" target CPU.
	 * But apart from this offlining scenario, the code tolerates such
	 * lags.  It will function correctly even if a channel interrupt comes
	 * in on a CPU that is different from the channel target_cpu value.
	 */

	channel->target_cpu = target_cpu;

	/* See init_vp_index(). */
	if (hv_is_perf_channel(channel))
		hv_update_alloced_cpus(origin_cpu, target_cpu);

	/* Currently set only for storvsc channels. */
	if (channel->change_target_cpu_callback) {
		(*channel->change_target_cpu_callback)(channel,
				origin_cpu, target_cpu);
	}

cpu_store_unlock:
	mutex_unlock(&vmbus_connection.channel_mutex);
	cpus_read_unlock();
	return ret;
}