mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Emerson Busson <emersonbusson@gmail.com>
To: linux-hyperv@vger.kernel.org
Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
	decui@microsoft.com, linux-kernel@vger.kernel.org,
	emersonbusson@gmail.com
Subject: [PATCH 2/2] hv: vmbus: add virtual memory fallback for ring buffer allocations under memory pressure
Date: Thu, 17 Sep 2026 22:40:17 -0300	[thread overview]
Message-ID: <20260918014017.2536753-3-emersonbusson@gmail.com> (raw)
In-Reply-To: <20260918014017.2536753-1-emersonbusson@gmail.com>

When VMBus sub-channels (such as synthetic SCSI, network, or vsock
channels) are dynamically opened during periods of sustained memory
load or memory tier eviction, `vmbus_alloc_ring()` attempts to
allocate physically contiguous memory using
`alloc_pages(GFP_KERNEL | __GFP_ZERO, order)`.

For standard rings (order-7, 512 KiB contiguous memory), high buddy
allocator fragmentation under memory pressure frequently causes
`alloc_pages()` to fail with -ENOMEM even when ample total virtual
memory is available. This manifests in userspace as connection
timeouts (e.g. `accept4 failed 110: Connection timed out` on WSL2 vsock
control planes).

This patch introduces a resilient fallback mechanism:
1. When `alloc_pages()` fails due to external fragmentation,
   `vmbus_alloc_ring()` falls back to `vzalloc_node()` (or `vzalloc()`)
   to satisfy the buffer allocation from virtually contiguous pages.
2. In `hv_ringbuffer_init()`, detects `is_vmalloc_addr(virt_addr)` and
   populates the `pages_wraparound` mapping array using
   `vmalloc_to_page()`.
3. In `vmbus_free_ring()`, tracks `ringbuffer_is_vmalloc` and calls
   `vfree()` safely, preserving Confidential VM (CoCo) memory
   re-encryption isolation checks prior to release.

Signed-off-by: Emerson Busson <emersonbusson@gmail.com>
---
 drivers/hv/channel.c      | 46 ++++++++++++++++++++++++++++++++++-----
 drivers/hv/hyperv_vmbus.h |  2 +-
 drivers/hv/ring_buffer.c  | 19 +++++++++++-----
 include/linux/hyperv.h    |  2 ++
 4 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 162d6aeec..f0fb3dd8f 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -12,6 +12,7 @@
 #include <linux/sched.h>
 #include <linux/wait.h>
 #include <linux/mm.h>
+#include <linux/vmalloc.h>
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/hyperv.h>
@@ -153,13 +154,20 @@ void vmbus_free_ring(struct vmbus_channel *channel)
 	hv_ringbuffer_cleanup(&channel->outbound);
 	hv_ringbuffer_cleanup(&channel->inbound);
 
-	if (channel->ringbuffer_page) {
+	if (channel->ringbuffer_is_vmalloc && channel->ringbuffer_page_virt) {
+		/* In a CoCo VM leak the memory if it didn't get re-encrypted */
+		if (!channel->ringbuffer_gpadlhandle.decrypted)
+			vfree(channel->ringbuffer_page_virt);
+		channel->ringbuffer_page_virt = NULL;
+		channel->ringbuffer_is_vmalloc = false;
+	} else if (channel->ringbuffer_page) {
 		/* In a CoCo VM leak the memory if it didn't get re-encrypted */
 		if (!channel->ringbuffer_gpadlhandle.decrypted)
 			__free_pages(channel->ringbuffer_page,
 			     get_order(channel->ringbuffer_pagecount
 				       << PAGE_SHIFT));
 		channel->ringbuffer_page = NULL;
+		channel->ringbuffer_page_virt = NULL;
 	}
 }
 EXPORT_SYMBOL_GPL(vmbus_free_ring);
@@ -182,10 +190,26 @@ int vmbus_alloc_ring(struct vmbus_channel *newchannel,
 	if (!page)
 		page = alloc_pages(GFP_KERNEL|__GFP_ZERO, order);
 
-	if (!page)
-		return -ENOMEM;
+	if (!page) {
+		/* Fallback to virtual memory allocation under buddy fragmentation */
+		void *virt_addr = vzalloc_node(send_size + recv_size,
+					       cpu_to_node(newchannel->target_cpu));
+
+		if (!virt_addr)
+			virt_addr = vzalloc(send_size + recv_size);
+
+		if (!virt_addr)
+			return -ENOMEM;
+
+		newchannel->ringbuffer_page = NULL;
+		newchannel->ringbuffer_page_virt = virt_addr;
+		newchannel->ringbuffer_is_vmalloc = true;
+	} else {
+		newchannel->ringbuffer_page = page;
+		newchannel->ringbuffer_page_virt = page_address(page);
+		newchannel->ringbuffer_is_vmalloc = false;
+	}
 
-	newchannel->ringbuffer_page = page;
 	newchannel->ringbuffer_pagecount = (send_size + recv_size) >> PAGE_SHIFT;
 	newchannel->ringbuffer_send_offset = send_size >> PAGE_SHIFT;
 
@@ -639,6 +663,7 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
 	struct vmbus_channel_open_channel *open_msg;
 	struct vmbus_channel_msginfo *open_info = NULL;
 	struct page *page = newchannel->ringbuffer_page;
+	void *inbound_virt = NULL;
 	u32 send_pages, recv_pages;
 	unsigned long flags;
 	int err;
@@ -669,6 +694,8 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
 	newchannel->ringbuffer_gpadlhandle.gpadl_handle = 0;
 
 	err = __vmbus_establish_gpadl(newchannel, HV_GPADL_RING,
+				      newchannel->ringbuffer_page_virt ?
+				      newchannel->ringbuffer_page_virt :
 				      page_address(newchannel->ringbuffer_page),
 				      (send_pages + recv_pages) << PAGE_SHIFT,
 				      newchannel->ringbuffer_send_offset << PAGE_SHIFT,
@@ -677,11 +704,18 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
 		goto error_clean_ring;
 
 	err = hv_ringbuffer_init(&newchannel->outbound,
-				 page, send_pages, 0);
+				 page, newchannel->ringbuffer_page_virt,
+				 send_pages, 0);
 	if (err)
 		goto error_free_gpadl;
 
-	err = hv_ringbuffer_init(&newchannel->inbound, &page[send_pages],
+	if (newchannel->ringbuffer_page_virt)
+		inbound_virt = newchannel->ringbuffer_page_virt +
+			       (send_pages << PAGE_SHIFT);
+
+	err = hv_ringbuffer_init(&newchannel->inbound,
+				 page ? &page[send_pages] : NULL,
+				 inbound_virt,
 				 recv_pages, newchannel->max_pkt_size);
 	if (err)
 		goto error_free_gpadl;
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 34943de7d..ec06c30d2 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -182,7 +182,7 @@ extern int hv_synic_cleanup(unsigned int cpu);
 void hv_ringbuffer_pre_init(struct vmbus_channel *channel);
 
 int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
-		       struct page *pages, u32 pagecnt, u32 max_pkt_size);
+		       struct page *pages, void *virt_addr, u32 pagecnt, u32 max_pkt_size);
 
 void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);
 
diff --git a/drivers/hv/ring_buffer.c b/drivers/hv/ring_buffer.c
index 23ce1fb70..e6d4cf185 100644
--- a/drivers/hv/ring_buffer.c
+++ b/drivers/hv/ring_buffer.c
@@ -184,7 +184,7 @@ void hv_ringbuffer_pre_init(struct vmbus_channel *channel)
 
 /* Initialize the ring buffer. */
 int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
-		       struct page *pages, u32 page_cnt, u32 max_pkt_size)
+		       struct page *pages, void *virt_addr, u32 page_cnt, u32 max_pkt_size)
 {
 	struct page **pages_wraparound;
 	int i;
@@ -201,10 +201,19 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
 	if (!pages_wraparound)
 		return -ENOMEM;
 
-	pages_wraparound[0] = pages;
-	for (i = 0; i < 2 * (page_cnt - 1); i++)
-		pages_wraparound[i + 1] =
-			&pages[i % (page_cnt - 1) + 1];
+	if (virt_addr && is_vmalloc_addr(virt_addr)) {
+		pages_wraparound[0] = vmalloc_to_page(virt_addr);
+		for (i = 0; i < 2 * (page_cnt - 1); i++) {
+			void *curr_virt = virt_addr + ((i % (page_cnt - 1) + 1) << PAGE_SHIFT);
+
+			pages_wraparound[i + 1] = vmalloc_to_page(curr_virt);
+		}
+	} else {
+		pages_wraparound[0] = pages;
+		for (i = 0; i < 2 * (page_cnt - 1); i++)
+			pages_wraparound[i + 1] =
+				&pages[i % (page_cnt - 1) + 1];
+	}
 
 	ring_info->ring_buffer = (struct hv_ring_buffer *)
 		vmap(pages_wraparound, page_cnt * 2 - 1, VM_MAP,
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index a76f556f5..63203df4d 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -807,6 +807,8 @@ struct vmbus_channel {
 
 	/* Allocated memory for ring buffer */
 	struct page *ringbuffer_page;
+	void *ringbuffer_page_virt;
+	bool ringbuffer_is_vmalloc;
 	u32 ringbuffer_pagecount;
 	u32 ringbuffer_send_offset;
 	struct hv_ring_buffer_info outbound;	/* send to parent */
-- 
2.43.0


      parent reply	other threads:[~2026-09-18  1:40 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-18  1:40 [PATCH 0/2] hv: vmbus: prevent control-plane starvation and order-7 ring deadlocks " Emerson Busson
2026-09-18  1:40 ` [PATCH 1/2] hv: vmbus: prevent control-plane starvation and balloon thrash " Emerson Busson
2026-09-18  1:40 ` Emerson Busson [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260918014017.2536753-3-emersonbusson@gmail.com \
    --to=emersonbusson@gmail.com \
    --cc=decui@microsoft.com \
    --cc=haiyangz@microsoft.com \
    --cc=kys@microsoft.com \
    --cc=linux-hyperv@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=wei.liu@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®