From 1d287c17cc5bfd6386fe0770999be410b248d021 Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Mon, 19 Oct 2026 16:09:46 -0700 Subject: Drivers: hv: vmbus: Bounds check the shared ring buffer indices In a CoCo VM the host is untrusted. Since the VMBus ring buffer read and write indices live in shared memory, the guest has to treat both as potentially malicious. hv_ringbuffer_write() copies into the ring at write_index with no bounds checking, so the host can make the guest write packet data at any offset up to 4 GiB past the start of the ring buffer. read_index matters too: the available space derived from both indices is the only bound on how much is copied, and an out-of-range index can make it far larger than the ring. The fix is to validate both indices before use and to use the validated snapshot instead of re-accessing. For invalid indices, hv_ringbuffer_write() logs and returns -EIO. One re-read of write_index is left unchecked in this patch: hv_get_ring_bufferindices() fetches it again for the prev_indices trailer. The value is only returned to the host and is not used as an offset or length for indexing. Since this is not a security issue, it is addressed later in the series. Open-coding the bytes available arithmetic keeps the fix free of prerequisites; a later patch refactors it into a helper function. Fixes: 3e7ee4902fe6 ("Staging: hv: add the Hyper-V virtual bus") Cc: stable@vger.kernel.org # 5.15.x Reviewed-by: Michael Kelley Signed-off-by: Kameron Carr Signed-off-by: Wei Liu --- drivers/hv/ring_buffer.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 592a9601faaa..a18b309c0f4e 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -70,15 +70,6 @@ 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, @@ -281,6 +272,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 +287,20 @@ 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(read_index >= outring_info->ring_datasize || + old_write >= outring_info->ring_datasize)) { + 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 = old_write >= read_index ? + outring_info->ring_datasize - (old_write - read_index) : + read_index - old_write; /* * If there is only room for the packet, assume it is full. @@ -317,9 +322,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, -- cgit v1.2.3 From 10e2eba7ccb507af62bfff72687c8c559fa4d689 Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Mon, 19 Oct 2026 16:09:47 -0700 Subject: Drivers: hv: vmbus: Use *_ONCE() to access shared ring buffer indices The ring buffer read/write indices live in a page shared with the host, so the compiler must not split, merge or refetch accesses to them. Add READ_ONCE()/WRITE_ONCE() in hv_set_next_write_location(), hv_pkt_iter_close(), hv_get_bytes_to_read() and hv_get_bytes_to_write(). The accesses in hv_ringbuffer_get_debuginfo() are left to the next patch. Drop hv_get_ring_bufferindices(). It has one caller and is a one-line expression on the write index. Inlining it moves the access to the call site, so hv_ringbuffer_write() can reuse its validated snapshot of that index, old_write, rather than reading the shared memory a second time. No functional change intended for a well-behaved host. Reviewed-by: Michael Kelley Signed-off-by: Kameron Carr Signed-off-by: Wei Liu --- drivers/hv/ring_buffer.c | 15 ++++----------- include/linux/hyperv.h | 4 ++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index a18b309c0f4e..7f466c56ba0f 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -75,7 +75,7 @@ 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. */ @@ -85,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!! @@ -358,7 +351,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, @@ -582,8 +575,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; + start_read_index = READ_ONCE(rbi->ring_buffer->read_index); + WRITE_ONCE(rbi->ring_buffer->read_index, rbi->priv_read_index); /* * Older versions of Hyper-V (before WS2102 and Win8) do not diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 9e109d91aa14..5c6582085b16 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -214,7 +214,7 @@ static inline u32 hv_get_bytes_to_read(const struct hv_ring_buffer_info *rbi) u32 read_loc, write_loc, dsize, read; 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); read = write_loc >= read_loc ? (write_loc - read_loc) : @@ -229,7 +229,7 @@ static inline u32 hv_get_bytes_to_write(const struct hv_ring_buffer_info *rbi) 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); write = write_loc >= read_loc ? dsize - (write_loc - read_loc) : read_loc - write_loc; -- cgit v1.2.3 From a8e60890cb86a181d8216ac7e60873ba9d7d2ff9 Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Mon, 19 Oct 2026 16:09:48 -0700 Subject: Drivers: hv: vmbus: Compute ring byte counts from a caller-held snapshot hv_get_ringbuffer_availbytes() reads the indices directly, so a caller that also wants the index values has to read them a second time. The host can change them in between, resulting in the byte counts and the indices reflecting two different states of the ring. Replace it with hv_ringbuffer_avail_write() and hv_ringbuffer_avail_read(), which take the indices as arguments and return a single count. They are separate because most callers want only one of the two values. Use them in hv_ringbuffer_write() in place of the open-coded equivalent. hv_get_bytes_to_read(), hv_get_bytes_to_write() and hv_pkt_iter_avail() duplicated the same arithmetic, so put the helpers in include/linux/hyperv.h and use them there too. Add a hv_ringbuffer_indices_invalid() helper that bounds checks a pair of read and write indices, and use it for the check in hv_ringbuffer_write(). Convert the remaining callers to use a single snapshot: - hv_ringbuffer_get_debuginfo() now reports the indices and the computed byte counts from the same snapshot. - hv_pkt_iter_close() computes the free space from priv_read_index instead of re-reading the shared read index. The two agree unless a misbehaving host has rewritten the value. No functional change intended for a well-behaved host. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/ring_buffer.c | 68 ++++++++++++++++-------------------------------- include/linux/hyperv.h | 50 +++++++++++++++++++++++++++-------- 2 files changed, 61 insertions(+), 57 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 7f466c56ba0f..cda957d5d5c6 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -107,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); @@ -144,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); @@ -282,8 +260,8 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, read_index = READ_ONCE(outring_info->ring_buffer->read_index); old_write = READ_ONCE(outring_info->ring_buffer->write_index); - if (unlikely(read_index >= outring_info->ring_datasize || - old_write >= outring_info->ring_datasize)) { + 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, @@ -291,9 +269,8 @@ int hv_ringbuffer_write(struct vmbus_channel *channel, return -EIO; } - bytes_avail_towrite = old_write >= read_index ? - outring_info->ring_datasize - (old_write - read_index) : - read_index - old_write; + 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. @@ -445,10 +422,7 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi) */ write_loc = virt_load_acquire(&rbi->ring_buffer->write_index); - 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); } /* @@ -568,6 +542,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 @@ -607,11 +582,13 @@ 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); + 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); /* @@ -627,8 +604,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/include/linux/hyperv.h b/include/linux/hyperv.h index 5c6582085b16..09f25ade88b0 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -209,31 +209,59 @@ 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 = READ_ONCE(rbi->ring_buffer->read_index); write_loc = READ_ONCE(rbi->ring_buffer->write_index); - 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 = READ_ONCE(rbi->ring_buffer->write_index); - 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( -- cgit v1.2.3 From be0d83e869430232ca56f439eabac9c99758221f Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Mon, 19 Oct 2026 16:09:49 -0700 Subject: Drivers: hv: vmbus: Keep the ring byte counts sane for a bad index The remaining users of the shared indices can't cause a bad memory access with the channels a CoCo VM accepts today, but an out-of-range index gives them a nonsense byte count. Give them defined behavior instead. hv_pkt_iter_first() bounds its memcpy() by hv_pkt_iter_avail(), which is derived from write_index, and by pkt_buffer_size, which comes from max_pkt_size and can exceed ring_datasize. A bad write index can then make it read past the end of the ring. Only KVP has such a max_pkt_size (16K on a 12K ring with 4K pages), and vmbus_is_valid_offer() rejects it in isolated VMs, so this is latent. hv_pkt_iter_avail() now reports an empty ring for a bad write index and logs it, rate-limited like hv_ringbuffer_write(). It takes the channel instead of the ring so the message can include the relid. hv_get_bytes_to_read() and hv_get_bytes_to_write() now return 0 for a bad index, so callers see nothing to read and no room to write. This also stops hv_end_read() from reporting data that hv_pkt_iter_first() won't return, which would keep a channel rescheduling its callback. hv_pkt_iter_close() only uses the indices to decide whether to signal the host, so skip the signal if either is out of range. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/ring_buffer.c | 16 ++++++++++++++-- include/linux/hyperv.h | 4 ++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index cda957d5d5c6..72f30ab23d1d 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -408,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; @@ -421,6 +422,13 @@ 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; + } return hv_ringbuffer_avail_read(rbi, priv_read_loc, write_loc); } @@ -438,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); @@ -587,6 +595,10 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) */ virt_rmb(); 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); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index 09f25ade88b0..216c0b9e9be5 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -250,6 +250,8 @@ static inline u32 hv_get_bytes_to_read(const struct hv_ring_buffer_info *rbi) 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; return hv_ringbuffer_avail_read(rbi, read_loc, write_loc); } @@ -260,6 +262,8 @@ static inline u32 hv_get_bytes_to_write(const struct hv_ring_buffer_info *rbi) 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; return hv_ringbuffer_avail_write(rbi, read_loc, write_loc); } -- cgit v1.2.3 From 6eb81a2a06e73490cfba9b1a89b73bff44e0399b Mon Sep 17 00:00:00 2001 From: Kameron Carr Date: Mon, 19 Oct 2026 16:09:50 -0700 Subject: Documentation: hyperv: Describe validation of the ring buffer indices Document that the guest must snapshot and validate the read and write indices before using them to index into the ring buffer or to compute the available space. Signed-off-by: Kameron Carr Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- Documentation/virt/hyperv/coco.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 -- cgit v1.2.3 From dd57772e36f72f8d9a53d7fa4d90c974d8ebbcff Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Sat, 17 Oct 2026 14:43:13 +0800 Subject: Drivers: hv: vmbus: Check ACPI companion before use Platform drivers can be forced to bind to devices outside their match tables through driver_override. vmbus_acpi_add() assumes that every bound device has an ACPI companion and dereferences it without checking. Return -ENODEV when the companion is absent to avoid a NULL pointer dereference. Signed-off-by: Jiale Yao Fixes: 9c8434238041 ("Drivers: hv: vmbus: Convert acpi_device to more generic platform_device") Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- drivers/hv/vmbus_drv.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 4692687cdbf3..28f459679406 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -2571,10 +2571,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; -- cgit v1.2.3 From 6a3d77a0a8cf69cf3d234305e6711729eb5d10c4 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Mon, 12 Oct 2026 18:43:54 -0700 Subject: Documentation: hyperv: Update documentation for sending VMBus packets The functions for sending VMBus packets have changed. vmbus_sendpacket_mpb_desc() can target multiple ranges, and vmbus_sendpacket_pagebuffer() has been removed. vmbus_sendpacket_getid() has been added. Update the documentation accordingly. Fixes: 45a442fe369e ("Drivers: hv: vmbus: Remove vmbus_sendpacket_pagebuffer()") Cc: stable@vger.kernel.org Signed-off-by: Michael Kelley Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- Documentation/virt/hyperv/vmbus.rst | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) 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 -- cgit v1.2.3 From 016ac8174d932db3615534ba15ce5f70b3a15913 Mon Sep 17 00:00:00 2001 From: Sasha Levin Date: Sun, 11 Oct 2026 18:33:40 -0400 Subject: tools: hv: remove unused rte_smp_mb() rte_smp_mb() has never been used, and its "static inline __always_inline" declares inline twice, so clang warns: vmbus_bufring.c:61:15: warning: duplicate 'inline' declaration specifier [-Wduplicate-decl-specifier] vmbus_bufring.c:62:1: warning: unused function 'rte_smp_mb' [-Wunused-function] Remove it. Fixes: 45bab4d746510 ("tools: hv: Add vmbus_bufring") Assisted-by: LLM Signed-off-by: Sasha Levin Reviewed-by: Hamza Mahfooz Acked-by: Saurabh Sengar Signed-off-by: Wei Liu --- tools/hv/vmbus_bufring.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/hv/vmbus_bufring.c b/tools/hv/vmbus_bufring.c index bac32c1109df..23542e59a2ee 100644 --- a/tools/hv/vmbus_bufring.c +++ b/tools/hv/vmbus_bufring.c @@ -58,12 +58,6 @@ void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen) br->dsize = blen - sizeof(struct vmbus_bufring); } -static inline __always_inline void -rte_smp_mb(void) -{ - asm volatile("lock addl $0, -128(%%rsp); " ::: "memory"); -} - static inline int rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src) { -- cgit v1.2.3 From 14c73a2b061e494ab6da346ab075b14f2a4b2d06 Mon Sep 17 00:00:00 2001 From: Shradha Gupta Date: Fri, 9 Oct 2026 03:22:32 -0700 Subject: kexec: Add helper to report an active kexec reboot A shutdown callback in a loadable module may need to distinguish a kexec reboot from a normal restart, halt, or poweroff. The Hyper-V VMBus driver needs this distinction when moving its kexec cleanup into a common syscore shutdown callback. Add kexec_is_in_progress() and export it to GPL modules so the callback can remain guarded when VMBus is built as a module. Provide a false inline implementation when CONFIG_KEXEC_CORE is disabled. The helper reports only the normal kexec reboot path. Crash kexec and preserve-context kexec do not set this state. Signed-off-by: Shradha Gupta Acked-by: Mike Rapoport (Microsoft) Reviewed-by: Michael Kelley Reviewed-by: Bradley Morgan Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- include/linux/kexec.h | 2 ++ kernel/kexec_core.c | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/include/linux/kexec.h b/include/linux/kexec.h index 0af8ae4fdd08..aeb74b47c36e 100644 --- a/include/linux/kexec.h +++ b/include/linux/kexec.h @@ -468,6 +468,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) @@ -544,6 +545,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 dc770b9a6d05..282c7b3c6c35 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -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; /* -- cgit v1.2.3 From 228e21764b389a71a05af6a356efa1bad0751b5e Mon Sep 17 00:00:00 2001 From: Shradha Gupta Date: Fri, 9 Oct 2026 03:22:46 -0700 Subject: Drivers: hv: Run VMBus kexec cleanup from syscore shutdown ARM64 Hyper-V guests do not run the VMBus kexec cleanup before secondary CPUs are stopped. Consequently, the old VMBus connection and SynIC state remain active when the kexec kernel starts. Run the VMBus kexec handler from the existing VMBus syscore shutdown callback. syscore_shutdown() runs after device shutdown and before machine shutdown, while all CPUs needed by the SynIC CPU hotplug teardown are still available. Guard the callback with kexec_is_in_progress() so normal restart, halt, and poweroff paths are unchanged. Use the same path on x86 and remove the architecture-specific handler pointer and the weak setup/remove interfaces. VMBus teardown now precedes hv_stimer_global_cleanup() on x86. The new x86 ordering relies on commit be0cfab740e5 ("clocksource: hyper-v: Remove support for stimer interrupts in message mode"), which removed the synthetic timer's dependency on VMBus message delivery. Older kernels that retain message-mode stimer interrupts must preserve the existing x86 ordering and therefore require an ARM64-specific backport. Document the VMBus cleanup path and its position in shutdown ordering. Fixes: 9bbb888824e3 ("arm64: hyperv: Initialize hypervisor on boot") Link: https://lore.kernel.org/all/20260814093133.3191250-1-shradhagupta@linux.microsoft.com/ Suggested-by: Catalin Marinas Suggested-by: Michael Kelley Signed-off-by: Shradha Gupta Reviewed-by: Michael Kelley Reviewed-by: Bradley Morgan Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- Documentation/virt/hyperv/overview.rst | 12 +++++++----- arch/x86/kernel/cpu/mshyperv.c | 27 ++++++--------------------- drivers/hv/hv_common.c | 10 ---------- drivers/hv/vmbus_drv.c | 22 ++++++++++++++-------- include/asm-generic/mshyperv.h | 2 -- 5 files changed, 27 insertions(+), 46 deletions(-) diff --git a/Documentation/virt/hyperv/overview.rst b/Documentation/virt/hyperv/overview.rst index 77408a89d1a4..34a53ba7a122 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 -------------- 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 31256cb22b39..bb290a5eaf1d 100644 --- a/drivers/hv/hv_common.c +++ b/drivers/hv/hv_common.c @@ -681,16 +681,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/vmbus_drv.c b/drivers/hv/vmbus_drv.c index 28f459679406..c8330fffb138 100644 --- a/drivers/hv/vmbus_drv.c +++ b/drivers/hv/vmbus_drv.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -2908,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; @@ -2958,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) @@ -3014,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; @@ -3039,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 bf601d67cecb..98fe5c0f54ea 100644 --- a/include/asm-generic/mshyperv.h +++ b/include/asm-generic/mshyperv.h @@ -183,8 +183,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)); -- cgit v1.2.3 From 7b5903dea8adb782391a7c7ee19835bf12145d93 Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Fri, 9 Oct 2026 10:40:07 -0700 Subject: Documentation: hyperv: Remove support for guests on Hyper-V 2012/2012R2 Support for running Linux guests on Windows Server 2012/2012 R2 Hyper-V hosts and Windows 8/8.1 hosts has been removed, which includes removing support for operating Hyper-V synthetic timers in other than Direct Mode. Update the documentation to reflect these removals. Signed-off-by: Michael Kelley Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- .../device_drivers/ethernet/microsoft/netvsc.rst | 14 +++++++------- Documentation/virt/hyperv/clocks.rst | 8 ++------ Documentation/virt/hyperv/overview.rst | 6 +++--- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst b/Documentation/networking/device_drivers/ethernet/microsoft/netvsc.rst index fc5acd427a5d..5a2a8b56d9ff 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/overview.rst b/Documentation/virt/hyperv/overview.rst index 34a53ba7a122..a5088dfef680 100644 --- a/Documentation/virt/hyperv/overview.rst +++ b/Documentation/virt/hyperv/overview.rst @@ -159,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 -- cgit v1.2.3 From daa782262efe0de83e61e7b0d2862fc377f5226d Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Sat, 17 Oct 2026 09:08:48 -0700 Subject: Documentation: hyperv: Update information on guest CPU offlining In Hyper-V Overview documentation, remove the out-of-date reference to explicitly reassigning VMBus channel interrupts before taking a guest CPU offline. Instead, reference the VMBus documentation, which is already up-to-date on this topic. Signed-off-by: Michael Kelley Reviewed-by: Naman Jain Reviewed-by: Hamza Mahfooz Signed-off-by: Wei Liu --- Documentation/virt/hyperv/overview.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Documentation/virt/hyperv/overview.rst b/Documentation/virt/hyperv/overview.rst index a5088dfef680..6c1b9d4b6ddd 100644 --- a/Documentation/virt/hyperv/overview.rst +++ b/Documentation/virt/hyperv/overview.rst @@ -134,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 ----------------- -- cgit v1.2.3 From 60de22022fe0cb37bdec608d60d53292050903d5 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Sun, 18 Oct 2026 11:46:16 +0800 Subject: mshv_vtl: initialize poll file mutex before device registration mshv_vtl_init() registers /dev/mshv before initializing the poll file mutex. A concurrent MSHV_SET_POLL_FILE ioctl can therefore attempt to lock an uninitialized mutex while module initialization is still in progress. Initialize the file-scope mutex statically so it is ready before the misc device becomes visible. Fixes: 7bfe3b8ea6e3 ("Drivers: hv: Introduce mshv_vtl driver") Signed-off-by: Jiale Yao Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- drivers/hv/mshv_vtl_main.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c index d8225242c73f..2b40c9e3bf88 100644 --- a/drivers/hv/mshv_vtl_main.c +++ b/drivers/hv/mshv_vtl_main.c @@ -95,7 +95,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; @@ -1370,8 +1370,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); -- cgit v1.2.3 From efc5dc4c15713fdd6e87882686a26ed1deb22dab Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 19 Oct 2026 17:43:48 +0900 Subject: mshv_vtl: detach poll waiter on POLLFREE MSHV_SET_POLL_FILE keeps a poll wait entry after the ioctl returns. A file reference does not always pin the registered waitqueue: signalfd_poll() uses current->sighand->signalfd_wqh, whose lifetime is tied to the task. signalfd_cleanup() sends POLLFREE before the sighand is freed. The MSHV callback currently leaves its entry linked, so __wake_up_pollfree() warns and a later replacement calls remove_wait_queue() through a stale wqh pointer. A source-equivalent KASAN run reproduced: BUG: KASAN: slab-use-after-free in _raw_spin_lock_irqsave _raw_spin_lock_irqsave remove_wait_queue harness_ioctl [mshv_pollfree_harness] The object belonged to sighand_cache and had been released through slab_free_after_rcu_debug() after __cleanup_sighand(). On POLLFREE, unlink the entry under the provider waitqueue lock. Preserve the existing CPU cancellation before publishing a NULL wqh, since a NULL load lets a racing replacement reuse the persistent poll state. Use a matching acquire load under RCU when removing a normal registration. The callback takes no mutex and does not sleep, so the existing mutex-to-waitqueue-lock order is unchanged. An LLM assisted the source audit, fix development, and validation of this issue. Fixes: 7bfe3b8ea6e3 ("Drivers: hv: Introduce mshv_vtl driver") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- drivers/hv/mshv_vtl_main.c | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c index 2b40c9e3bf88..1ffa904837b3 100644 --- a/drivers/hv/mshv_vtl_main.c +++ b/drivers/hv/mshv_vtl_main.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -448,12 +449,35 @@ 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); @@ -490,9 +514,7 @@ 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; -- cgit v1.2.3 From 70cd4623f3e5068a72c72791b96f2dcb4c991131 Mon Sep 17 00:00:00 2001 From: Daehyeon Ko <4ncienth@gmail.com> Date: Mon, 19 Oct 2026 17:43:49 +0900 Subject: mshv_vtl: reject poll files with multiple waitqueues MSHV_SET_POLL_FILE invokes a file descriptor poll callback with a poll table backed by a single embedded wait entry. A poll callback may, however, register the table with more than one waitqueue. pipe_poll() does this naturally for an O_RDWR FIFO. The second registration currently warns but then inserts the same list node into another list. The new insertion overwrites the entry links while the first waitqueue still points at it, corrupting the first waitqueue and causing an invalid callback traversal when that queue wakes. Reject a second distinct waitqueue, remove the first registration through the common removal helper, and return -EINVAL from the ioctl. Repeated registration of the same waitqueue is treated as a no-op. A rejected replacement leaves the CPU slot unregistered, consistent with the existing remove-before-register ordering. Single-waitqueue users such as the OpenHCL io_uring poll file keep their existing behavior. An LLM assisted the source audit, fix development, and validation of this issue. Fixes: 7bfe3b8ea6e3 ("Drivers: hv: Introduce mshv_vtl driver") Cc: stable@vger.kernel.org Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> Reviewed-by: Naman Jain Signed-off-by: Wei Liu --- drivers/hv/mshv_vtl_main.c | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/drivers/hv/mshv_vtl_main.c b/drivers/hv/mshv_vtl_main.c index 1ffa904837b3..2d4fe24cecbf 100644 --- a/drivers/hv/mshv_vtl_main.c +++ b/drivers/hv/mshv_vtl_main.c @@ -73,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; }; @@ -482,7 +483,18 @@ static void mshv_vtl_ptable_queue_proc(struct file *file, wait_queue_head_t *wqh { 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); } @@ -492,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; @@ -518,6 +531,7 @@ static int mshv_vtl_ioctl_set_poll_file(struct mshv_vtl_set_poll_file __user *us old_file = poll_file->file; poll_file->file = file; + poll_file->error = 0; poll_file->cpu = input.cpu; if (file) { @@ -525,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 */ -- cgit v1.2.3 From 28513249f2a97374947cce0ddf3003aeb407ccae Mon Sep 17 00:00:00 2001 From: Mukesh R Date: Fri, 11 Sep 2026 17:03:16 -0700 Subject: mshv: Rename memory deposit memory functions to _old Rename hv_call_deposit_pages() and hv_deposit_memory_node() to _old to make reviewing the new functions much easier. Signed-off-by: Mukesh R Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/hv_proc.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c index 57b2c64197cb..57864bb5bcd8 100644 --- a/drivers/hv/hv_proc.c +++ b/drivers/hv/hv_proc.c @@ -13,10 +13,10 @@ * See struct hv_deposit_memory. The first u64 is partition ID, the rest * are GPAs. */ -#define HV_DEPOSIT_MAX (HV_HYP_PAGE_SIZE / sizeof(u64) - 1) +#define HV_DEPOSIT_MAX_OLD (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_call_deposit_pages_old(int node, u64 partition_id, u32 num_pages) { struct page **pages, *page; int *counts; @@ -29,7 +29,7 @@ int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) struct hv_deposit_memory *input_page; unsigned long flags; - if (num_pages > HV_DEPOSIT_MAX) + if (num_pages > HV_DEPOSIT_MAX_OLD) return -E2BIG; if (!num_pages) return 0; @@ -40,7 +40,7 @@ int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) return -ENOMEM; pages = page_address(page); - counts = kzalloc_objs(int, HV_DEPOSIT_MAX); + counts = kzalloc_objs(int, HV_DEPOSIT_MAX_OLD); if (!counts) { free_page((unsigned long)pages); return -ENOMEM; @@ -108,10 +108,14 @@ free_buf: kfree(counts); return ret; } + +int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) +{ + return hv_call_deposit_pages_old(node, partition_id, num_pages); +} EXPORT_SYMBOL_GPL(hv_call_deposit_pages); -int hv_deposit_memory_node(int node, u64 partition_id, - u64 hv_status) +static int __maybe_unused hv_deposit_memory_node_old(int node, u64 partition_id, u64 hv_status) { u32 num_pages = 1; @@ -137,7 +141,12 @@ int hv_deposit_memory_node(int node, u64 partition_id, hv_status_err(hv_status, "Unexpected!\n"); return -ENOMEM; } - return hv_call_deposit_pages(node, partition_id, num_pages); + return hv_call_deposit_pages_old(node, partition_id, num_pages); +} + +int hv_deposit_memory_node(int node, u64 partition_id, u64 hv_status) +{ + return hv_deposit_memory_node_old(node, partition_id, hv_status); } EXPORT_SYMBOL_GPL(hv_deposit_memory_node); -- cgit v1.2.3 From 56fb075b77f85dbde5404755ebb244a83b131c6e Mon Sep 17 00:00:00 2001 From: Mukesh R Date: Fri, 11 Sep 2026 17:03:17 -0700 Subject: mshv: Redesign hypervisor memory deposit logic The current memory deposit implementation has a few issues and bugs: o It is very slow: one of the main contributions to slow VM creations and boot is many hypercalls repeatedly coming back with insufficient memory. On Hyper-V, a hypercall returns with such status whenever it cannot complete due to lack of memory in the hypervisor and needs more. o Contiguous range requirement is broken, and is critical bug o An incorrect assumption is made that contiguous memory size would always be power of 2. o Two pages are allocated, only one is really needed. Extra alloc adds to the overhead. o For a 512 page deposit, the allocation is split into two: one for 511 and second for 1. Thus, an order 9 allocation never happens. A contiguous 2M range, if possible, greatly improves performance in the hypervisor as it can be mapped as large page whenever possible. Also, removing extra allocation reduces overhead in linux. o Since a page is already allocated to collect the frames, there is not really a need to use per cpu input page, and hence avoid local irq disable. o In hv_call_deposit_pages(), in case of error, under err_free_allocations label, all pages are freed without checking status to see if some pages were deposited. This is a critical bug as it would free pages that hyp may be using. All of above is addressed by: o Allocate 2M by default, this is the recommendation from the hypervisor team, and greatly improves performance. Depositing one or few pages at a time results in lot of insufficient memory returns from hypercalls. o Always start with a full 2M range allocation, thus getting contiguous if available. In cases where possible, the deposits are much faster. o Allocate only one page in the deposit function and collect 511 pfns there. Just use a local variable for last pfn. o Use the page as input to hypercall. Since this page is locally allocated, irq disable can be avoided helping speed up the deposit. o Fix the physical contiguous memory enforcement.. o Lastly, remove pre-deposits hv_call_create_vp() and hv_call_initialize_partition() as they were removed internally while ago, most likely because they didn't help much. Signed-off-by: Mukesh R Reviewed-by: Souradeep Chakrabarti Signed-off-by: Wei Liu --- drivers/hv/hv_proc.c | 194 ++++++++++++++++++++++++++++++++++++----- drivers/hv/mshv_root_hv_call.c | 10 +-- include/asm-generic/mshyperv.h | 5 -- 3 files changed, 175 insertions(+), 34 deletions(-) diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c index 57864bb5bcd8..df39a5c587ca 100644 --- a/drivers/hv/hv_proc.c +++ b/drivers/hv/hv_proc.c @@ -9,6 +9,180 @@ #include #include +#define HV_DEPOSIT_MAX 512 +#define HV_DEPOSIT_INP_MAX ((HV_HYP_PAGE_SIZE - \ + offsetof(struct hv_deposit_memory, gpa_page_list)) / sizeof(u64)) + +/* + * 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 + */ +static int hv_alloc_dep_pages(int node, u64 *pfna, u64 *lastpfnp, int num_pages, + bool single) +{ + struct page *page; + int num_allocd, count = 0; + + /* Published ABI, enforce its immutability. */ + BUILD_BUG_ON(HV_DEPOSIT_INP_MAX != 511); + + 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 */ + int order = 31 - __builtin_clz(num_pages); + gfp_t gfp_flags = GFP_KERNEL; + + if (!single) + gfp_flags |= __GFP_NOWARN; + + while (1) { + page = alloc_pages_node(node, gfp_flags, order); + if (page || order == 0 || single) + break; + + order--; + } + + if (page == NULL) + break; + + split_page(page, order); + num_allocd = 1 << order; + num_pages -= num_allocd; + + while (num_allocd && count < HV_DEPOSIT_INP_MAX) { + pfna[count++] = page_to_pfn(page++); + num_allocd--; + } + + if (num_allocd-- && count == HV_DEPOSIT_INP_MAX) { + *lastpfnp = page_to_pfn(page); + count++; + break; + } + } + + return count ? count : -ENOMEM; +} + +/* + * 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; + } + + 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; + } + + /* 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; + + 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_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; +} + +int hv_deposit_memory_node(int node, u64 pt_id, u64 hv_status) +{ + int result = hv_result(hv_status); + bool contiguous = false; + + 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 -EINVAL; + } + + pt_id = HV_PARTITION_ID_SELF; + } + + 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); + /* * See struct hv_deposit_memory. The first u64 is partition ID, the rest * are GPAs. @@ -109,12 +283,6 @@ free_buf: return ret; } -int hv_call_deposit_pages(int node, u64 partition_id, u32 num_pages) -{ - return hv_call_deposit_pages_old(node, partition_id, num_pages); -} -EXPORT_SYMBOL_GPL(hv_call_deposit_pages); - static int __maybe_unused hv_deposit_memory_node_old(int node, u64 partition_id, u64 hv_status) { u32 num_pages = 1; @@ -144,12 +312,6 @@ static int __maybe_unused hv_deposit_memory_node_old(int node, u64 partition_id, return hv_call_deposit_pages_old(node, partition_id, num_pages); } -int hv_deposit_memory_node(int node, u64 partition_id, u64 hv_status) -{ - return hv_deposit_memory_node_old(node, partition_id, hv_status); -} -EXPORT_SYMBOL_GPL(hv_deposit_memory_node); - bool hv_result_needs_memory(u64 status) { switch (hv_result(status)) { @@ -212,14 +374,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/include/asm-generic/mshyperv.h b/include/asm-generic/mshyperv.h index 98fe5c0f54ea..064c40bb2821 100644 --- a/include/asm-generic/mshyperv.h +++ b/include/asm-generic/mshyperv.h @@ -343,7 +343,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); @@ -358,10 +357,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; -- cgit v1.2.3 From 347f8745462dad74022284e34ddd9d1c16b7473f Mon Sep 17 00:00:00 2001 From: Mukesh R Date: Fri, 11 Sep 2026 17:03:18 -0700 Subject: mshv: Remove unused *_old memory deposit functions Remove the renamed *_old functions, done for ease of review. Signed-off-by: Mukesh R Reviewed-by: Michael Kelley Signed-off-by: Wei Liu --- drivers/hv/hv_proc.c | 129 --------------------------------------------------- 1 file changed, 129 deletions(-) diff --git a/drivers/hv/hv_proc.c b/drivers/hv/hv_proc.c index df39a5c587ca..f811bb5c42a1 100644 --- a/drivers/hv/hv_proc.c +++ b/drivers/hv/hv_proc.c @@ -183,135 +183,6 @@ int hv_deposit_memory_node(int node, u64 pt_id, u64 hv_status) } EXPORT_SYMBOL_GPL(hv_deposit_memory_node); -/* - * See struct hv_deposit_memory. The first u64 is partition ID, the rest - * are GPAs. - */ -#define HV_DEPOSIT_MAX_OLD (HV_HYP_PAGE_SIZE / sizeof(u64) - 1) - -/* Deposits exact number of pages. Must be called with interrupts enabled. */ -static int hv_call_deposit_pages_old(int node, u64 partition_id, u32 num_pages) -{ - 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_OLD) - 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); - - counts = kzalloc_objs(int, HV_DEPOSIT_MAX_OLD); - if (!counts) { - free_page((unsigned long)pages); - return -ENOMEM; - } - - /* Allocate all the pages before disabling interrupts */ - i = 0; - - while (num_pages) { - /* Find highest order we can actually allocate */ - order = 31 - __builtin_clz(num_pages); - - while (1) { - pages[i] = alloc_pages_node(node, GFP_KERNEL, order); - if (pages[i]) - break; - if (!order) { - ret = -ENOMEM; - num_allocations = i; - goto err_free_allocations; - } - --order; - } - - split_page(pages[i], order); - counts[i] = 1 << order; - num_pages -= counts[i]; - i++; - } - num_allocations = i; - - local_irq_save(flags); - - input_page = *this_cpu_ptr(hyperv_pcpu_input_arg); - - input_page->partition_id = partition_id; - - /* 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; - } - 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; - } - - ret = 0; - goto free_buf; - -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)); - } - -free_buf: - free_page((unsigned long)pages); - kfree(counts); - return ret; -} - -static int __maybe_unused hv_deposit_memory_node_old(int node, u64 partition_id, u64 hv_status) -{ - u32 num_pages = 1; - - 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 (!hv_root_partition()) { - hv_status_err(hv_status, "Unexpected root memory deposit\n"); - return -ENOMEM; - } - partition_id = HV_PARTITION_ID_SELF; - break; - - default: - hv_status_err(hv_status, "Unexpected!\n"); - return -ENOMEM; - } - return hv_call_deposit_pages_old(node, partition_id, num_pages); -} - bool hv_result_needs_memory(u64 status) { switch (hv_result(status)) { -- cgit v1.2.3 From 33b28f3b313353f11fc939e9a268605e66290d0b Mon Sep 17 00:00:00 2001 From: Michael Kelley Date: Thu, 22 Oct 2026 20:05:27 -0700 Subject: Drivers: hv: Remove leftover support for old Hyper-V versions When removing support for running Linux guests on Windows Server 2008/2008R2 and Windows 7, one area was missed due to the code comment spelling "2012" incorrectly. Remove that functionality now. Signed-off-by: Michael Kelley Reviewed-by: Hamza Mahfooz Signed-off-by: Wei Liu --- drivers/hv/ring_buffer.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c index 72f30ab23d1d..d8b085dfdb46 100644 --- a/drivers/hv/ring_buffer.c +++ b/drivers/hv/ring_buffer.c @@ -561,14 +561,6 @@ void hv_pkt_iter_close(struct vmbus_channel *channel) start_read_index = READ_ONCE(rbi->ring_buffer->read_index); WRITE_ONCE(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; - /* * Issue a full memory barrier before making the signaling decision. * If reading pending_send_sz were to be reordered and happen -- cgit v1.2.3