Drivers: hv: vmbus: Implement multi-channel support

Starting with Win8, the host supports multiple sub-channels for a given
device. As in the past, the initial channel offer specifies the device and
is associated with both the type and the instance GUIDs. For performance
critical devices, the host may support multiple sub-channels. The sub-channels
share the same type and instance GUID as the primary channel. The number of
sub-channels offerrred to the guest depends on the number of virtual CPUs
assigned to the guest. The guest can request the creation of these sub-channels
and once created and opened, the guest can distribute the traffic across all
the channels (the primary and the sub-channels). A request sent on a sub-channel
will have the response delivered on the same sub-channel.

At channel (sub-channel) creation we bind the channel interrupt to a CPU and
with this sub-channel support we will be able to spread the interrupt load
of a given device across all available CPUs.

Signed-off-by: K. Y. Srinivasan <[email protected]>
Reviewed-by: Haiyang Zhang <[email protected]>
Signed-off-by: Greg Kroah-Hartman <[email protected]>
This commit is contained in:
K. Y. Srinivasan 2013-05-23 12:02:32 -07:00 • committed by Greg Kroah-Hartman
commit e68d2971d2
4 changed files with 236 additions and 9 deletions

View file

@ -116,6 +116,15 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
unsigned long flags;
int ret, t, err = 0;
spin_lock_irqsave(&newchannel->sc_lock, flags);
if (newchannel->state == CHANNEL_OPEN_STATE) {
newchannel->state = CHANNEL_OPENING_STATE;
} else {
spin_unlock_irqrestore(&newchannel->sc_lock, flags);
return -EINVAL;
}
spin_unlock_irqrestore(&newchannel->sc_lock, flags);
newchannel->onchannel_callback = onchannelcallback;
newchannel->channel_callback_context = context;
@ -216,6 +225,9 @@ int vmbus_open(struct vmbus_channel *newchannel, u32 send_ringbuffer_size,
list_del(&open_info->msglistentry);
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
if (err == 0)
newchannel->state = CHANNEL_OPENED_STATE;
kfree(open_info);
return err;
@ -500,15 +512,14 @@ int vmbus_teardown_gpadl(struct vmbus_channel *channel, u32 gpadl_handle)
}
EXPORT_SYMBOL_GPL(vmbus_teardown_gpadl);
/*
* vmbus_close - Close the specified channel
*/
void vmbus_close(struct vmbus_channel *channel)
static void vmbus_close_internal(struct vmbus_channel *channel)
{
struct vmbus_channel_close_channel *msg;
int ret;
unsigned long flags;
channel->state = CHANNEL_OPEN_STATE;
channel->sc_creation_callback = NULL;
/* Stop callback and cancel the timer asap */
spin_lock_irqsave(&channel->inbound_lock, flags);
channel->onchannel_callback = NULL;
@ -538,6 +549,37 @@ void vmbus_close(struct vmbus_channel *channel)
}
/*
* vmbus_close - Close the specified channel
*/
void vmbus_close(struct vmbus_channel *channel)
{
struct list_head *cur, *tmp;
struct vmbus_channel *cur_channel;
if (channel->primary_channel != NULL) {
/*
* We will only close sub-channels when
* the primary is closed.
*/
return;
}
/*
* Close all the sub-channels first and then close the
* primary channel.
*/
list_for_each_safe(cur, tmp, &channel->sc_list) {
cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
if (cur_channel->state != CHANNEL_OPENED_STATE)
continue;
vmbus_close_internal(cur_channel);
}
/*
* Now close the primary.
*/
vmbus_close_internal(channel);
}
EXPORT_SYMBOL_GPL(vmbus_close);
/**

View file

@ -115,6 +115,9 @@ static struct vmbus_channel *alloc_channel(void)
return NULL;
spin_lock_init(&channel->inbound_lock);
spin_lock_init(&channel->sc_lock);
INIT_LIST_HEAD(&channel->sc_list);
channel->controlwq = create_workqueue("hv_vmbus_ctl");
if (!channel->controlwq) {
@ -166,6 +169,7 @@ static void vmbus_process_rescind_offer(struct work_struct *work)
struct vmbus_channel,
work);
unsigned long flags;
struct vmbus_channel *primary_channel;
struct vmbus_channel_relid_released msg;
vmbus_device_unregister(channel->device_obj);
@ -174,9 +178,16 @@ static void vmbus_process_rescind_offer(struct work_struct *work)
msg.header.msgtype = CHANNELMSG_RELID_RELEASED;
vmbus_post_msg(&msg, sizeof(struct vmbus_channel_relid_released));
spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
list_del(&channel->listentry);
spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
if (channel->primary_channel == NULL) {
spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
list_del(&channel->listentry);
spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
} else {
primary_channel = channel->primary_channel;
spin_lock_irqsave(&primary_channel->sc_lock, flags);
list_del(&channel->listentry);
spin_unlock_irqrestore(&primary_channel->sc_lock, flags);
}
free_channel(channel);
}
@ -228,6 +239,24 @@ static void vmbus_process_offer(struct work_struct *work)
spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);
if (!fnew) {
/*
* Check to see if this is a sub-channel.
*/
if (newchannel->offermsg.offer.sub_channel_index != 0) {
/*
* Process the sub-channel.
*/
newchannel->primary_channel = channel;
spin_lock_irqsave(&channel->sc_lock, flags);
list_add_tail(&newchannel->sc_list, &channel->sc_list);
spin_unlock_irqrestore(&channel->sc_lock, flags);
newchannel->state = CHANNEL_OPEN_STATE;
if (channel->sc_creation_callback != NULL)
channel->sc_creation_callback(newchannel);
return;
}
free_channel(newchannel);
return;
}
@ -685,4 +714,86 @@ cleanup:
return ret;
}
/* eof */
/*
* Retrieve the (sub) channel on which to send an outgoing request.
* When a primary channel has multiple sub-channels, we choose a
* channel whose VCPU binding is closest to the VCPU on which
* this call is being made.
*/
struct vmbus_channel *vmbus_get_outgoing_channel(struct vmbus_channel *primary)
{
struct list_head *cur, *tmp;
int cur_cpu = hv_context.vp_index[smp_processor_id()];
struct vmbus_channel *cur_channel;
struct vmbus_channel *outgoing_channel = primary;
int cpu_distance, new_cpu_distance;
if (list_empty(&primary->sc_list))
return outgoing_channel;
list_for_each_safe(cur, tmp, &primary->sc_list) {
cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
if (cur_channel->state != CHANNEL_OPENED_STATE)
continue;
if (cur_channel->target_vp == cur_cpu)
return cur_channel;
cpu_distance = ((outgoing_channel->target_vp > cur_cpu) ?
(outgoing_channel->target_vp - cur_cpu) :
(cur_cpu - outgoing_channel->target_vp));
new_cpu_distance = ((cur_channel->target_vp > cur_cpu) ?
(cur_channel->target_vp - cur_cpu) :
(cur_cpu - cur_channel->target_vp));
if (cpu_distance < new_cpu_distance)
continue;
outgoing_channel = cur_channel;
}
return outgoing_channel;
}
EXPORT_SYMBOL_GPL(vmbus_get_outgoing_channel);
static void invoke_sc_cb(struct vmbus_channel *primary_channel)
{
struct list_head *cur, *tmp;
struct vmbus_channel *cur_channel;
if (primary_channel->sc_creation_callback == NULL)
return;
list_for_each_safe(cur, tmp, &primary_channel->sc_list) {
cur_channel = list_entry(cur, struct vmbus_channel, sc_list);
primary_channel->sc_creation_callback(cur_channel);
}
}
void vmbus_set_sc_create_callback(struct vmbus_channel *primary_channel,
void (*sc_cr_cb)(struct vmbus_channel *new_sc))
{
primary_channel->sc_creation_callback = sc_cr_cb;
}
EXPORT_SYMBOL_GPL(vmbus_set_sc_create_callback);
bool vmbus_are_subchannels_present(struct vmbus_channel *primary)
{
bool ret;
ret = !list_empty(&primary->sc_list);
if (ret) {
/*
* Invoke the callback on sub-channel creation.
* This will present a uniform interface to the
* clients.
*/
invoke_sc_cb(primary);
}
return ret;
}
EXPORT_SYMBOL_GPL(vmbus_are_subchannels_present);

View file

@ -246,12 +246,26 @@ struct vmbus_channel *relid2channel(u32 relid)
struct vmbus_channel *channel;
struct vmbus_channel *found_channel = NULL;
unsigned long flags;
struct list_head *cur, *tmp;
struct vmbus_channel *cur_sc;
spin_lock_irqsave(&vmbus_connection.channel_lock, flags);
list_for_each_entry(channel, &vmbus_connection.chn_list, listentry) {
if (channel->offermsg.child_relid == relid) {
found_channel = channel;
break;
} else if (!list_empty(&channel->sc_list)) {
/*
* Deal with sub-channels.
*/
list_for_each_safe(cur, tmp, &channel->sc_list) {
cur_sc = list_entry(cur, struct vmbus_channel,
sc_list);
if (cur_sc->offermsg.child_relid == relid) {
found_channel = cur_sc;
break;
}
}
}
}
spin_unlock_irqrestore(&vmbus_connection.channel_lock, flags);