diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 2b5bf672c467..0dc4692b411a 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -1057,12 +1057,9 @@ static void vmbus_onmessage_work(struct work_struct *work) kfree(ctx); } -void vmbus_on_msg_dpc(unsigned long data) +static void __vmbus_on_msg_dpc(void *message_page_addr) { - struct hv_per_cpu_context *hv_cpu = (void *)data; - void *page_addr = hv_cpu->hyp_synic_message_page; - struct hv_message msg_copy, *msg = (struct hv_message *)page_addr + - VMBUS_MESSAGE_SINT; + struct hv_message msg_copy, *msg; struct vmbus_channel_message_header *hdr; enum vmbus_channel_message_type msgtype; const struct vmbus_channel_message_table_entry *entry; @@ -1070,6 +1067,10 @@ void vmbus_on_msg_dpc(unsigned long data) __u8 payload_size; u32 message_type; + if (!message_page_addr) + return; + msg = (struct hv_message *)message_page_addr + VMBUS_MESSAGE_SINT; + /* * 'enum vmbus_channel_message_type' is supposed to always be 'u32' as * it is being used in 'struct vmbus_channel_message_header' definition @@ -1195,6 +1196,14 @@ void vmbus_on_msg_dpc(unsigned long data) vmbus_signal_eom(msg, message_type); } +void vmbus_on_msg_dpc(unsigned long data) +{ + struct hv_per_cpu_context *hv_cpu = (void *)data; + + __vmbus_on_msg_dpc(hv_cpu->hyp_synic_message_page); + __vmbus_on_msg_dpc(hv_cpu->para_synic_message_page); +} + #ifdef CONFIG_PM_SLEEP /* * Fake RESCIND_CHANNEL messages to clean up hv_sock channels by force for @@ -1233,21 +1242,19 @@ static void vmbus_force_channel_rescinded(struct vmbus_channel *channel) #endif /* CONFIG_PM_SLEEP */ /* - * Schedule all channels with events pending + * Schedule all channels with events pending. + * The event page can be directly checked to get the id of + * the channel that has the interrupt pending. */ -static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu) +static void vmbus_chan_sched(void *event_page_addr) { unsigned long *recv_int_page; u32 maxbits, relid; + union hv_synic_event_flags *event; - /* - * The event page can be directly checked to get the id of - * the channel that has the interrupt pending. - */ - void *page_addr = hv_cpu->hyp_synic_event_page; - union hv_synic_event_flags *event - = (union hv_synic_event_flags *)page_addr + - VMBUS_MESSAGE_SINT; + if (!event_page_addr) + return; + event = (union hv_synic_event_flags *)event_page_addr + VMBUS_MESSAGE_SINT; maxbits = HV_EVENT_FLAGS_COUNT; recv_int_page = event->flags; @@ -1255,6 +1262,11 @@ static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu) if (unlikely(!recv_int_page)) return; + /* + * Suggested-by: Michael Kelley + * One possible optimization would be to keep track of the largest relID that's in use, + * and only scan up to that relID. + */ for_each_set_bit(relid, recv_int_page, maxbits) { void (*callback_fn)(void *context); struct vmbus_channel *channel; @@ -1318,26 +1330,35 @@ static void vmbus_chan_sched(struct hv_per_cpu_context *hv_cpu) } } -static void vmbus_isr(void) +static void vmbus_message_sched(struct hv_per_cpu_context *hv_cpu, void *message_page_addr) { - struct hv_per_cpu_context *hv_cpu - = this_cpu_ptr(hv_context.cpu_context); - void *page_addr; struct hv_message *msg; - vmbus_chan_sched(hv_cpu); - - page_addr = hv_cpu->hyp_synic_message_page; - msg = (struct hv_message *)page_addr + VMBUS_MESSAGE_SINT; + if (!message_page_addr) + return; + msg = (struct hv_message *)message_page_addr + VMBUS_MESSAGE_SINT; /* Check if there are actual msgs to be processed */ if (msg->header.message_type != HVMSG_NONE) { if (msg->header.message_type == HVMSG_TIMER_EXPIRED) { hv_stimer0_isr(); vmbus_signal_eom(msg, HVMSG_TIMER_EXPIRED); - } else + } else { tasklet_schedule(&hv_cpu->msg_dpc); + } } +} + +static void vmbus_isr(void) +{ + struct hv_per_cpu_context *hv_cpu + = this_cpu_ptr(hv_context.cpu_context); + + vmbus_chan_sched(hv_cpu->hyp_synic_event_page); + vmbus_chan_sched(hv_cpu->para_synic_event_page); + + vmbus_message_sched(hv_cpu, hv_cpu->hyp_synic_message_page); + vmbus_message_sched(hv_cpu, hv_cpu->para_synic_message_page); add_interrupt_randomness(vmbus_interrupt); } @@ -1355,54 +1376,14 @@ static void vmbus_percpu_work(struct work_struct *work) hv_synic_init(cpu); } -/* - * vmbus_bus_init -Main vmbus driver initialization routine. - * - * Here, we - * - initialize the vmbus driver context - * - invoke the vmbus hv main init routine - * - retrieve the channel offers - */ -static int vmbus_bus_init(void) +static int vmbus_alloc_synic_and_connect(void) { int ret, cpu; struct work_struct __percpu *works; - - ret = hv_init(); - if (ret != 0) { - pr_err("Unable to initialize the hypervisor - 0x%x\n", ret); - return ret; - } - - ret = bus_register(&hv_bus); - if (ret) - return ret; - - /* - * VMbus interrupts are best modeled as per-cpu interrupts. If - * on an architecture with support for per-cpu IRQs (e.g. ARM64), - * allocate a per-cpu IRQ using standard Linux kernel functionality. - * If not on such an architecture (e.g., x86/x64), then rely on - * code in the arch-specific portion of the code tree to connect - * the VMbus interrupt handler. - */ - - if (vmbus_irq == -1) { - hv_setup_vmbus_handler(vmbus_isr); - } else { - vmbus_evt = alloc_percpu(long); - ret = request_percpu_irq(vmbus_irq, vmbus_percpu_isr, - "Hyper-V VMbus", vmbus_evt); - if (ret) { - pr_err("Can't request Hyper-V VMbus IRQ %d, Err %d", - vmbus_irq, ret); - free_percpu(vmbus_evt); - goto err_setup; - } - } + int hyperv_cpuhp_online; ret = hv_synic_alloc(); - if (ret) + if (ret < 0) goto err_alloc; works = alloc_percpu(struct work_struct); @@ -1436,6 +1417,72 @@ static int vmbus_bus_init(void) hyperv_cpuhp_online = ret; ret = vmbus_connect(); + if (ret) + goto err_connect; + return 0; + +err_connect: + cpuhp_remove_state(hyperv_cpuhp_online); + return -ENODEV; +err_alloc: + hv_synic_free(); + return -ENOMEM; +} + +/* + * vmbus_bus_init -Main vmbus driver initialization routine. + * + * Here, we + * - initialize the vmbus driver context + * - invoke the vmbus hv main init routine + * - retrieve the channel offers + */ +static int vmbus_bus_init(void) +{ + int ret; + + ret = hv_init(); + if (ret != 0) { + pr_err("Unable to initialize the hypervisor - 0x%x\n", ret); + return ret; + } + + ret = bus_register(&hv_bus); + if (ret) + return ret; + + /* + * VMbus interrupts are best modeled as per-cpu interrupts. If + * on an architecture with support for per-cpu IRQs (e.g. ARM64), + * allocate a per-cpu IRQ using standard Linux kernel functionality. + * If not on such an architecture (e.g., x86/x64), then rely on + * code in the arch-specific portion of the code tree to connect + * the VMbus interrupt handler. + */ + + if (vmbus_irq == -1) { + hv_setup_vmbus_handler(vmbus_isr); + } else { + vmbus_evt = alloc_percpu(long); + ret = request_percpu_irq(vmbus_irq, vmbus_percpu_isr, + "Hyper-V VMbus", vmbus_evt); + if (ret) { + pr_err("Can't request Hyper-V VMbus IRQ %d, Err %d", + vmbus_irq, ret); + free_percpu(vmbus_evt); + goto err_setup; + } + } + + /* + * Cache the value as getting it involves a VM exit on x86(_64), and + * doing that on each VP while initializing SynIC's wastes time. + */ + is_confidential = ms_hyperv.confidential_vmbus_available; + if (is_confidential) + pr_info("Establishing connection to the confidential VMBus\n"); + hv_para_set_sint_proxy(!is_confidential); + ret = vmbus_alloc_synic_and_connect(); if (ret) goto err_connect; @@ -1451,9 +1498,6 @@ static int vmbus_bus_init(void) return 0; err_connect: - cpuhp_remove_state(hyperv_cpuhp_online); -err_alloc: - hv_synic_free(); if (vmbus_irq == -1) { hv_remove_vmbus_handler(); } else {