diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-29 12:39:45 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-29 12:39:45 -1000 |
| commit | 9c7e3d59107e2daf204bf6bf87b6c448ca89defe (patch) | |
| tree | 59e0a03cba2d5dc957b578b0afbb5e95a39fe0ee | |
| parent | 1b8b67503725fa6deb3a4f00ea61f982bd00c152 (diff) | |
| parent | 33b28f3b313353f11fc939e9a268605e66290d0b (diff) | |
Merge tag 'hyperv-next-signed-20261028' of git://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux
Pull hyperv updates from Wei Liu:
- Update to documents and code comments (Michael Kelley)
- Harden Vmbus (Kameron Carr)
- Rework MSHV driver page deposit logic (Mukesh Rathor)
- Vmbus kexec bug fix (Shradha Gupta)
- Miscellaneous bug fixes (Daehyeon Ko, Jiale Yao, Sasha Levin)
* tag 'hyperv-next-signed-20261028' of git://git.kernel.org/pub/scm/linux/kernel/git/hyperv/linux:
Drivers: hv: Remove leftover support for old Hyper-V versions
mshv: Remove unused *_old memory deposit functions
mshv: Redesign hypervisor memory deposit logic
mshv: Rename memory deposit memory functions to _old
mshv_vtl: reject poll files with multiple waitqueues
mshv_vtl: detach poll waiter on POLLFREE
mshv_vtl: initialize poll file mutex before device registration
Documentation: hyperv: Update information on guest CPU offlining
Documentation: hyperv: Remove support for guests on Hyper-V 2012/2012R2
Drivers: hv: Run VMBus kexec cleanup from syscore shutdown
kexec: Add helper to report an active kexec reboot
tools: hv: remove unused rte_smp_mb()
Documentation: hyperv: Update documentation for sending VMBus packets
Drivers: hv: vmbus: Check ACPI companion before use
Documentation: hyperv: Describe validation of the ring buffer indices
Drivers: hv: vmbus: Keep the ring byte counts sane for a bad index
Drivers: hv: vmbus: Compute ring byte counts from a caller-held snapshot
Drivers: hv: vmbus: Use *_ONCE() to access shared ring buffer indices
Drivers: hv: vmbus: Bounds check the shared ring buffer indices
| -rw-r--r-- | Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst | 14 | ||||
| -rw-r--r-- | Documentation/virt/hyperv/clocks.rst | 8 | ||||
| -rw-r--r-- | Documentation/virt/hyperv/coco.rst | 5 | ||||
| -rw-r--r-- | Documentation/virt/hyperv/overview.rst | 28 | ||||
| -rw-r--r-- | Documentation/virt/hyperv/vmbus.rst | 17 | ||||
| -rw-r--r-- | arch/x86/kernel/cpu/mshyperv.c | 27 | ||||
| -rw-r--r-- | drivers/hv/hv_common.c | 10 | ||||
| -rw-r--r-- | drivers/hv/hv_proc.c | 246 | ||||
| -rw-r--r-- | drivers/hv/mshv_root_hv_call.c | 10 | ||||
| -rw-r--r-- | drivers/hv/mshv_vtl_main.c | 55 | ||||
| -rw-r--r-- | drivers/hv/ring_buffer.c | 126 | ||||
| -rw-r--r-- | drivers/hv/vmbus_drv.c | 30 | ||||
| -rw-r--r-- | include/asm-generic/mshyperv.h | 7 | ||||
| -rw-r--r-- | include/linux/hyperv.h | 58 | ||||
| -rw-r--r-- | include/linux/kexec.h | 2 | ||||
| -rw-r--r-- | kernel/kexec_core.c | 7 |
16 files changed, 356 insertions, 294 deletions
diff --git a/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst b/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst index 616f808e885b..0d464706a59a 100644 --- a/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst +++ b/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst @@ -7,8 +7,9 @@ Hyper-V network driver Compatibility ============= -This driver is compatible with Windows Server 2012 R2, 2016 and -Windows 10. +This driver supports Linux guests running on Windows Server 2016 Hyper-V +and later, and Windows 10 and later. Support for running as a guest on +earlier versions of Hyper-V has been removed from Linux. Features ======== @@ -16,9 +17,8 @@ Features Checksum offload ---------------- The netvsc driver supports checksum offload as long as the - Hyper-V host version does. Windows Server 2016 and Azure - support checksum offload for TCP and UDP for both IPv4 and - IPv6. Windows Server 2012 only supports checksum offload for TCP. + Hyper-V host version does. Windows Server 2016 Hyper-V and later, and + Azure, support checksum offload for TCP and UDP for both IPv4 and IPv6. Receive Side Scaling -------------------- @@ -58,8 +58,8 @@ Large Receive Offload (LRO), or Receive Side Coalescing (RSC) ------------------------------------------------------------- The driver supports LRO/RSC in the vSwitch feature. It reduces the per packet processing overhead by coalescing multiple TCP segments when possible. The - feature is enabled by default on VMs running on Windows Server 2019 and - later. It may be changed by ethtool command:: + feature is enabled by default on VMs running on Windows Server 2019 Hyper-V + and later. It may be changed by ethtool command:: ethtool -K eth0 lro on ethtool -K eth0 lro off diff --git a/Documentation/virt/hyperv/clocks.rst b/Documentation/virt/hyperv/clocks.rst index 176043265803..9527e6073654 100644 --- a/Documentation/virt/hyperv/clocks.rst +++ b/Documentation/virt/hyperv/clocks.rst @@ -64,12 +64,8 @@ applying the scale and offset to get the constant 10 MHz clock. Linux clockevents are based on Hyper-V synthetic timer 0 (stimer0). While Hyper-V offers 4 synthetic timers for each CPU, Linux only uses -timer 0. In older versions of Hyper-V, an interrupt from stimer0 -results in a VMBus control message that is demultiplexed by -vmbus_isr() as described in the Documentation/virt/hyperv/vmbus.rst -documentation. In newer versions of Hyper-V, stimer0 interrupts can -be mapped to an architectural interrupt, which is referred to as -"Direct Mode". Linux prefers to use Direct Mode when available. Since +timer 0. stimer0 is operated in what Hyper-V calls "Direct Mode" +where timer interrupts are mapped to an architectural interrupt. Since x86/x64 doesn't support per-CPU interrupts, Direct Mode statically allocates an x86 interrupt vector (HYPERV_STIMER0_VECTOR) across all CPUs and explicitly codes it to call the stimer0 interrupt handler. Hence diff --git a/Documentation/virt/hyperv/coco.rst b/Documentation/virt/hyperv/coco.rst index 3231e51444da..3e20249bd9b3 100644 --- a/Documentation/virt/hyperv/coco.rst +++ b/Documentation/virt/hyperv/coco.rst @@ -200,7 +200,10 @@ validation is performed. For messages passed from the host to the guest in a VMBus ring buffer, the length of the message is validated, and the message is copied into a temporary (encrypted) buffer for further validation and processing. The copying adds a small amount of overhead, but is the only way -to protect against a malicious host. See hv_pkt_iter_first(). +to protect against a malicious host. See hv_pkt_iter_first(). Similarly, the +ring buffer header is shared with the host, so the guest must snapshot and +validate the read and write indices before using them to index into the ring +buffer or to compute available space. Many drivers for VMBus devices have been "hardened" by adding code to fully validate messages received over VMBus, instead of assuming that Hyper-V is diff --git a/Documentation/virt/hyperv/overview.rst b/Documentation/virt/hyperv/overview.rst index 77408a89d1a4..6c1b9d4b6ddd 100644 --- a/Documentation/virt/hyperv/overview.rst +++ b/Documentation/virt/hyperv/overview.rst @@ -114,11 +114,13 @@ a shared page or remove an overlay page after the new kernel is using the page for a different purpose, corrupting the new kernel. Hyper-V does not provide a single "set everything" operation to guest VMs, so Linux code must individually revoke all sharing before -doing kexec. See hv_kexec_handler() and hv_crash_handler(). But -the crash/panic path still has holes in cleanup because some shared -pages are set using per-CPU synthetic registers and there's no -mechanism to revoke the shared pages for CPUs other than the CPU -running the panic path. +doing kexec. For a normal kexec reboot, the VMBus driver calls +hv_kexec_handler() from its syscore shutdown callback, after device +shutdown and before the architecture-specific machine shutdown. The +crash/panic path uses hv_crash_handler(), but still has holes in +cleanup because some shared pages are set using per-CPU synthetic +registers and there is no mechanism to revoke the shared pages for +CPUs other than the CPU running the panic path. CPU Management -------------- @@ -132,11 +134,11 @@ Starting in Windows Server 2022 Hyper-V, the ACPI tables reflect only the CPUs actually present in the VM, so Linux does not report any hot-add CPUs. -A Linux guest CPU may be taken offline using the normal Linux -mechanisms, provided no VMBus channel interrupts are assigned to -the CPU. See the section on VMBus Interrupts for more details -on how VMBus channel interrupts can be re-assigned to permit -taking a CPU offline. +A Linux guest CPU may be taken offline, and subsequently brought +back online, using the normal Linux mechanisms. See the "VMBus +Interrupts" section of Documentation/virt/hyperv/vmbus.rst +documentation for how VMBus channel interrupts are automatically +re-assigned if necessary when taking a CPU offline. 32-bit and 64-bit ----------------- @@ -157,9 +159,9 @@ when accessing data shared with Hyper-V. Versioning ---------- Current Linux kernels operate correctly with older versions of -Hyper-V back to Windows Server 2012 Hyper-V. Support for running -on the original Hyper-V release in Windows Server 2008/2008 R2 -has been removed. +Hyper-V back to Windows Server 2016 Hyper-V and Windows 10. Support for +running on Windows Server 2012/2012 R2, Windows Server 2008/2008 R2, +and Windows 8.1 and earlier has been removed. A Linux guest on Hyper-V outputs in dmesg the version of Hyper-V it is running on. This version is in the form of a Windows build diff --git a/Documentation/virt/hyperv/vmbus.rst b/Documentation/virt/hyperv/vmbus.rst index 654bb4849972..c546e821ce6b 100644 --- a/Documentation/virt/hyperv/vmbus.rst +++ b/Documentation/virt/hyperv/vmbus.rst @@ -141,14 +141,15 @@ Three functions exist to send VMBus channel messages: 1. vmbus_sendpacket(): Control-only messages and messages with embedded data -- no GPAs -2. vmbus_sendpacket_pagebuffer(): Message with list of GPAs - identifying data to transfer. An offset and length is - associated with each GPA so that multiple discontinuous areas - of guest memory can be targeted. -3. vmbus_sendpacket_mpb_desc(): Message with list of GPAs - identifying data to transfer. A single offset and length is - associated with a list of GPAs. The GPAs must describe a - single logical area of guest memory to be targeted. +2. vmbus_sendpacket_getid(): Similar to vmbus_sendpacket() but returns + a transaction ID that is matched against a reply from the Hyper-V + host. Useful when the struct completion (or other rendezvous data) + must reside on the caller's stack. By invalidating the transaction + ID before exiting the function due to a timeout or other error, a + late arriving interrupt is assured to not trash the stack. +3. vmbus_sendpacket_mpb_desc(): Message with one or more data ranges + to target. Each range consists of a single offset and length plus + a list of GPAs that describe a logical area of guest memory. Historically, Linux guests have trusted Hyper-V to send well-formed and valid messages, and Linux drivers for synthetic devices did not diff --git a/arch/x86/kernel/cpu/mshyperv.c b/arch/x86/kernel/cpu/mshyperv.c index b4af7c0a70ac..fec1c37dafbf 100644 --- a/arch/x86/kernel/cpu/mshyperv.c +++ b/arch/x86/kernel/cpu/mshyperv.c @@ -148,7 +148,6 @@ EXPORT_SYMBOL_GPL(hv_set_msr); static void (*mshv_handler)(void); static void (*vmbus_handler)(void); static void (*hv_stimer0_handler)(void); -static void (*hv_kexec_handler)(void); static void (*hv_crash_handler)(struct pt_regs *regs); DEFINE_IDTENTRY_SYSVEC(sysvec_hyperv_callback) @@ -215,16 +214,6 @@ void hv_remove_stimer0_handler(void) hv_stimer0_handler = NULL; } -void hv_setup_kexec_handler(void (*handler)(void)) -{ - hv_kexec_handler = handler; -} - -void hv_remove_kexec_handler(void) -{ - hv_kexec_handler = NULL; -} - void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs)) { hv_crash_handler = handler; @@ -240,17 +229,13 @@ static void hv_machine_shutdown(void) { if (kexec_in_progress) { hv_stimer_global_cleanup(); - - if (hv_kexec_handler) - hv_kexec_handler(); - } - - /* - * Call hv_cpu_die() on all the CPUs, otherwise later the hypervisor - * corrupts the old VP Assist Pages and can crash the kexec kernel. - */ - if (kexec_in_progress) + /* + * Call hv_cpu_die() on all the CPUs, otherwise later the + * hypervisor corrupts the old VP Assist Pages and can crash + * the kexec kernel. + */ cpuhp_remove_state(CPUHP_AP_HYPERV_ONLINE); + } /* The function calls stop_other_cpus(). */ native_machine_shutdown(); diff --git a/drivers/hv/hv_common.c b/drivers/hv/hv_common.c index 4f3bb9b4e87c..27b9b79d4f4c 100644 --- a/drivers/hv/hv_common.c +++ b/drivers/hv/hv_common.c @@ -711,16 +711,6 @@ void __weak hv_setup_mshv_handler(void (*handler)(void)) } EXPORT_SYMBOL_GPL(hv_setup_mshv_handler); -void __weak hv_setup_kexec_handler(void (*handler)(void)) -{ -} -EXPORT_SYMBOL_GPL(hv_setup_kexec_handler); - -void __weak hv_remove_kexec_handler(void) -{ -} -EXPORT_SYMBOL_GPL(hv_remove_kexec_handler); - void __weak hv_setup_crash_handler(void (*handler)(struct pt_regs *regs)) { } diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c index 57b2c64197cb..f811bb5c42a1 100644 --- a/drivers/hv/hv_proc.c +++ b/drivers/hv/hv_proc.c @@ -9,135 +9,177 @@ #include <linux/export.h> #include <asm/mshyperv.h> +#define HV_DEPOSIT_MAX 512 +#define HV_DEPOSIT_INP_MAX ((HV_HYP_PAGE_SIZE - \ + offsetof(struct hv_deposit_memory, gpa_page_list)) / sizeof(u64)) + /* - * See struct hv_deposit_memory. The first u64 is partition ID, the rest - * are GPAs. + * Allocate free pages for deposit to hypervisor. pfna[] must be large enough + * to hold HV_DEPOSIT_INP_MAX (511) pages. If num_pages is 512, return last + * pfn in lastpfn. If @single, then it must be a single allocation (not split + * over multiple contiguous ranges). + * + * Returns: number of pages allocated or -ENOMEM */ -#define HV_DEPOSIT_MAX (HV_HYP_PAGE_SIZE / sizeof(u64) - 1) - -/* Deposits exact number of pages. Must be called with interrupts enabled. */ -int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) +static int hv_alloc_dep_pages(int node, u64 *pfna, u64 *lastpfnp, int num_pages, + bool single) { - struct page **pages, *page; - int *counts; - int num_allocations; - int i, j, page_count; - int order; - u64 status; - int ret; - u64 base_pfn; - struct hv_deposit_memory *input_page; - unsigned long flags; - - if (num_pages > HV_DEPOSIT_MAX) - return -E2BIG; - if (!num_pages) - return 0; - - /* One buffer for page pointers and counts */ - page = alloc_page(GFP_KERNEL); - if (!page) - return -ENOMEM; - pages = page_address(page); + struct page *page; + int num_allocd, count = 0; - counts = kzalloc_objs(int, HV_DEPOSIT_MAX); - if (!counts) { - free_page((unsigned long)pages); - return -ENOMEM; - } + /* Published ABI, enforce its immutability. */ + BUILD_BUG_ON(HV_DEPOSIT_INP_MAX != 511); - /* Allocate all the pages before disabling interrupts */ - i = 0; + if (num_pages > HV_DEPOSIT_MAX || + (num_pages == HV_DEPOSIT_MAX && lastpfnp == NULL)) + return -EINVAL; while (num_pages) { /* Find highest order we can actually allocate */ - order = 31 - __builtin_clz(num_pages); + int order = 31 - __builtin_clz(num_pages); + gfp_t gfp_flags = GFP_KERNEL; + + if (!single) + gfp_flags |= __GFP_NOWARN; while (1) { - pages[i] = alloc_pages_node(node, GFP_KERNEL, order); - if (pages[i]) + page = alloc_pages_node(node, gfp_flags, order); + if (page || order == 0 || single) break; - if (!order) { - ret = -ENOMEM; - num_allocations = i; - goto err_free_allocations; - } - --order; + + order--; } - split_page(pages[i], order); - counts[i] = 1 << order; - num_pages -= counts[i]; - i++; - } - num_allocations = i; + if (page == NULL) + break; - local_irq_save(flags); + split_page(page, order); + num_allocd = 1 << order; + num_pages -= num_allocd; - input_page = *this_cpu_ptr(hyperv_pcpu_input_arg); + while (num_allocd && count < HV_DEPOSIT_INP_MAX) { + pfna[count++] = page_to_pfn(page++); + num_allocd--; + } - input_page->partition_id = partition_id; + if (num_allocd-- && count == HV_DEPOSIT_INP_MAX) { + *lastpfnp = page_to_pfn(page); + count++; + break; + } + } + + return count ? count : -ENOMEM; +} - /* Populate gpa_page_list - these will fit on the input page */ - for (i = 0, page_count = 0; i < num_allocations; ++i) { - base_pfn = page_to_pfn(pages[i]); - for (j = 0; j < counts[i]; ++j, ++page_count) - input_page->gpa_page_list[page_count] = base_pfn + j; +/* + * Deposit memory in the hypervisor. Even if @contiguous is false, a contiguous + * 2M worth of pfns is utmost desired for performance reasons. But short of + * that, we deposit whatever contiguous chunks we can get. If @contiguous is + * true, then the entire range has to be physically contiguous. Note, in that + * case, upon withdrawl, hypervisor could return any page in between the range, + * so we must split that also. Lastly, HV_MAX_CONTIGUOUS_ALLOCATION_PAGES is + * not guaranteed to always be power of 2. + */ +static int hv_call_deposit_pages(int node, u64 partition_id, bool contiguous) +{ + struct hv_deposit_memory *hc_input; + int i, rc, num_pages; + u64 status, *pfna, lastpfn = 0; + bool trunc_extra = false; + + BUILD_BUG_ON(HV_MAX_CONTIGUOUS_ALLOCATION_PAGES > HV_DEPOSIT_MAX); + + if (contiguous) { + num_pages = roundup_pow_of_two( + HV_MAX_CONTIGUOUS_ALLOCATION_PAGES); + trunc_extra = num_pages != HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; + } else { + num_pages = HV_DEPOSIT_MAX; } - status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, - page_count, 0, input_page, NULL); - local_irq_restore(flags); - if (!hv_result_success(status)) { - hv_status_err(status, "\n"); - ret = hv_result_to_errno(status); - goto err_free_allocations; + + hc_input = (struct hv_deposit_memory *)get_zeroed_page(GFP_KERNEL); + if (hc_input == NULL) + return -ENOMEM; + + hc_input->partition_id = partition_id; + pfna = hc_input->gpa_page_list; + + rc = hv_alloc_dep_pages(node, pfna, &lastpfn, num_pages, contiguous); + if (rc < 0) + goto out_free; + + num_pages = rc; + if (num_pages > HV_DEPOSIT_INP_MAX) + num_pages = HV_DEPOSIT_INP_MAX; + + if (contiguous && trunc_extra) { + for (i = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; i < num_pages; i++) + __free_page(pfn_to_page(pfna[i])); + + if (lastpfn) { + __free_page(pfn_to_page(lastpfn)); + lastpfn = 0; + } + + num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; } - ret = 0; - goto free_buf; + /* Not using hyperv_pcpu_input_arg, so no need to disable interrupts */ + + status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, num_pages, 0, + hc_input, NULL); + if (!hv_result_success(status)) + goto err_free_dep_pages; -err_free_allocations: - for (i = 0; i < num_allocations; ++i) { - base_pfn = page_to_pfn(pages[i]); - for (j = 0; j < counts[i]; ++j) - __free_page(pfn_to_page(base_pfn + j)); + if (lastpfn) { + hc_input->gpa_page_list[0] = lastpfn; + status = hv_do_rep_hypercall(HVCALL_DEPOSIT_MEMORY, 1, 0, + hc_input, NULL); + + if (!hv_result_success(status) && hv_repcomp(status) == 0) + /* We deposited lot earlier, so give it a go */ + __free_page(pfn_to_page(lastpfn)); } -free_buf: - free_page((unsigned long)pages); - kfree(counts); - return ret; + free_page((unsigned long)hc_input); + return 0; + +err_free_dep_pages: + hv_status_err(status, "\n"); + rc = hv_result_to_errno(status); + + for (i = hv_repcomp(status); i < num_pages; i++) + __free_page(pfn_to_page(pfna[i])); + if (lastpfn) + __free_page(pfn_to_page(lastpfn)); + +out_free: + free_page((unsigned long)hc_input); + return rc; } -EXPORT_SYMBOL_GPL(hv_call_deposit_pages); -int hv_deposit_memory_node(int node, u64 partition_id, - u64 hv_status) +int hv_deposit_memory_node(int node, u64 pt_id, u64 hv_status) { - u32 num_pages = 1; + int result = hv_result(hv_status); + bool contiguous = false; - switch (hv_result(hv_status)) { - case HV_STATUS_INSUFFICIENT_MEMORY: - break; - case HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY: - num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; - break; - - case HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY: - num_pages = HV_MAX_CONTIGUOUS_ALLOCATION_PAGES; - fallthrough; - case HV_STATUS_INSUFFICIENT_ROOT_MEMORY: + if (result == HV_STATUS_INSUFFICIENT_ROOT_MEMORY || + result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY) { if (!hv_root_partition()) { - hv_status_err(hv_status, "Unexpected root memory deposit\n"); - return -ENOMEM; + hv_status_err(hv_status, + "Unexpected root memory deposit\n"); + return -EINVAL; } - partition_id = HV_PARTITION_ID_SELF; - break; - default: - hv_status_err(hv_status, "Unexpected!\n"); - return -ENOMEM; + pt_id = HV_PARTITION_ID_SELF; } - return hv_call_deposit_pages(node, partition_id, num_pages); + + if (result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_MEMORY || + result == HV_STATUS_INSUFFICIENT_CONTIGUOUS_ROOT_MEMORY) + contiguous = true; + + return hv_call_deposit_pages(node, pt_id, contiguous); } EXPORT_SYMBOL_GPL(hv_deposit_memory_node); @@ -203,14 +245,6 @@ int hv_call_create_vp(int node, u64 partition_id, u32 vp_index, u32 flags) unsigned long irq_flags; int ret = 0; - /* Root VPs don't seem to need pages deposited */ - if (partition_id != hv_current_partition_id) { - /* The value 90 is empirically determined. It may change. */ - ret = hv_call_deposit_pages(node, partition_id, 90); - if (ret) - return ret; - } - do { local_irq_save(irq_flags); diff --git a/drivers/hv/mshv_root_hv_call.c b/drivers/hv/mshv_root_hv_call.c index cb55d4d4be2e..b8d199f95299 100644 --- a/drivers/hv/mshv_root_hv_call.c +++ b/drivers/hv/mshv_root_hv_call.c @@ -15,8 +15,6 @@ #include "mshv_root.h" /* Determined empirically */ -#define HV_INIT_PARTITION_DEPOSIT_PAGES 208 -#define HV_MAP_GPA_DEPOSIT_PAGES 256 #define HV_UMAP_GPA_PAGES 512 #define HV_PAGE_COUNT_2M_ALIGNED(pg_count) (!((pg_count) & (0x200 - 1))) @@ -140,11 +138,6 @@ int hv_call_initialize_partition(u64 partition_id) input.partition_id = partition_id; - ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id, - HV_INIT_PARTITION_DEPOSIT_PAGES); - if (ret) - return ret; - do { status = hv_do_fast_hypercall8(HVCALL_INITIALIZE_PARTITION, *(u64 *)&input); @@ -248,8 +241,7 @@ static int hv_do_map_gpa_hcall(u64 partition_id, u64 gfn, u64 page_struct_count, completed = hv_repcomp(status); if (hv_result_needs_memory(status)) { - ret = hv_call_deposit_pages(NUMA_NO_NODE, partition_id, - HV_MAP_GPA_DEPOSIT_PAGES); + ret = hv_deposit_memory(partition_id, status); if (ret) break; diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c index d8225242c73f..2d4fe24cecbf 100644 --- a/drivers/hv/mshv_vtl_main.c +++ b/drivers/hv/mshv_vtl_main.c @@ -17,6 +17,7 @@ #include <linux/entry-virt.h> #include <linux/eventfd.h> #include <linux/poll.h> +#include <linux/rcupdate.h> #include <linux/file.h> #include <linux/vmalloc.h> #include <asm/debugreg.h> @@ -72,6 +73,7 @@ struct mshv_vtl_poll_file { wait_queue_entry_t wait; wait_queue_head_t *wqh; poll_table pt; + int error; int cpu; }; @@ -95,7 +97,7 @@ union hv_synic_overlay_page_msr { } __packed; }; -static struct mutex mshv_vtl_poll_file_lock; +static DEFINE_MUTEX(mshv_vtl_poll_file_lock); static union hv_register_vsm_page_offsets mshv_vsm_page_offsets; static union hv_register_vsm_capabilities mshv_vsm_capabilities; @@ -448,17 +450,51 @@ static void mshv_vtl_cancel(int cpu) static int mshv_vtl_poll_file_wake(wait_queue_entry_t *wait, unsigned int mode, int sync, void *key) { struct mshv_vtl_poll_file *poll_file = container_of(wait, struct mshv_vtl_poll_file, wait); + __poll_t pollflags = key_to_poll(key); + + if (pollflags & POLLFREE) + list_del_init(&wait->entry); mshv_vtl_cancel(poll_file->cpu); + if (pollflags & POLLFREE) { + /* Publish teardown last; pair with acquire in the removal path. */ + smp_store_release(&poll_file->wqh, NULL); + } + return 0; } +static void mshv_vtl_poll_file_remove_wait_queue(struct mshv_vtl_poll_file *poll_file) +{ + wait_queue_head_t *wqh; + + /* POLLFREE providers RCU-delay the waitqueue free. */ + rcu_read_lock(); + /* Pair with the release store in the POLLFREE callback. */ + wqh = smp_load_acquire(&poll_file->wqh); + if (wqh) + remove_wait_queue(wqh, &poll_file->wait); + rcu_read_unlock(); + WRITE_ONCE(poll_file->wqh, NULL); +} + static void mshv_vtl_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh, poll_table *pt) { struct mshv_vtl_poll_file *poll_file = container_of(pt, struct mshv_vtl_poll_file, pt); - WARN_ON(poll_file->wqh); + if (poll_file->error) + return; + + if (poll_file->wqh) { + if (poll_file->wqh == wqh) + return; + + mshv_vtl_poll_file_remove_wait_queue(poll_file); + poll_file->error = -EINVAL; + return; + } + poll_file->wqh = wqh; add_wait_queue(wqh, &poll_file->wait); } @@ -468,6 +504,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us struct file *file, *old_file; struct mshv_vtl_poll_file *poll_file; struct mshv_vtl_set_poll_file input; + int ret; if (copy_from_user(&input, user_input, sizeof(input))) return -EFAULT; @@ -490,12 +527,11 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us mutex_lock(&mshv_vtl_poll_file_lock); - if (poll_file->wqh) - remove_wait_queue(poll_file->wqh, &poll_file->wait); - poll_file->wqh = NULL; + mshv_vtl_poll_file_remove_wait_queue(poll_file); old_file = poll_file->file; poll_file->file = file; + poll_file->error = 0; poll_file->cpu = input.cpu; if (file) { @@ -503,13 +539,18 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us init_poll_funcptr(&poll_file->pt, mshv_vtl_ptable_queue_proc); vfs_poll(file, &poll_file->pt); } + ret = poll_file->error; + if (ret) + poll_file->file = NULL; mutex_unlock(&mshv_vtl_poll_file_lock); if (old_file) fput(old_file); + if (ret) + fput(file); - return 0; + return ret; } /* Static table mapping register names to their corresponding actions */ @@ -1370,8 +1411,6 @@ static int __init mshv_vtl_init(void) goto free_low; } - mutex_init(&mshv_vtl_poll_file_lock); - device_initialize(mem_dev); dev_set_name(mem_dev, "mshv vtl mem dev"); ret = device_add(mem_dev); diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 592a9601faaa..d8b085dfdb46 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -70,21 +70,12 @@ static void hv_signal_on_write(u32 old_write, struct vmbus_channel *channel) } } -/* Get the next write location for the specified ring buffer. */ -static inline u32 -hv_get_next_write_location(struct hv_ring_buffer_info *ring_info) -{ - u32 next = ring_info->ring_buffer->write_index; - - return next; -} - /* Set the next write location for the specified ring buffer. */ static inline void hv_set_next_write_location(struct hv_ring_buffer_info *ring_info, u32 next_write_location) { - ring_info->ring_buffer->write_index = next_write_location; + WRITE_ONCE(ring_info->ring_buffer->write_index, next_write_location); } /* Get the size of the ring buffer. */ @@ -94,13 +85,6 @@ hv_get_ring_buffersize(const struct hv_ring_buffer_info *ring_info) return ring_info->ring_datasize; } -/* Get the read and write indices as u64 of the specified ring buffer. */ -static inline u64 -hv_get_ring_bufferindices(struct hv_ring_buffer_info *ring_info) -{ - return (u64)ring_info->ring_buffer->write_index << 32; -} - /* * Helper routine to copy from source to ring buffer. * Assume there is enough room. Handles wrap-around in dest case only!! @@ -123,35 +107,12 @@ static u32 hv_copyto_ringbuffer( return start_write_offset; } -/* - * - * hv_get_ringbuffer_availbytes() - * - * Get number of bytes available to read and to write to - * for the specified ring buffer - */ -static void -hv_get_ringbuffer_availbytes(const struct hv_ring_buffer_info *rbi, - u32 *read, u32 *write) -{ - u32 read_loc, write_loc, dsize; - - /* Capture the read/write indices before they changed */ - read_loc = READ_ONCE(rbi->ring_buffer->read_index); - write_loc = READ_ONCE(rbi->ring_buffer->write_index); - dsize = rbi->ring_datasize; - - *write = write_loc >= read_loc ? dsize - (write_loc - read_loc) : - read_loc - write_loc; - *read = dsize - *write; -} - /* Get various debug metrics for the specified ring buffer. */ int hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, struct hv_ring_buffer_debug_info *debug_info) { - u32 bytes_avail_towrite; - u32 bytes_avail_toread; + u32 read_index; + u32 write_index; mutex_lock(&ring_info->ring_buffer_mutex); @@ -160,13 +121,14 @@ int hv_ringbuffer_get_debuginfo(struct hv_ring_buffer_info *ring_info, return -EINVAL; } - hv_get_ringbuffer_availbytes(ring_info, - &bytes_avail_toread, - &bytes_avail_towrite); - debug_info->bytes_avail_toread = bytes_avail_toread; - debug_info->bytes_avail_towrite = bytes_avail_towrite; - debug_info->current_read_index = ring_info->ring_buffer->read_index; - debug_info->current_write_index = ring_info->ring_buffer->write_index; + read_index = READ_ONCE(ring_info->ring_buffer->read_index); + write_index = READ_ONCE(ring_info->ring_buffer->write_index); + debug_info->bytes_avail_toread = + hv_ringbuffer_avail_read(ring_info, read_index, write_index); + debug_info->bytes_avail_towrite = + hv_ringbuffer_avail_write(ring_info, read_index, write_index); + debug_info->current_read_index = read_index; + debug_info->current_write_index = write_index; debug_info->current_interrupt_mask = ring_info->ring_buffer->interrupt_mask; mutex_unlock(&ring_info->ring_buffer_mutex); @@ -281,6 +243,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, u32 totalbytes_towrite = sizeof(u64); u32 next_write_location; u32 old_write; + u32 read_index; u64 prev_indices; unsigned long flags; struct hv_ring_buffer_info *outring_info = &channel->outbound; @@ -295,7 +258,19 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, spin_lock_irqsave(&outring_info->ring_lock, flags); - bytes_avail_towrite = hv_get_bytes_to_write(outring_info); + read_index = READ_ONCE(outring_info->ring_buffer->read_index); + old_write = READ_ONCE(outring_info->ring_buffer->write_index); + if (unlikely(hv_ringbuffer_indices_invalid(outring_info, read_index, + old_write))) { + spin_unlock_irqrestore(&outring_info->ring_lock, flags); + pr_err_ratelimited("outbound ring indices out of range: relid %u read %u write %u size %u\n", + channel->offermsg.child_relid, read_index, + old_write, outring_info->ring_datasize); + return -EIO; + } + + bytes_avail_towrite = hv_ringbuffer_avail_write(outring_info, read_index, + old_write); /* * If there is only room for the packet, assume it is full. @@ -317,9 +292,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, channel->out_full_flag = false; /* Write to the ring buffer */ - next_write_location = hv_get_next_write_location(outring_info); - - old_write = next_write_location; + next_write_location = old_write; for (i = 0; i < kv_count; i++) { next_write_location = hv_copyto_ringbuffer(outring_info, @@ -355,7 +328,7 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, *trans_id = __trans_id; /* Set previous packet start */ - prev_indices = hv_get_ring_bufferindices(outring_info); + prev_indices = (u64)old_write << 32; next_write_location = hv_copyto_ringbuffer(outring_info, next_write_location, @@ -435,8 +408,9 @@ int hv_ringbuffer_read(struct vmbus_channel *channel, * This is similar to hv_get_bytes_to_read but with private * read index instead. */ -static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi) +static u32 hv_pkt_iter_avail(const struct vmbus_channel *channel) { + const struct hv_ring_buffer_info *rbi = &channel->inbound; u32 priv_read_loc = rbi->priv_read_index; u32 write_loc; @@ -448,11 +422,15 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi) * stale data. */ write_loc = virt_load_acquire(&rbi->ring_buffer->write_index); + if (unlikely(hv_ringbuffer_indices_invalid(rbi, priv_read_loc, + write_loc))) { + pr_err_ratelimited("inbound write index out of range: relid %u write %u size %u\n", + channel->offermsg.child_relid, write_loc, + rbi->ring_datasize); + return 0; + } - if (write_loc >= priv_read_loc) - return write_loc - priv_read_loc; - else - return (rbi->ring_datasize - priv_read_loc) + write_loc; + return hv_ringbuffer_avail_read(rbi, priv_read_loc, write_loc); } /* @@ -468,7 +446,7 @@ struct vmpacket_descriptor *hv_pkt_iter_first(struct vmbus_channel *channel) hv_debug_delay_test(channel, MESSAGE_DELAY); - bytes_avail = hv_pkt_iter_avail(rbi); + bytes_avail = hv_pkt_iter_avail(channel); if (bytes_avail < sizeof(struct vmpacket_descriptor)) return NULL; bytes_avail = min(rbi->pkt_buffer_size, bytes_avail); @@ -572,6 +550,7 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) { struct hv_ring_buffer_info *rbi = &channel->inbound; u32 curr_write_sz, pending_sz, bytes_read, start_read_index; + u32 write_index; /* * Make sure all reads are done before we update the read index since @@ -579,16 +558,8 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) * is updated. */ virt_rmb(); - start_read_index = rbi->ring_buffer->read_index; - rbi->ring_buffer->read_index = rbi->priv_read_index; - - /* - * Older versions of Hyper-V (before WS2102 and Win8) do not - * implement pending_send_sz and simply poll if the host->guest - * ring buffer is full. No signaling is needed or expected. - */ - if (!rbi->ring_buffer->feature_bits.feat_pending_send_sz) - return; + start_read_index = READ_ONCE(rbi->ring_buffer->read_index); + WRITE_ONCE(rbi->ring_buffer->read_index, rbi->priv_read_index); /* * Issue a full memory barrier before making the signaling decision. @@ -611,11 +582,17 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) return; /* - * Ensure the read of write_index in hv_get_bytes_to_write() - * happens after the read of pending_send_sz. + * Ensure the read of write_index happens after the read of + * pending_send_sz. */ virt_rmb(); - curr_write_sz = hv_get_bytes_to_write(rbi); + write_index = READ_ONCE(rbi->ring_buffer->write_index); + if (unlikely(hv_ringbuffer_indices_invalid(rbi, start_read_index, + write_index))) + return; + + curr_write_sz = hv_ringbuffer_avail_write(rbi, rbi->priv_read_index, + write_index); bytes_read = hv_pkt_iter_bytes_read(rbi, start_read_index); /* @@ -631,8 +608,7 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) * Exactly filling the ring buffer is treated as "not enough * space". The ring buffer always must have at least one byte * empty so the empty and full conditions are distinguishable. - * hv_get_bytes_to_write() doesn't fully tell the truth in - * this regard. + * curr_write_sz doesn't fully tell the truth in this regard. * * So first check if we were in the "enough free space" state * before we began the iteration. If so, the host was not diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4692687cdbf3..c8330fffb138 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -33,6 +33,7 @@ #include <linux/sysfb.h> #include <linux/efi.h> #include <linux/kernel.h> +#include <linux/kexec.h> #include <linux/syscore_ops.h> #include <linux/dma-map-ops.h> #include <linux/pci.h> @@ -2571,10 +2572,14 @@ EXPORT_SYMBOL_GPL(vmbus_free_mmio); #ifdef CONFIG_ACPI static int vmbus_acpi_add(struct platform_device *pdev) { + struct acpi_device *ancestor; + struct acpi_device *device; acpi_status result; int ret_val = -ENODEV; - struct acpi_device *ancestor; - struct acpi_device *device = ACPI_COMPANION(&pdev->dev); + + device = ACPI_COMPANION(&pdev->dev); + if (!device) + return -ENODEV; vmbus_root_device = &device->dev; @@ -2904,6 +2909,12 @@ static void hv_kexec_handler(void) cpuhp_remove_state(hyperv_cpuhp_online); }; +static void hv_vmbus_syscore_shutdown(void *data) +{ + if (kexec_is_in_progress()) + hv_kexec_handler(); +} + static void hv_crash_handler(struct pt_regs *regs) { int cpu; @@ -2954,14 +2965,15 @@ static void hv_synic_resume(void *data) */ } -/* The callbacks run only on CPU0, with irqs_disabled. */ -static const struct syscore_ops hv_synic_syscore_ops = { +/* The suspend and resume callbacks run only on CPU0, with irqs_disabled. */ +static const struct syscore_ops hv_vmbus_syscore_ops = { .suspend = hv_synic_suspend, .resume = hv_synic_resume, + .shutdown = hv_vmbus_syscore_shutdown, }; -static struct syscore hv_synic_syscore = { - .ops = &hv_synic_syscore_ops, +static struct syscore hv_vmbus_syscore = { + .ops = &hv_vmbus_syscore_ops, }; static int __init hv_acpi_init(void) @@ -3010,10 +3022,9 @@ static int __init hv_acpi_init(void) if (ret) goto cleanup; - hv_setup_kexec_handler(hv_kexec_handler); hv_setup_crash_handler(hv_crash_handler); - register_syscore(&hv_synic_syscore); + register_syscore(&hv_vmbus_syscore); return 0; @@ -3035,9 +3046,8 @@ static void __exit vmbus_exit(void) */ return; - unregister_syscore(&hv_synic_syscore); + unregister_syscore(&hv_vmbus_syscore); - hv_remove_kexec_handler(); hv_remove_crash_handler(); hv_stimer_global_cleanup(); vmbus_disconnect(); diff --git a/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h index 34d4c332449c..4c9f3353074a 100644 --- a/include/asm-generic/mshyperv.h +++ b/include/asm-generic/mshyperv.h @@ -187,8 +187,6 @@ void hv_remove_vmbus_handler(void); void hv_setup_stimer0_handler(void (*handler)(void)); void hv_remove_stimer0_handler(void); -void hv_setup_kexec_handler(void (*handler)(void)); -void hv_remove_kexec_handler(void); void hv_setup_crash_handler(void (*handler)(struct pt_regs *regs)); void hv_remove_crash_handler(void); void hv_setup_mshv_handler(void (*handler)(void)); @@ -349,7 +347,6 @@ static inline bool hv_parent_partition(void) bool hv_result_needs_memory(u64 status); int hv_deposit_memory_node(int node, u64 partition_id, u64 status); -int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages); int hv_call_add_logical_proc(int node, u32 lp_index, u32 acpi_id); int hv_call_notify_all_processors_started(void); bool hv_lp_exists(u32 lp_index); @@ -364,10 +361,6 @@ static inline int hv_deposit_memory_node(int node, u64 partition_id, u64 status) { return -EOPNOTSUPP; } -static inline int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) -{ - return -EOPNOTSUPP; -} static inline int hv_call_add_logical_proc(int node, u32 lp_index, u32 acpi_id) { return -EOPNOTSUPP; diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index e61f9a4cb7c3..e85a1a043eac 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -209,31 +209,63 @@ struct hv_ring_buffer_info { }; +/* + * The indices live in memory shared with the untrusted host, so check a + * snapshot of them before using it as an offset or to compute a byte count. + */ +static inline bool +hv_ringbuffer_indices_invalid(const struct hv_ring_buffer_info *rbi, + u32 read_loc, u32 write_loc) +{ + return read_loc >= rbi->ring_datasize || + write_loc >= rbi->ring_datasize; +} + +/* + * Byte counts for a caller-supplied snapshot of the indices, so that the + * counts and the indices the caller goes on to use describe one state of the + * ring. + */ +static inline u32 +hv_ringbuffer_avail_write(const struct hv_ring_buffer_info *rbi, + u32 read_loc, u32 write_loc) +{ + u32 dsize = rbi->ring_datasize; + + return write_loc >= read_loc ? dsize - (write_loc - read_loc) : + read_loc - write_loc; +} + +static inline u32 +hv_ringbuffer_avail_read(const struct hv_ring_buffer_info *rbi, + u32 read_loc, u32 write_loc) +{ + return rbi->ring_datasize - + hv_ringbuffer_avail_write(rbi, read_loc, write_loc); +} + static inline u32 hv_get_bytes_to_read(const struct hv_ring_buffer_info *rbi) { - u32 read_loc, write_loc, dsize, read; + u32 read_loc, write_loc; - dsize = rbi->ring_datasize; - read_loc = rbi->ring_buffer->read_index; + read_loc = READ_ONCE(rbi->ring_buffer->read_index); write_loc = READ_ONCE(rbi->ring_buffer->write_index); + if (unlikely(hv_ringbuffer_indices_invalid(rbi, read_loc, write_loc))) + return 0; - read = write_loc >= read_loc ? (write_loc - read_loc) : - (dsize - read_loc) + write_loc; - - return read; + return hv_ringbuffer_avail_read(rbi, read_loc, write_loc); } static inline u32 hv_get_bytes_to_write(const struct hv_ring_buffer_info *rbi) { - u32 read_loc, write_loc, dsize, write; + u32 read_loc, write_loc; - dsize = rbi->ring_datasize; read_loc = READ_ONCE(rbi->ring_buffer->read_index); - write_loc = rbi->ring_buffer->write_index; + write_loc = READ_ONCE(rbi->ring_buffer->write_index); + if (unlikely(hv_ringbuffer_indices_invalid(rbi, read_loc, write_loc))) + return 0; - write = write_loc >= read_loc ? dsize - (write_loc - read_loc) : - read_loc - write_loc; - return write; + return hv_ringbuffer_avail_write(rbi, read_loc, write_loc); } static inline u32 hv_get_avail_to_write_percent( diff --git a/include/linux/kexec.h b/include/linux/kexec.h index f970ca2c8ce9..bac11e940e74 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -453,6 +453,7 @@ bool kexec_load_permitted(int kexec_image_type); /* flag to track if kexec reboot is in progress */ extern bool kexec_in_progress; +bool kexec_is_in_progress(void); #ifndef page_to_boot_pfn static inline unsigned long page_to_boot_pfn(struct page *page) @@ -529,6 +530,7 @@ static inline void *kimage_map_segment(struct kimage *image, int idx) { return NULL; } static inline void kimage_unmap_segment(void *buffer) { } #define kexec_in_progress false +static inline bool kexec_is_in_progress(void) { return false; } #endif /* CONFIG_KEXEC_CORE */ #ifdef CONFIG_KEXEC_SIG diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index 7ee8c9f078f6..f4a31565c447 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -23,6 +23,7 @@ #include <linux/hardirq.h> #include <linux/elf.h> #include <linux/elfcore.h> +#include <linux/export.h> #include <linux/utsname.h> #include <linux/numa.h> #include <linux/suspend.h> @@ -54,6 +55,12 @@ atomic_t __kexec_lock = ATOMIC_INIT(0); /* Flag to indicate we are going to kexec a new kernel */ bool kexec_in_progress = false; +bool kexec_is_in_progress(void) +{ + return kexec_in_progress; +} +EXPORT_SYMBOL_GPL(kexec_is_in_progress); + bool kexec_file_dbg_print; /* |
