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