vmbus: change to per channel tasklet
Make the event handling tasklet per channel rather than per-cpu. This allows for better fairness when getting lots of data on the same cpu. Signed-off-by: Stephen Hemminger <sthemmin@microsoft.com> Signed-off-by: K. Y. Srinivasan <kys@microsoft.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Родитель
37cdd991fa
Коммит
631e63a9f3
|
@ -530,7 +530,7 @@ static int vmbus_close_internal(struct vmbus_channel *channel)
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* process_chn_event(), running in the tasklet, can race
|
* vmbus_on_event(), running in the tasklet, can race
|
||||||
* with vmbus_close_internal() in the case of SMP guest, e.g., when
|
* with vmbus_close_internal() in the case of SMP guest, e.g., when
|
||||||
* the former is accessing channel->inbound.ring_buffer, the latter
|
* the former is accessing channel->inbound.ring_buffer, the latter
|
||||||
* could be freeing the ring_buffer pages.
|
* could be freeing the ring_buffer pages.
|
||||||
|
|
|
@ -339,6 +339,9 @@ static struct vmbus_channel *alloc_channel(void)
|
||||||
INIT_LIST_HEAD(&channel->sc_list);
|
INIT_LIST_HEAD(&channel->sc_list);
|
||||||
INIT_LIST_HEAD(&channel->percpu_list);
|
INIT_LIST_HEAD(&channel->percpu_list);
|
||||||
|
|
||||||
|
tasklet_init(&channel->callback_event,
|
||||||
|
vmbus_on_event, (unsigned long)channel);
|
||||||
|
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,6 +350,7 @@ static struct vmbus_channel *alloc_channel(void)
|
||||||
*/
|
*/
|
||||||
static void free_channel(struct vmbus_channel *channel)
|
static void free_channel(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
|
tasklet_kill(&channel->callback_event);
|
||||||
kfree(channel);
|
kfree(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,21 +384,15 @@ static void vmbus_release_relid(u32 relid)
|
||||||
|
|
||||||
void hv_event_tasklet_disable(struct vmbus_channel *channel)
|
void hv_event_tasklet_disable(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
struct hv_per_cpu_context *hv_cpu;
|
tasklet_disable(&channel->callback_event);
|
||||||
|
|
||||||
hv_cpu = per_cpu_ptr(hv_context.cpu_context, channel->target_cpu);
|
|
||||||
tasklet_disable(&hv_cpu->event_dpc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hv_event_tasklet_enable(struct vmbus_channel *channel)
|
void hv_event_tasklet_enable(struct vmbus_channel *channel)
|
||||||
{
|
{
|
||||||
struct hv_per_cpu_context *hv_cpu;
|
tasklet_enable(&channel->callback_event);
|
||||||
|
|
||||||
hv_cpu = per_cpu_ptr(hv_context.cpu_context, channel->target_cpu);
|
|
||||||
tasklet_enable(&hv_cpu->event_dpc);
|
|
||||||
|
|
||||||
/* In case there is any pending event */
|
/* In case there is any pending event */
|
||||||
tasklet_schedule(&hv_cpu->event_dpc);
|
tasklet_schedule(&channel->callback_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
void hv_process_channel_removal(struct vmbus_channel *channel, u32 relid)
|
||||||
|
|
|
@ -259,29 +259,6 @@ void vmbus_disconnect(void)
|
||||||
vmbus_connection.monitor_pages[1] = NULL;
|
vmbus_connection.monitor_pages[1] = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Map the given relid to the corresponding channel based on the
|
|
||||||
* per-cpu list of channels that have been affinitized to this CPU.
|
|
||||||
* This will be used in the channel callback path as we can do this
|
|
||||||
* mapping in a lock-free fashion.
|
|
||||||
*/
|
|
||||||
static struct vmbus_channel *pcpu_relid2channel(u32 relid)
|
|
||||||
{
|
|
||||||
struct hv_per_cpu_context *hv_cpu
|
|
||||||
= this_cpu_ptr(hv_context.cpu_context);
|
|
||||||
struct vmbus_channel *found_channel = NULL;
|
|
||||||
struct vmbus_channel *channel;
|
|
||||||
|
|
||||||
list_for_each_entry(channel, &hv_cpu->chan_list, percpu_list) {
|
|
||||||
if (channel->offermsg.child_relid == relid) {
|
|
||||||
found_channel = channel;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found_channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* relid2channel - Get the channel object given its
|
* relid2channel - Get the channel object given its
|
||||||
* child relative id (ie channel id)
|
* child relative id (ie channel id)
|
||||||
|
@ -318,24 +295,15 @@ struct vmbus_channel *relid2channel(u32 relid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* process_chn_event - Process a channel event notification
|
* vmbus_on_event - Process a channel event notification
|
||||||
*/
|
*/
|
||||||
static void process_chn_event(u32 relid)
|
void vmbus_on_event(unsigned long data)
|
||||||
{
|
{
|
||||||
struct vmbus_channel *channel;
|
struct vmbus_channel *channel = (void *) data;
|
||||||
void *arg;
|
void *arg;
|
||||||
bool read_state;
|
bool read_state;
|
||||||
u32 bytes_to_read;
|
u32 bytes_to_read;
|
||||||
|
|
||||||
/*
|
|
||||||
* Find the channel based on this relid and invokes the
|
|
||||||
* channel callback to process the event
|
|
||||||
*/
|
|
||||||
channel = pcpu_relid2channel(relid);
|
|
||||||
|
|
||||||
if (!channel)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A channel once created is persistent even when there
|
* A channel once created is persistent even when there
|
||||||
* is no driver handling the device. An unloading driver
|
* is no driver handling the device. An unloading driver
|
||||||
|
@ -344,7 +312,6 @@ static void process_chn_event(u32 relid)
|
||||||
* Thus, checking and invoking the driver specific callback takes
|
* Thus, checking and invoking the driver specific callback takes
|
||||||
* care of orderly unloading of the driver.
|
* care of orderly unloading of the driver.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (channel->onchannel_callback != NULL) {
|
if (channel->onchannel_callback != NULL) {
|
||||||
arg = channel->channel_callback_context;
|
arg = channel->channel_callback_context;
|
||||||
read_state = channel->batched_reading;
|
read_state = channel->batched_reading;
|
||||||
|
@ -372,45 +339,6 @@ static void process_chn_event(u32 relid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* vmbus_on_event - Handler for events
|
|
||||||
*/
|
|
||||||
void vmbus_on_event(unsigned long data)
|
|
||||||
{
|
|
||||||
struct hv_per_cpu_context *hv_cpu = (void *)data;
|
|
||||||
unsigned long *recv_int_page;
|
|
||||||
u32 maxbits, relid;
|
|
||||||
|
|
||||||
if (vmbus_proto_version < VERSION_WIN8) {
|
|
||||||
maxbits = MAX_NUM_CHANNELS_SUPPORTED;
|
|
||||||
recv_int_page = vmbus_connection.recv_int_page;
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* When the host is win8 and beyond, the event page
|
|
||||||
* can be directly checked to get the id of the channel
|
|
||||||
* that has the interrupt pending.
|
|
||||||
*/
|
|
||||||
void *page_addr = hv_cpu->synic_event_page;
|
|
||||||
union hv_synic_event_flags *event
|
|
||||||
= (union hv_synic_event_flags *)page_addr +
|
|
||||||
VMBUS_MESSAGE_SINT;
|
|
||||||
|
|
||||||
maxbits = HV_EVENT_FLAGS_COUNT;
|
|
||||||
recv_int_page = event->flags;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unlikely(!recv_int_page))
|
|
||||||
return;
|
|
||||||
|
|
||||||
for_each_set_bit(relid, recv_int_page, maxbits) {
|
|
||||||
if (sync_test_and_clear_bit(relid, recv_int_page)) {
|
|
||||||
/* Special case - vmbus channel protocol msg */
|
|
||||||
if (relid != 0)
|
|
||||||
process_chn_event(relid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* vmbus_post_msg - Send a msg on the vmbus's message connection
|
* vmbus_post_msg - Send a msg on the vmbus's message connection
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -156,8 +156,6 @@ int hv_synic_alloc(void)
|
||||||
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
||||||
|
|
||||||
memset(hv_cpu, 0, sizeof(*hv_cpu));
|
memset(hv_cpu, 0, sizeof(*hv_cpu));
|
||||||
tasklet_init(&hv_cpu->event_dpc,
|
|
||||||
vmbus_on_event, (unsigned long) hv_cpu);
|
|
||||||
tasklet_init(&hv_cpu->msg_dpc,
|
tasklet_init(&hv_cpu->msg_dpc,
|
||||||
vmbus_on_msg_dpc, (unsigned long) hv_cpu);
|
vmbus_on_msg_dpc, (unsigned long) hv_cpu);
|
||||||
|
|
||||||
|
|
|
@ -206,7 +206,6 @@ struct hv_per_cpu_context {
|
||||||
* we will manage the tasklet that handles events messages on a per CPU
|
* we will manage the tasklet that handles events messages on a per CPU
|
||||||
* basis.
|
* basis.
|
||||||
*/
|
*/
|
||||||
struct tasklet_struct event_dpc;
|
|
||||||
struct tasklet_struct msg_dpc;
|
struct tasklet_struct msg_dpc;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -885,6 +885,56 @@ msg_handled:
|
||||||
vmbus_signal_eom(msg, message_type);
|
vmbus_signal_eom(msg, message_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Schedule all channels with events pending
|
||||||
|
*/
|
||||||
|
static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu)
|
||||||
|
{
|
||||||
|
unsigned long *recv_int_page;
|
||||||
|
u32 maxbits, relid;
|
||||||
|
|
||||||
|
if (vmbus_proto_version < VERSION_WIN8) {
|
||||||
|
maxbits = MAX_NUM_CHANNELS_SUPPORTED;
|
||||||
|
recv_int_page = vmbus_connection.recv_int_page;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* When the host is win8 and beyond, the event page
|
||||||
|
* can be directly checked to get the id of the channel
|
||||||
|
* that has the interrupt pending.
|
||||||
|
*/
|
||||||
|
void *page_addr = hv_cpu->synic_event_page;
|
||||||
|
union hv_synic_event_flags *event
|
||||||
|
= (union hv_synic_event_flags *)page_addr +
|
||||||
|
VMBUS_MESSAGE_SINT;
|
||||||
|
|
||||||
|
maxbits = HV_EVENT_FLAGS_COUNT;
|
||||||
|
recv_int_page = event->flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlikely(!recv_int_page))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for_each_set_bit(relid, recv_int_page, maxbits) {
|
||||||
|
struct vmbus_channel *channel;
|
||||||
|
|
||||||
|
if (!sync_test_and_clear_bit(relid, recv_int_page))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Special case - vmbus channel protocol msg */
|
||||||
|
if (relid == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* Find channel based on relid */
|
||||||
|
list_for_each_entry(channel, &hv_cpu->chan_list, percpu_list) {
|
||||||
|
if (channel->offermsg.child_relid == relid) {
|
||||||
|
tasklet_schedule(&channel->callback_event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void vmbus_isr(void)
|
static void vmbus_isr(void)
|
||||||
{
|
{
|
||||||
struct hv_per_cpu_context *hv_cpu
|
struct hv_per_cpu_context *hv_cpu
|
||||||
|
@ -922,8 +972,7 @@ static void vmbus_isr(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handled)
|
if (handled)
|
||||||
tasklet_schedule(&hv_cpu->event_dpc);
|
vmbus_chan_sched(hv_cpu);
|
||||||
|
|
||||||
|
|
||||||
page_addr = hv_cpu->synic_message_page;
|
page_addr = hv_cpu->synic_message_page;
|
||||||
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT;
|
||||||
|
@ -1536,12 +1585,7 @@ static void __exit vmbus_exit(void)
|
||||||
&hyperv_panic_block);
|
&hyperv_panic_block);
|
||||||
}
|
}
|
||||||
bus_unregister(&hv_bus);
|
bus_unregister(&hv_bus);
|
||||||
for_each_online_cpu(cpu) {
|
|
||||||
struct hv_per_cpu_context *hv_cpu
|
|
||||||
= per_cpu_ptr(hv_context.cpu_context, cpu);
|
|
||||||
|
|
||||||
tasklet_kill(&hv_cpu->event_dpc);
|
|
||||||
}
|
|
||||||
cpuhp_remove_state(hyperv_cpuhp_online);
|
cpuhp_remove_state(hyperv_cpuhp_online);
|
||||||
hv_synic_free();
|
hv_synic_free();
|
||||||
acpi_bus_unregister_driver(&vmbus_acpi_driver);
|
acpi_bus_unregister_driver(&vmbus_acpi_driver);
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
#include <linux/device.h>
|
#include <linux/device.h>
|
||||||
#include <linux/mod_devicetable.h>
|
#include <linux/mod_devicetable.h>
|
||||||
|
#include <linux/interrupt.h>
|
||||||
|
|
||||||
#define MAX_PAGE_BUFFER_COUNT 32
|
#define MAX_PAGE_BUFFER_COUNT 32
|
||||||
#define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */
|
#define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */
|
||||||
|
@ -743,6 +743,7 @@ struct vmbus_channel {
|
||||||
struct vmbus_close_msg close_msg;
|
struct vmbus_close_msg close_msg;
|
||||||
|
|
||||||
/* Channel callback's invoked in softirq context */
|
/* Channel callback's invoked in softirq context */
|
||||||
|
struct tasklet_struct callback_event;
|
||||||
void (*onchannel_callback)(void *context);
|
void (*onchannel_callback)(void *context);
|
||||||
void *channel_callback_context;
|
void *channel_callback_context;
|
||||||
|
|
||||||
|
|
Загрузка…
Ссылка в новой задаче