Linux-HyperV List
 help / color / mirror / Atom feed
* [PATCH v3 0/3] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses
@ 2026-08-06 21:33 Kameron Carr
  2026-08-06 21:33 ` [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
                   ` (2 more replies)
  0 siblings, 3 replies; 7+ messages in thread
From: Kameron Carr @ 2026-08-06 21:33 UTC (permalink / raw)
  To: decui, haiyangz, kys, longli, wei.liu, mhklinux
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
	linux-kernel, netdev

Arm CCA Realms implement set_memory_decrypted() only for addresses in the
kernel linear map; vmalloc()/vmap() addresses are rejected. netvsc
allocates its large send and receive buffers with vzalloc() and passes
those addresses to vmbus_establish_gpadl(), which calls
set_memory_decrypted(), causing GPADL establishment to fail in a Realm.

This series allocates shared buffers as a list of chunks, each physically
contiguous, decrypts each chunk through its direct-map address, and
combines them into a virtually contiguous mapping with vmap(). Private
buffers continue to use vzalloc().

Changes since v1 [1]:
  Patch 1:
    - Add HV_GPADL_BUFFER_DECRYPTED to enum hv_gpadl_type to indicate a
      buffer that has already been decrypted.
  Patch 2:
    - Rename netvsc_{alloc,free}_buf_pages() -> vmbus_{alloc,free}_buffer()
      and move them to channel.c. 
    - Remove struct netvsc_buf_chunk and replace with page folio in
      compound pages.
    - vmbus_alloc_buffer():
      - use vzalloc() for non-host-visible buffers and remove the
        encryption decision out of netvsc.
      - min_t() -> min().
      - zero the memory after allocation.
      - specify @size "will be rounded up to PAGE_SIZE" in the docstring.

Changes since v2 [2]:
  Patch 2:
    - Split patch into two patches, separating new helper functions in 
      VMBus from the netvsc driver changes.
    - Change `nr_pages`, `remaining`, and `page_idx` to unsigned long to
      avoid casting.
    - Move `order` initialization to the top of vmbus_alloc_buffer(),
      removing one min() call.
    - Change `order` to a signed int to avoid needing min_t().
    - Restructure loop in vmbus_alloc_buffer() to avoid nested loops and
      simplify logic.
    - Small comment changes.

[1]: https://lore.kernel.org/all/20260721195633.1438361-1-kameroncarr@linux.microsoft.com/
[2]: https://lore.kernel.org/all/20260730233359.3850612-1-kameroncarr@linux.microsoft.com/


Kameron Carr (3):
  Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted()
  hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs
  hv_netvsc: Allocate host-visible GPADL buffers using
    vmbus_alloc_buffer()

 drivers/hv/channel.c            | 182 +++++++++++++++++++++++++++++++-
 drivers/net/hyperv/hyperv_net.h |   8 +-
 drivers/net/hyperv/netvsc.c     | 104 ++++++++++++------
 drivers/net/hyperv/netvsc_drv.c |   6 ++
 include/linux/hyperv.h          |  15 ++-
 5 files changed, 278 insertions(+), 37 deletions(-)


base-commit: a4ffc59238be84dd1c26bf1c001543e832674fc6
-- 
2.45.4


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted()
  2026-08-06 21:33 [PATCH v3 0/3] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses Kameron Carr
@ 2026-08-06 21:33 ` Kameron Carr
  2026-08-07 15:11   ` Michael Kelley
  2026-08-06 21:33 ` [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs Kameron Carr
  2026-08-06 21:33 ` [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer() Kameron Carr
  2 siblings, 1 reply; 7+ messages in thread
From: Kameron Carr @ 2026-08-06 21:33 UTC (permalink / raw)
  To: decui, haiyangz, kys, longli, wei.liu, mhklinux
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
	linux-kernel, netdev

Add a new vmbus_establish_gpadl_caller_decrypted() for callers that want to
decrypt their own buffers. Add a new hv_gpadl_type,
HV_GPADL_BUFFER_DECRYPTED, to communicate the decryption status of the
buffer.

No functional change for existing callers.

Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
 drivers/hv/channel.c   | 27 +++++++++++++++++++++++++--
 include/linux/hyperv.h |  8 +++++++-
 2 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 6821f225248b1..4782f5070bba9 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -40,6 +40,7 @@ static inline u32 hv_gpadl_size(enum hv_gpadl_type type, u32 size)
 {
 	switch (type) {
 	case HV_GPADL_BUFFER:
+	case HV_GPADL_BUFFER_DECRYPTED:
 		return size;
 	case HV_GPADL_RING:
 		/* The size of a ringbuffer must be page-aligned */
@@ -100,6 +101,7 @@ static inline u64 hv_gpadl_hvpfn(enum hv_gpadl_type type, void *kbuffer,
 
 	switch (type) {
 	case HV_GPADL_BUFFER:
+	case HV_GPADL_BUFFER_DECRYPTED:
 		break;
 	case HV_GPADL_RING:
 		if (i == 0)
@@ -460,7 +462,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
 	}
 
 	gpadl->decrypted = !((channel->co_external_memory && type == HV_GPADL_BUFFER) ||
-		(channel->co_ring_buffer && type == HV_GPADL_RING));
+		(channel->co_ring_buffer && type == HV_GPADL_RING) ||
+		(type == HV_GPADL_BUFFER_DECRYPTED));
 	if (gpadl->decrypted) {
 		/*
 		 * The "decrypted" flag being true assumes that set_memory_decrypted() succeeds.
@@ -575,7 +578,7 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
  * @channel: a channel
  * @kbuffer: from kmalloc or vmalloc
  * @size: page-size multiple
- * @gpadl_handle: some funky thing
+ * @gpadl: output gpadl
  */
 int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
 			  u32 size, struct vmbus_gpadl *gpadl)
@@ -585,6 +588,26 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
 }
 EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
 
+/*
+ * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer
+ * that has already been decrypted by the caller.
+ *
+ * @channel: a channel
+ * @kbuffer: from kmalloc or vmalloc; must already be decrypted by the caller
+ * @size: page-size multiple
+ * @gpadl: output gpadl
+ *
+ * The caller is responsible for re-encrypting the buffer before freeing it.
+ */
+int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+					   void *kbuffer, u32 size,
+					   struct vmbus_gpadl *gpadl)
+{
+	return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER_DECRYPTED,
+				       kbuffer, size, 0U, gpadl);
+}
+EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
+
 /**
  * request_arr_init - Allocates memory for the requestor array. Each slot
  * keeps track of the next available slot in the array. Initially, each
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 964f1be8150c5..1146addbb42c4 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -70,7 +70,8 @@
  */
 enum hv_gpadl_type {
 	HV_GPADL_BUFFER,
-	HV_GPADL_RING
+	HV_GPADL_RING,
+	HV_GPADL_BUFFER_DECRYPTED
 };
 
 /* Single-page buffer */
@@ -1205,6 +1206,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel,
 				      u32 size,
 				      struct vmbus_gpadl *gpadl);
 
+extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+						  void *kbuffer,
+						  u32 size,
+						  struct vmbus_gpadl *gpadl);
+
 extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
 				     struct vmbus_gpadl *gpadl);
 
-- 
2.45.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs
  2026-08-06 21:33 [PATCH v3 0/3] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses Kameron Carr
  2026-08-06 21:33 ` [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
@ 2026-08-06 21:33 ` Kameron Carr
  2026-08-07 15:11   ` Michael Kelley
  2026-08-06 21:33 ` [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer() Kameron Carr
  2 siblings, 1 reply; 7+ messages in thread
From: Kameron Carr @ 2026-08-06 21:33 UTC (permalink / raw)
  To: decui, haiyangz, kys, longli, wei.liu, mhklinux
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
	linux-kernel, netdev

On CoCo VMs without confidential VMBus, the netvsc send and receive buffers
must be made host-visible by decrypting them. These buffers are vmalloc'ed,
but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory.
This use case is (so far) unique to netvsc, so solve it locally rather than
changing the set_memory() or allocation APIs.

Add vmbus_alloc_buffer()/vmbus_free_buffer() to the VMBus core. When the
guest's isolation model requires it, allocate the buffer as a list of
physically-contiguous chunks via alloc_pages_node(), starting at
MAX_PAGE_ORDER and falling back to smaller orders so the allocation still
succeeds under memory fragmentation. Each chunk is decrypted in place via
set_memory_decrypted() on its direct-map address, and the chunks are then
stitched into a single virtually-contiguous range with vmap(). Buffers that
do not need decryption keep using vzalloc().

To free the buffer, vmbus_free_buffer() calls vunmap() on the range then
re-encrypts and frees each chunk individually; any chunk that fails
re-encryption is leaked to prevent accidentally freeing decrypted memory.

This approach minimizes scattering of decrypted 4 KiB pages through the
kernel direct map and the resulting shattering of large page mappings.

Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
 drivers/hv/channel.c   | 155 +++++++++++++++++++++++++++++++++++++++++
 include/linux/hyperv.h |   7 ++
 2 files changed, 162 insertions(+)

diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 4782f5070bba9..f4370617deacb 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -13,11 +13,13 @@
 #include <linux/wait.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
+#include <linux/log2.h>
 #include <linux/module.h>
 #include <linux/hyperv.h>
 #include <linux/uio.h>
 #include <linux/interrupt.h>
 #include <linux/set_memory.h>
+#include <linux/vmalloc.h>
 #include <linux/export.h>
 #include <asm/page.h>
 #include <asm/mshyperv.h>
@@ -608,6 +610,159 @@ int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
 }
 EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
 
+/**
+ * vmbus_free_buffer - release a buffer allocated by vmbus_alloc_buffer().
+ *
+ * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a
+ *        failed allocation)
+ * @chunks: chunks array from vmbus_alloc_buffer(), or NULL
+ * @chunk_cnt: number of entries in @chunks
+ *
+ * When @chunks is NULL the buffer is a plain vzalloc() allocation.
+ *
+ * Otherwise tear down the vmap, and for each chunk re-encrypt and free
+ * the underlying pages. Any chunk that cannot be re-encrypted is leaked.
+ */
+void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt)
+{
+	u32 i;
+
+	if (!chunks) {
+		vfree(addr);
+		return;
+	}
+
+	vunmap(addr);
+
+	for (i = 0; i < chunk_cnt; i++) {
+		unsigned long vaddr =
+			(unsigned long)page_address(chunks[i]);
+		unsigned int order = folio_order(page_folio(chunks[i]));
+
+		if (set_memory_encrypted(vaddr, 1U << order))
+			continue;
+		__free_pages(chunks[i], order);
+	}
+
+	kvfree(chunks);
+}
+EXPORT_SYMBOL_GPL(vmbus_free_buffer);
+
+/**
+ * vmbus_alloc_buffer - allocate a host-visible, virtually-contiguous buffer.
+ *
+ * @channel: the channel the buffer will be attached to
+ * @size: requested buffer size in bytes (will be rounded up to PAGE_SIZE)
+ * @chunks_out: on success, set to the array of underlying chunks, or NULL when
+ *              the buffer was allocated with vzalloc()
+ * @chunk_cnt_out: on success, set to the number of chunks
+ *
+ * Buffers not requiring decryption are allocated with vzalloc().
+ *
+ * Buffers requiring decryption are allocated as a series of
+ * physically-contiguous chunks, starting at MAX_PAGE_ORDER and falling back to
+ * smaller orders on allocation failure. Each chunk is transitioned to
+ * host-visible via set_memory_decrypted() on its direct-map address, then all
+ * chunks are combined into a virtually-contiguous range via vmap().
+ *
+ * Return: the buffer's virtual address, or NULL on failure.
+ */
+void *vmbus_alloc_buffer(struct vmbus_channel *channel,
+			 u32 size,
+			 struct page ***chunks_out,
+			 u32 *chunk_cnt_out)
+{
+	unsigned long nr_pages = PFN_UP(size);
+	unsigned long remaining = nr_pages;
+	unsigned long page_idx = 0;
+	struct page **chunks = NULL;
+	struct page **pages = NULL;
+	int order = MAX_PAGE_ORDER;
+	u32 chunk_cnt = 0;
+	void *addr;
+	u32 i;
+	int ret;
+
+	*chunks_out = NULL;
+	*chunk_cnt_out = 0;
+
+	if (!nr_pages)
+		return NULL;
+
+	/* If the buffer does not need to be decrypted, just use vzalloc() */
+	if (!hv_is_isolation_supported() || channel->co_external_memory)
+		return vzalloc(nr_pages << PAGE_SHIFT);
+
+	/* Worst case: every chunk is a single page. */
+	chunks = kvmalloc_array(nr_pages, sizeof(*chunks),
+				GFP_KERNEL | __GFP_ZERO);
+	if (!chunks)
+		goto err;
+
+	pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
+	if (!pages)
+		goto err;
+
+	while (remaining) {
+		struct page *page;
+		gfp_t gfp;
+
+		order = min(order, ilog2(remaining));
+
+		/*
+		 * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing,
+		 * but try harder at order 0 since that is the final
+		 * fallback.
+		 * __GFP_COMP stores order information in the page folio.
+		 */
+		gfp = GFP_KERNEL | __GFP_ZERO;
+		if (order)
+			gfp |= __GFP_COMP | __GFP_NORETRY | __GFP_NOWARN;
+
+		page = alloc_pages_node(cpu_to_node(channel->target_cpu),
+					gfp, order);
+		if (!page) {
+			if (!order--)
+				goto err;
+			continue;
+		}
+
+		ret = set_memory_decrypted((unsigned long)page_address(page),
+					   1U << order);
+		if (ret) {
+			/*
+			 * set_memory_decrypted() failed; the page state is
+			 * unknown so it must be leaked rather than freed.
+			 */
+			goto err;
+		}
+
+		chunks[chunk_cnt++] = page;
+
+		for (i = 0; i < (1U << order); i++)
+			pages[page_idx++] = page + i;
+
+		remaining -= 1U << order;
+	}
+
+	addr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL));
+	if (!addr)
+		goto err;
+
+	memset(addr, 0, nr_pages << PAGE_SHIFT);
+
+	kvfree(pages);
+	*chunks_out = chunks;
+	*chunk_cnt_out = chunk_cnt;
+	return addr;
+
+err:
+	kvfree(pages);
+	vmbus_free_buffer(NULL, chunks, chunk_cnt);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(vmbus_alloc_buffer);
+
 /**
  * request_arr_init - Allocates memory for the requestor array. Each slot
  * keeps track of the next available slot in the array. Initially, each
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 1146addbb42c4..f843ee0efa22f 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -1214,6 +1214,13 @@ extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
 extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
 				     struct vmbus_gpadl *gpadl);
 
+extern void *vmbus_alloc_buffer(struct vmbus_channel *channel,
+				u32 size,
+				struct page ***chunks_out,
+				u32 *chunk_cnt_out);
+
+extern void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt);
+
 void vmbus_reset_channel_cb(struct vmbus_channel *channel);
 
 extern int vmbus_recvpacket(struct vmbus_channel *channel,
-- 
2.45.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer()
  2026-08-06 21:33 [PATCH v3 0/3] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses Kameron Carr
  2026-08-06 21:33 ` [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
  2026-08-06 21:33 ` [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs Kameron Carr
@ 2026-08-06 21:33 ` Kameron Carr
  2026-08-07 15:12   ` Michael Kelley
  2 siblings, 1 reply; 7+ messages in thread
From: Kameron Carr @ 2026-08-06 21:33 UTC (permalink / raw)
  To: decui, haiyangz, kys, longli, wei.liu, mhklinux
  Cc: andrew+netdev, davem, edumazet, kuba, pabeni, linux-hyperv,
	linux-kernel, netdev

On CoCo VMs without confidential VMBus, the netvsc send and receive buffers
must be made host-visible by decrypting them. These buffers are vmalloc'ed,
but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory.
This use case is (so far) unique to netvsc, so solve it locally rather than
changing the set_memory() or allocation APIs.

Use vmbus_alloc_buffer() to allocate the send and receive buffers, which
will make them host-visible. Store the list of memory chunks in the
netvsc_device struct so they can be individually freed later. Use
vmbus_establish_gpadl_caller_decrypted() so there is no attempt to decrypt
the virtual address.

Appropriately free the buffers with vmbus_free_buffer(). Because vunmap()
and set_memory_encrypted() must run in process context, replace the
rcu_head/call_rcu() pair used to defer free_netvsc_device() with
rcu_work/queue_rcu_work(). This also fixes a small race condition where the
buffers may be accessed while being re-encrypted by moving the
re-encryption after the RCU grace period.

Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>
---
 drivers/net/hyperv/hyperv_net.h |   8 ++-
 drivers/net/hyperv/netvsc.c     | 103 ++++++++++++++++++++++----------
 drivers/net/hyperv/netvsc_drv.c |   6 ++
 3 files changed, 83 insertions(+), 34 deletions(-)

diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
index 7397c693f984a..4841367fdab2f 100644
--- a/drivers/net/hyperv/hyperv_net.h
+++ b/drivers/net/hyperv/hyperv_net.h
@@ -220,6 +220,8 @@ struct net_device_context;
 
 extern u32 netvsc_ring_bytes;
 
+int netvsc_workqueue_init(void);
+void netvsc_workqueue_destroy(void);
 struct netvsc_device *netvsc_device_add(struct hv_device *device,
 					const struct netvsc_device_info *info);
 int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);
@@ -1158,6 +1160,8 @@ struct netvsc_device {
 	/* Receive buffer allocated by us but manages by NetVSP */
 	void *recv_buf;
 	u32 recv_buf_size; /* allocated bytes */
+	struct page **recv_buf_chunks;
+	u32 recv_buf_chunk_cnt;
 	struct vmbus_gpadl recv_buf_gpadl_handle;
 	u32 recv_section_cnt;
 	u32 recv_section_size;
@@ -1166,6 +1170,8 @@ struct netvsc_device {
 	/* Send buffer allocated by us */
 	void *send_buf;
 	u32 send_buf_size;
+	struct page **send_buf_chunks;
+	u32 send_buf_chunk_cnt;
 	struct vmbus_gpadl send_buf_gpadl_handle;
 	u32 send_section_cnt;
 	u32 send_section_size;
@@ -1193,7 +1199,7 @@ struct netvsc_device {
 
 	struct netvsc_channel chan_table[VRSS_CHANNEL_MAX];
 
-	struct rcu_head rcu;
+	struct rcu_work rwork;
 };
 
 /* NdisInitialize message */
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c
index 59e95341f9b1e..c59f2a44badf0 100644
--- a/drivers/net/hyperv/netvsc.c
+++ b/drivers/net/hyperv/netvsc.c
@@ -28,6 +28,8 @@
 #include "hyperv_net.h"
 #include "netvsc_trace.h"
 
+static struct workqueue_struct *netvsc_wq;
+
 /*
  * Switch the data path from the synthetic interface to the VF
  * interface.
@@ -125,6 +127,47 @@ static void netvsc_subchan_work(struct work_struct *w)
 	rtnl_unlock();
 }
 
+static void __free_netvsc_device(struct netvsc_device *nvdev)
+{
+	int i;
+
+	kfree(nvdev->extension);
+
+	vmbus_free_buffer(nvdev->recv_buf, nvdev->recv_buf_chunks,
+			  nvdev->recv_buf_chunk_cnt);
+	vmbus_free_buffer(nvdev->send_buf, nvdev->send_buf_chunks,
+			  nvdev->send_buf_chunk_cnt);
+	bitmap_free(nvdev->send_section_map);
+
+	for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
+		xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
+		kfree(nvdev->chan_table[i].recv_buf);
+		vfree(nvdev->chan_table[i].mrc.slots);
+	}
+
+	kfree(nvdev);
+}
+
+static void free_netvsc_device(struct work_struct *w)
+{
+	struct rcu_work *rwork = to_rcu_work(w);
+
+	__free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));
+}
+
+int netvsc_workqueue_init(void)
+{
+	netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0);
+
+	return netvsc_wq ? 0 : -ENOMEM;
+}
+
+void netvsc_workqueue_destroy(void)
+{
+	rcu_barrier();
+	destroy_workqueue(netvsc_wq);
+}
+
 static struct netvsc_device *alloc_net_device(void)
 {
 	struct netvsc_device *net_device;
@@ -143,36 +186,18 @@ static struct netvsc_device *alloc_net_device(void)
 	init_completion(&net_device->channel_init_wait);
 	init_waitqueue_head(&net_device->subchan_open);
 	INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
+	INIT_RCU_WORK(&net_device->rwork, free_netvsc_device);
 
 	return net_device;
 }
 
-static void free_netvsc_device(struct rcu_head *head)
-{
-	struct netvsc_device *nvdev
-		= container_of(head, struct netvsc_device, rcu);
-	int i;
-
-	kfree(nvdev->extension);
-
-	if (!nvdev->recv_buf_gpadl_handle.decrypted)
-		vfree(nvdev->recv_buf);
-	if (!nvdev->send_buf_gpadl_handle.decrypted)
-		vfree(nvdev->send_buf);
-	bitmap_free(nvdev->send_section_map);
-
-	for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
-		xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
-		kfree(nvdev->chan_table[i].recv_buf);
-		vfree(nvdev->chan_table[i].mrc.slots);
-	}
-
-	kfree(nvdev);
-}
-
 static void free_netvsc_device_rcu(struct netvsc_device *nvdev)
 {
-	call_rcu(&nvdev->rcu, free_netvsc_device);
+	/*
+	 * Defer the actual free to process context: vunmap() and
+	 * set_memory_encrypted() cannot run from RCU softirq context.
+	 */
+	queue_rcu_work(netvsc_wq, &nvdev->rwork);
 }
 
 static void netvsc_revoke_recv_buf(struct hv_device *device,
@@ -351,7 +376,10 @@ static int netvsc_init_buf(struct hv_device *device,
 		buf_size = min_t(unsigned int, buf_size,
 				 NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);
 
-	net_device->recv_buf = vzalloc(buf_size);
+	net_device->recv_buf =
+		vmbus_alloc_buffer(device->channel, buf_size,
+				   &net_device->recv_buf_chunks,
+				   &net_device->recv_buf_chunk_cnt);
 	if (!net_device->recv_buf) {
 		netdev_err(ndev,
 			   "unable to allocate receive buffer of size %u\n",
@@ -367,9 +395,10 @@ static int netvsc_init_buf(struct hv_device *device,
 	 * channel.  Note: This call uses the vmbus connection rather
 	 * than the channel to establish the gpadl handle.
 	 */
-	ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf,
-				    buf_size,
-				    &net_device->recv_buf_gpadl_handle);
+	ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+						     net_device->recv_buf,
+						     buf_size,
+						     &net_device->recv_buf_gpadl_handle);
 	if (ret != 0) {
 		netdev_err(ndev,
 			"unable to establish receive buffer's gpadl\n");
@@ -457,7 +486,10 @@ static int netvsc_init_buf(struct hv_device *device,
 	buf_size = device_info->send_sections * device_info->send_section_size;
 	buf_size = round_up(buf_size, PAGE_SIZE);
 
-	net_device->send_buf = vzalloc(buf_size);
+	net_device->send_buf =
+		vmbus_alloc_buffer(device->channel, buf_size,
+				   &net_device->send_buf_chunks,
+				   &net_device->send_buf_chunk_cnt);
 	if (!net_device->send_buf) {
 		netdev_err(ndev, "unable to allocate send buffer of size %u\n",
 			   buf_size);
@@ -470,9 +502,10 @@ static int netvsc_init_buf(struct hv_device *device,
 	 * channel.  Note: This call uses the vmbus connection rather
 	 * than the channel to establish the gpadl handle.
 	 */
-	ret = vmbus_establish_gpadl(device->channel, net_device->send_buf,
-				    buf_size,
-				    &net_device->send_buf_gpadl_handle);
+	ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+						     net_device->send_buf,
+						     buf_size,
+						     &net_device->send_buf_gpadl_handle);
 	if (ret != 0) {
 		netdev_err(ndev,
 			   "unable to establish send buffer's gpadl\n");
@@ -1863,7 +1896,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,
 	netif_napi_del(&net_device->chan_table[0].napi);
 
 cleanup2:
-	free_netvsc_device(&net_device->rcu);
+	/*
+	 * net_device was never published, so we don't need to wait for an
+	 * RCU grace period -- call the free routine synchronously.
+	 */
+	__free_netvsc_device(net_device);
 
 	return ERR_PTR(ret);
 }
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index ee5ab5ceb2be2..1d43c73fd73f1 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)
 {
 	unregister_netdevice_notifier(&netvsc_netdev_notifier);
 	vmbus_driver_unregister(&netvsc_drv);
+	netvsc_workqueue_destroy();
 }
 
 static int __init netvsc_drv_init(void)
 {
 	int ret;
 
+	ret = netvsc_workqueue_init();
+	if (ret)
+		return ret;
+
 	if (ring_size < RING_SIZE_MIN) {
 		ring_size = RING_SIZE_MIN;
 		pr_info("Increased ring_size to %u (min allowed)\n",
@@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)
 
 err_vmbus_reg:
 	unregister_netdevice_notifier(&netvsc_netdev_notifier);
+	netvsc_workqueue_destroy();
 	return ret;
 }
 
-- 
2.45.4


^ permalink raw reply related	[flat|nested] 7+ messages in thread

* RE: [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted()
  2026-08-06 21:33 ` [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
@ 2026-08-07 15:11   ` Michael Kelley
  0 siblings, 0 replies; 7+ messages in thread
From: Michael Kelley @ 2026-08-07 15:11 UTC (permalink / raw)
  To: Kameron Carr, decui@microsoft.com, haiyangz@microsoft.com,
	kys@microsoft.com, longli@microsoft.com, wei.liu@kernel.org,
	Michael Kelley
  Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux-hyperv@vger.kernel.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org

From: Kameron Carr <kameroncarr@linux.microsoft.com> Sent: Thursday, August 6, 2026 2:33 PM
> 
> Add a new vmbus_establish_gpadl_caller_decrypted() for callers that want to
> decrypt their own buffers. Add a new hv_gpadl_type,
> HV_GPADL_BUFFER_DECRYPTED, to communicate the decryption status of the
> buffer.
> 
> No functional change for existing callers.
> 
> Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>

Reviewed-by: Michael Kelley <mhklinux@outlook.com>

> ---
>  drivers/hv/channel.c   | 27 +++++++++++++++++++++++++--
>  include/linux/hyperv.h |  8 +++++++-
>  2 files changed, 32 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
> index 6821f225248b1..4782f5070bba9 100644
> --- a/drivers/hv/channel.c
> +++ b/drivers/hv/channel.c
> @@ -40,6 +40,7 @@ static inline u32 hv_gpadl_size(enum hv_gpadl_type type, u32
> size)
>  {
>  	switch (type) {
>  	case HV_GPADL_BUFFER:
> +	case HV_GPADL_BUFFER_DECRYPTED:
>  		return size;
>  	case HV_GPADL_RING:
>  		/* The size of a ringbuffer must be page-aligned */
> @@ -100,6 +101,7 @@ static inline u64 hv_gpadl_hvpfn(enum hv_gpadl_type type,
> void *kbuffer,
> 
>  	switch (type) {
>  	case HV_GPADL_BUFFER:
> +	case HV_GPADL_BUFFER_DECRYPTED:
>  		break;
>  	case HV_GPADL_RING:
>  		if (i == 0)
> @@ -460,7 +462,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel
> *channel,
>  	}
> 
>  	gpadl->decrypted = !((channel->co_external_memory && type ==
> HV_GPADL_BUFFER) ||
> -		(channel->co_ring_buffer && type == HV_GPADL_RING));
> +		(channel->co_ring_buffer && type == HV_GPADL_RING) ||
> +		(type == HV_GPADL_BUFFER_DECRYPTED));
>  	if (gpadl->decrypted) {
>  		/*
>  		 * The "decrypted" flag being true assumes that
> set_memory_decrypted() succeeds.
> @@ -575,7 +578,7 @@ static int __vmbus_establish_gpadl(struct vmbus_channel
> *channel,
>   * @channel: a channel
>   * @kbuffer: from kmalloc or vmalloc
>   * @size: page-size multiple
> - * @gpadl_handle: some funky thing
> + * @gpadl: output gpadl
>   */
>  int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
>  			  u32 size, struct vmbus_gpadl *gpadl)
> @@ -585,6 +588,26 @@ int vmbus_establish_gpadl(struct vmbus_channel *channel,
> void *kbuffer,
>  }
>  EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
> 
> +/*
> + * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer
> + * that has already been decrypted by the caller.
> + *
> + * @channel: a channel
> + * @kbuffer: from kmalloc or vmalloc; must already be decrypted by the caller
> + * @size: page-size multiple
> + * @gpadl: output gpadl
> + *
> + * The caller is responsible for re-encrypting the buffer before freeing it.
> + */
> +int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
> +					   void *kbuffer, u32 size,
> +					   struct vmbus_gpadl *gpadl)
> +{
> +	return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER_DECRYPTED,
> +				       kbuffer, size, 0U, gpadl);
> +}
> +EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
> +
>  /**
>   * request_arr_init - Allocates memory for the requestor array. Each slot
>   * keeps track of the next available slot in the array. Initially, each
> diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
> index 964f1be8150c5..1146addbb42c4 100644
> --- a/include/linux/hyperv.h
> +++ b/include/linux/hyperv.h
> @@ -70,7 +70,8 @@
>   */
>  enum hv_gpadl_type {
>  	HV_GPADL_BUFFER,
> -	HV_GPADL_RING
> +	HV_GPADL_RING,
> +	HV_GPADL_BUFFER_DECRYPTED
>  };
> 
>  /* Single-page buffer */
> @@ -1205,6 +1206,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel
> *channel,
>  				      u32 size,
>  				      struct vmbus_gpadl *gpadl);
> 
> +extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
> +						  void *kbuffer,
> +						  u32 size,
> +						  struct vmbus_gpadl *gpadl);
> +
>  extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
>  				     struct vmbus_gpadl *gpadl);
> 
> --
> 2.45.4
> 


^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs
  2026-08-06 21:33 ` [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs Kameron Carr
@ 2026-08-07 15:11   ` Michael Kelley
  0 siblings, 0 replies; 7+ messages in thread
From: Michael Kelley @ 2026-08-07 15:11 UTC (permalink / raw)
  To: Kameron Carr, decui@microsoft.com, haiyangz@microsoft.com,
	kys@microsoft.com, longli@microsoft.com, wei.liu@kernel.org,
	Michael Kelley
  Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux-hyperv@vger.kernel.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org

From: Kameron Carr <kameroncarr@linux.microsoft.com>

Nit:  Subject: prefix should be "Drivers: hv: vmbus:" for historical consistency.

If there are no other changes, Wei Liu can probably fix this when taking the
patch.

> 
> On CoCo VMs without confidential VMBus, the netvsc send and receive buffers
> must be made host-visible by decrypting them. These buffers are vmalloc'ed,
> but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory.
> This use case is (so far) unique to netvsc, so solve it locally rather than
> changing the set_memory() or allocation APIs.
> 
> Add vmbus_alloc_buffer()/vmbus_free_buffer() to the VMBus core. When the
> guest's isolation model requires it, allocate the buffer as a list of
> physically-contiguous chunks via alloc_pages_node(), starting at
> MAX_PAGE_ORDER and falling back to smaller orders so the allocation still
> succeeds under memory fragmentation. Each chunk is decrypted in place via
> set_memory_decrypted() on its direct-map address, and the chunks are then
> stitched into a single virtually-contiguous range with vmap(). Buffers that
> do not need decryption keep using vzalloc().
> 
> To free the buffer, vmbus_free_buffer() calls vunmap() on the range then
> re-encrypts and frees each chunk individually; any chunk that fails
> re-encryption is leaked to prevent accidentally freeing decrypted memory.
> 
> This approach minimizes scattering of decrypted 4 KiB pages through the
> kernel direct map and the resulting shattering of large page mappings.
> 
> Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>

Modulo the nit, looks good to me.

Reviewed-by: Michael Kelley <mhklinux@outlook.com>

> ---
>  drivers/hv/channel.c   | 155 +++++++++++++++++++++++++++++++++++++++++
>  include/linux/hyperv.h |   7 ++
>  2 files changed, 162 insertions(+)
> 
> diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
> index 4782f5070bba9..f4370617deacb 100644
> --- a/drivers/hv/channel.c
> +++ b/drivers/hv/channel.c
> @@ -13,11 +13,13 @@
>  #include <linux/wait.h>
>  #include <linux/mm.h>
>  #include <linux/slab.h>
> +#include <linux/log2.h>
>  #include <linux/module.h>
>  #include <linux/hyperv.h>
>  #include <linux/uio.h>
>  #include <linux/interrupt.h>
>  #include <linux/set_memory.h>
> +#include <linux/vmalloc.h>
>  #include <linux/export.h>
>  #include <asm/page.h>
>  #include <asm/mshyperv.h>
> @@ -608,6 +610,159 @@ int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
>  }
>  EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
> 
> +/**
> + * vmbus_free_buffer - release a buffer allocated by vmbus_alloc_buffer().
> + *
> + * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a
> + *        failed allocation)
> + * @chunks: chunks array from vmbus_alloc_buffer(), or NULL
> + * @chunk_cnt: number of entries in @chunks
> + *
> + * When @chunks is NULL the buffer is a plain vzalloc() allocation.
> + *
> + * Otherwise tear down the vmap, and for each chunk re-encrypt and free
> + * the underlying pages. Any chunk that cannot be re-encrypted is leaked.
> + */
> +void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt)
> +{
> +	u32 i;
> +
> +	if (!chunks) {
> +		vfree(addr);
> +		return;
> +	}
> +
> +	vunmap(addr);
> +
> +	for (i = 0; i < chunk_cnt; i++) {
> +		unsigned long vaddr =
> +			(unsigned long)page_address(chunks[i]);
> +		unsigned int order = folio_order(page_folio(chunks[i]));
> +
> +		if (set_memory_encrypted(vaddr, 1U << order))
> +			continue;
> +		__free_pages(chunks[i], order);
> +	}
> +
> +	kvfree(chunks);
> +}
> +EXPORT_SYMBOL_GPL(vmbus_free_buffer);
> +
> +/**
> + * vmbus_alloc_buffer - allocate a host-visible, virtually-contiguous buffer.
> + *
> + * @channel: the channel the buffer will be attached to
> + * @size: requested buffer size in bytes (will be rounded up to PAGE_SIZE)
> + * @chunks_out: on success, set to the array of underlying chunks, or NULL when
> + *              the buffer was allocated with vzalloc()
> + * @chunk_cnt_out: on success, set to the number of chunks
> + *
> + * Buffers not requiring decryption are allocated with vzalloc().
> + *
> + * Buffers requiring decryption are allocated as a series of
> + * physically-contiguous chunks, starting at MAX_PAGE_ORDER and falling back to
> + * smaller orders on allocation failure. Each chunk is transitioned to
> + * host-visible via set_memory_decrypted() on its direct-map address, then all
> + * chunks are combined into a virtually-contiguous range via vmap().
> + *
> + * Return: the buffer's virtual address, or NULL on failure.
> + */
> +void *vmbus_alloc_buffer(struct vmbus_channel *channel,
> +			 u32 size,
> +			 struct page ***chunks_out,
> +			 u32 *chunk_cnt_out)
> +{
> +	unsigned long nr_pages = PFN_UP(size);
> +	unsigned long remaining = nr_pages;
> +	unsigned long page_idx = 0;
> +	struct page **chunks = NULL;
> +	struct page **pages = NULL;
> +	int order = MAX_PAGE_ORDER;
> +	u32 chunk_cnt = 0;
> +	void *addr;
> +	u32 i;
> +	int ret;
> +
> +	*chunks_out = NULL;
> +	*chunk_cnt_out = 0;
> +
> +	if (!nr_pages)
> +		return NULL;
> +
> +	/* If the buffer does not need to be decrypted, just use vzalloc() */
> +	if (!hv_is_isolation_supported() || channel->co_external_memory)
> +		return vzalloc(nr_pages << PAGE_SHIFT);
> +
> +	/* Worst case: every chunk is a single page. */
> +	chunks = kvmalloc_array(nr_pages, sizeof(*chunks),
> +				GFP_KERNEL | __GFP_ZERO);
> +	if (!chunks)
> +		goto err;
> +
> +	pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
> +	if (!pages)
> +		goto err;
> +
> +	while (remaining) {
> +		struct page *page;
> +		gfp_t gfp;
> +
> +		order = min(order, ilog2(remaining));
> +
> +		/*
> +		 * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing,
> +		 * but try harder at order 0 since that is the final
> +		 * fallback.
> +		 * __GFP_COMP stores order information in the page folio.
> +		 */
> +		gfp = GFP_KERNEL | __GFP_ZERO;
> +		if (order)
> +			gfp |= __GFP_COMP | __GFP_NORETRY | __GFP_NOWARN;
> +
> +		page = alloc_pages_node(cpu_to_node(channel->target_cpu),
> +					gfp, order);
> +		if (!page) {
> +			if (!order--)
> +				goto err;
> +			continue;
> +		}
> +
> +		ret = set_memory_decrypted((unsigned long)page_address(page),
> +					   1U << order);
> +		if (ret) {
> +			/*
> +			 * set_memory_decrypted() failed; the page state is
> +			 * unknown so it must be leaked rather than freed.
> +			 */
> +			goto err;
> +		}
> +
> +		chunks[chunk_cnt++] = page;
> +
> +		for (i = 0; i < (1U << order); i++)
> +			pages[page_idx++] = page + i;
> +
> +		remaining -= 1U << order;
> +	}
> +
> +	addr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL));
> +	if (!addr)
> +		goto err;
> +
> +	memset(addr, 0, nr_pages << PAGE_SHIFT);
> +
> +	kvfree(pages);
> +	*chunks_out = chunks;
> +	*chunk_cnt_out = chunk_cnt;
> +	return addr;
> +
> +err:
> +	kvfree(pages);
> +	vmbus_free_buffer(NULL, chunks, chunk_cnt);
> +	return NULL;
> +}
> +EXPORT_SYMBOL_GPL(vmbus_alloc_buffer);
> +
>  /**
>   * request_arr_init - Allocates memory for the requestor array. Each slot
>   * keeps track of the next available slot in the array. Initially, each
> diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
> index 1146addbb42c4..f843ee0efa22f 100644
> --- a/include/linux/hyperv.h
> +++ b/include/linux/hyperv.h
> @@ -1214,6 +1214,13 @@ extern int vmbus_establish_gpadl_caller_decrypted(struct
> vmbus_channel *channel,
>  extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
>  				     struct vmbus_gpadl *gpadl);
> 
> +extern void *vmbus_alloc_buffer(struct vmbus_channel *channel,
> +				u32 size,
> +				struct page ***chunks_out,
> +				u32 *chunk_cnt_out);
> +
> +extern void vmbus_free_buffer(void *addr, struct page **chunks, u32 chunk_cnt);
> +
>  void vmbus_reset_channel_cb(struct vmbus_channel *channel);
> 
>  extern int vmbus_recvpacket(struct vmbus_channel *channel,
> --
> 2.45.4
> 


^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer()
  2026-08-06 21:33 ` [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer() Kameron Carr
@ 2026-08-07 15:12   ` Michael Kelley
  0 siblings, 0 replies; 7+ messages in thread
From: Michael Kelley @ 2026-08-07 15:12 UTC (permalink / raw)
  To: Kameron Carr, decui@microsoft.com, haiyangz@microsoft.com,
	kys@microsoft.com, longli@microsoft.com, wei.liu@kernel.org,
	Michael Kelley
  Cc: andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux-hyperv@vger.kernel.org,
	linux-kernel@vger.kernel.org, netdev@vger.kernel.org

From: Kameron Carr <kameroncarr@linux.microsoft.com> Sent: Thursday, August 6, 2026 2:33 PM
> 

Nit on the patch Subject: I'd quibble a bit about calling these "GPADL
buffers". The netvsc driver calls them send/receive buffers. A GPADL is the
mechanism by which the Hyper-V host is told where they live in the guest
physical address space. I think a more precise Subject would be:

hv_netvsc: Allocate send/receive buffers using vmbus_alloc_buffer()

Your commit description describes them correctly.

> On CoCo VMs without confidential VMBus, the netvsc send and receive buffers
> must be made host-visible by decrypting them. These buffers are vmalloc'ed,
> but set_memory_decrypted()/encrypted() do not work on vmalloc'ed memory.
> This use case is (so far) unique to netvsc, so solve it locally rather than
> changing the set_memory() or allocation APIs.
> 
> Use vmbus_alloc_buffer() to allocate the send and receive buffers, which
> will make them host-visible. Store the list of memory chunks in the
> netvsc_device struct so they can be individually freed later. Use
> vmbus_establish_gpadl_caller_decrypted() so there is no attempt to decrypt
> the virtual address.
> 
> Appropriately free the buffers with vmbus_free_buffer(). Because vunmap()
> and set_memory_encrypted() must run in process context, replace the
> rcu_head/call_rcu() pair used to defer free_netvsc_device() with
> rcu_work/queue_rcu_work(). This also fixes a small race condition where the
> buffers may be accessed while being re-encrypted by moving the
> re-encryption after the RCU grace period.
> 
> Signed-off-by: Kameron Carr <kameroncarr@linux.microsoft.com>

Modulo the nit, looks good me to.

Reviewed-by: Michael Kelley <mhklinux@outlook.com>

> ---
>  drivers/net/hyperv/hyperv_net.h |   8 ++-
>  drivers/net/hyperv/netvsc.c     | 103 ++++++++++++++++++++++----------
>  drivers/net/hyperv/netvsc_drv.c |   6 ++
>  3 files changed, 83 insertions(+), 34 deletions(-)
> 
> diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
> index 7397c693f984a..4841367fdab2f 100644
> --- a/drivers/net/hyperv/hyperv_net.h
> +++ b/drivers/net/hyperv/hyperv_net.h
> @@ -220,6 +220,8 @@ struct net_device_context;
> 
>  extern u32 netvsc_ring_bytes;
> 
> +int netvsc_workqueue_init(void);
> +void netvsc_workqueue_destroy(void);
>  struct netvsc_device *netvsc_device_add(struct hv_device *device,
>  					const struct netvsc_device_info *info);
>  int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);
> @@ -1158,6 +1160,8 @@ struct netvsc_device {
>  	/* Receive buffer allocated by us but manages by NetVSP */
>  	void *recv_buf;
>  	u32 recv_buf_size; /* allocated bytes */
> +	struct page **recv_buf_chunks;
> +	u32 recv_buf_chunk_cnt;
>  	struct vmbus_gpadl recv_buf_gpadl_handle;
>  	u32 recv_section_cnt;
>  	u32 recv_section_size;
> @@ -1166,6 +1170,8 @@ struct netvsc_device {
>  	/* Send buffer allocated by us */
>  	void *send_buf;
>  	u32 send_buf_size;
> +	struct page **send_buf_chunks;
> +	u32 send_buf_chunk_cnt;
>  	struct vmbus_gpadl send_buf_gpadl_handle;
>  	u32 send_section_cnt;
>  	u32 send_section_size;
> @@ -1193,7 +1199,7 @@ struct netvsc_device {
> 
>  	struct netvsc_channel chan_table[VRSS_CHANNEL_MAX];
> 
> -	struct rcu_head rcu;
> +	struct rcu_work rwork;
>  };
> 
>  /* NdisInitialize message */
> diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c
> index 59e95341f9b1e..c59f2a44badf0 100644
> --- a/drivers/net/hyperv/netvsc.c
> +++ b/drivers/net/hyperv/netvsc.c
> @@ -28,6 +28,8 @@
>  #include "hyperv_net.h"
>  #include "netvsc_trace.h"
> 
> +static struct workqueue_struct *netvsc_wq;
> +
>  /*
>   * Switch the data path from the synthetic interface to the VF
>   * interface.
> @@ -125,6 +127,47 @@ static void netvsc_subchan_work(struct work_struct *w)
>  	rtnl_unlock();
>  }
> 
> +static void __free_netvsc_device(struct netvsc_device *nvdev)
> +{
> +	int i;
> +
> +	kfree(nvdev->extension);
> +
> +	vmbus_free_buffer(nvdev->recv_buf, nvdev->recv_buf_chunks,
> +			  nvdev->recv_buf_chunk_cnt);
> +	vmbus_free_buffer(nvdev->send_buf, nvdev->send_buf_chunks,
> +			  nvdev->send_buf_chunk_cnt);
> +	bitmap_free(nvdev->send_section_map);
> +
> +	for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
> +		xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
> +		kfree(nvdev->chan_table[i].recv_buf);
> +		vfree(nvdev->chan_table[i].mrc.slots);
> +	}
> +
> +	kfree(nvdev);
> +}
> +
> +static void free_netvsc_device(struct work_struct *w)
> +{
> +	struct rcu_work *rwork = to_rcu_work(w);
> +
> +	__free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));
> +}
> +
> +int netvsc_workqueue_init(void)
> +{
> +	netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0);
> +
> +	return netvsc_wq ? 0 : -ENOMEM;
> +}
> +
> +void netvsc_workqueue_destroy(void)
> +{
> +	rcu_barrier();
> +	destroy_workqueue(netvsc_wq);
> +}
> +
>  static struct netvsc_device *alloc_net_device(void)
>  {
>  	struct netvsc_device *net_device;
> @@ -143,36 +186,18 @@ static struct netvsc_device *alloc_net_device(void)
>  	init_completion(&net_device->channel_init_wait);
>  	init_waitqueue_head(&net_device->subchan_open);
>  	INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
> +	INIT_RCU_WORK(&net_device->rwork, free_netvsc_device);
> 
>  	return net_device;
>  }
> 
> -static void free_netvsc_device(struct rcu_head *head)
> -{
> -	struct netvsc_device *nvdev
> -		= container_of(head, struct netvsc_device, rcu);
> -	int i;
> -
> -	kfree(nvdev->extension);
> -
> -	if (!nvdev->recv_buf_gpadl_handle.decrypted)
> -		vfree(nvdev->recv_buf);
> -	if (!nvdev->send_buf_gpadl_handle.decrypted)
> -		vfree(nvdev->send_buf);
> -	bitmap_free(nvdev->send_section_map);
> -
> -	for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
> -		xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
> -		kfree(nvdev->chan_table[i].recv_buf);
> -		vfree(nvdev->chan_table[i].mrc.slots);
> -	}
> -
> -	kfree(nvdev);
> -}
> -
>  static void free_netvsc_device_rcu(struct netvsc_device *nvdev)
>  {
> -	call_rcu(&nvdev->rcu, free_netvsc_device);
> +	/*
> +	 * Defer the actual free to process context: vunmap() and
> +	 * set_memory_encrypted() cannot run from RCU softirq context.
> +	 */
> +	queue_rcu_work(netvsc_wq, &nvdev->rwork);
>  }
> 
>  static void netvsc_revoke_recv_buf(struct hv_device *device,
> @@ -351,7 +376,10 @@ static int netvsc_init_buf(struct hv_device *device,
>  		buf_size = min_t(unsigned int, buf_size,
>  				 NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);
> 
> -	net_device->recv_buf = vzalloc(buf_size);
> +	net_device->recv_buf =
> +		vmbus_alloc_buffer(device->channel, buf_size,
> +				   &net_device->recv_buf_chunks,
> +				   &net_device->recv_buf_chunk_cnt);
>  	if (!net_device->recv_buf) {
>  		netdev_err(ndev,
>  			   "unable to allocate receive buffer of size %u\n",
> @@ -367,9 +395,10 @@ static int netvsc_init_buf(struct hv_device *device,
>  	 * channel.  Note: This call uses the vmbus connection rather
>  	 * than the channel to establish the gpadl handle.
>  	 */
> -	ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf,
> -				    buf_size,
> -				    &net_device->recv_buf_gpadl_handle);
> +	ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
> +						     net_device->recv_buf,
> +						     buf_size,
> +						     &net_device-
> >recv_buf_gpadl_handle);
>  	if (ret != 0) {
>  		netdev_err(ndev,
>  			"unable to establish receive buffer's gpadl\n");
> @@ -457,7 +486,10 @@ static int netvsc_init_buf(struct hv_device *device,
>  	buf_size = device_info->send_sections * device_info->send_section_size;
>  	buf_size = round_up(buf_size, PAGE_SIZE);
> 
> -	net_device->send_buf = vzalloc(buf_size);
> +	net_device->send_buf =
> +		vmbus_alloc_buffer(device->channel, buf_size,
> +				   &net_device->send_buf_chunks,
> +				   &net_device->send_buf_chunk_cnt);
>  	if (!net_device->send_buf) {
>  		netdev_err(ndev, "unable to allocate send buffer of size %u\n",
>  			   buf_size);
> @@ -470,9 +502,10 @@ static int netvsc_init_buf(struct hv_device *device,
>  	 * channel.  Note: This call uses the vmbus connection rather
>  	 * than the channel to establish the gpadl handle.
>  	 */
> -	ret = vmbus_establish_gpadl(device->channel, net_device->send_buf,
> -				    buf_size,
> -				    &net_device->send_buf_gpadl_handle);
> +	ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
> +						     net_device->send_buf,
> +						     buf_size,
> +						     &net_device-
> >send_buf_gpadl_handle);
>  	if (ret != 0) {
>  		netdev_err(ndev,
>  			   "unable to establish send buffer's gpadl\n");
> @@ -1863,7 +1896,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device
> *device,
>  	netif_napi_del(&net_device->chan_table[0].napi);
> 
>  cleanup2:
> -	free_netvsc_device(&net_device->rcu);
> +	/*
> +	 * net_device was never published, so we don't need to wait for an
> +	 * RCU grace period -- call the free routine synchronously.
> +	 */
> +	__free_netvsc_device(net_device);
> 
>  	return ERR_PTR(ret);
>  }
> diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
> index ee5ab5ceb2be2..1d43c73fd73f1 100644
> --- a/drivers/net/hyperv/netvsc_drv.c
> +++ b/drivers/net/hyperv/netvsc_drv.c
> @@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)
>  {
>  	unregister_netdevice_notifier(&netvsc_netdev_notifier);
>  	vmbus_driver_unregister(&netvsc_drv);
> +	netvsc_workqueue_destroy();
>  }
> 
>  static int __init netvsc_drv_init(void)
>  {
>  	int ret;
> 
> +	ret = netvsc_workqueue_init();
> +	if (ret)
> +		return ret;
> +
>  	if (ring_size < RING_SIZE_MIN) {
>  		ring_size = RING_SIZE_MIN;
>  		pr_info("Increased ring_size to %u (min allowed)\n",
> @@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)
> 
>  err_vmbus_reg:
>  	unregister_netdevice_notifier(&netvsc_netdev_notifier);
> +	netvsc_workqueue_destroy();
>  	return ret;
>  }
> 
> --
> 2.45.4
> 


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-08-07 15:12 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-06 21:33 [PATCH v3 0/3] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses Kameron Carr
2026-08-06 21:33 ` [PATCH v3 1/3] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
2026-08-07 15:11   ` Michael Kelley
2026-08-06 21:33 ` [PATCH v3 2/3] hv: vmbus: Add vmbus_alloc_buffer()/vmbus_free_buffer() for CoCo VMs Kameron Carr
2026-08-07 15:11   ` Michael Kelley
2026-08-06 21:33 ` [PATCH v3 3/3] hv_netvsc: Allocate host-visible GPADL buffers using vmbus_alloc_buffer() Kameron Carr
2026-08-07 15:12   ` Michael Kelley

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox