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 /drivers | |
| 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
Diffstat (limited to 'drivers')
| -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 |
6 files changed, 259 insertions, 218 deletions
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(); |
