From: "Kameron Carr" <kameroncarr@linux.microsoft.com>
To: "'Michael Kelley'" <mhklinux@outlook.com>, <decui@microsoft.com>,
<haiyangz@microsoft.com>, <kys@microsoft.com>,
<longli@microsoft.com>, <wei.liu@kernel.org>
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>
Subject: RE: [PATCH v2 2/2] hv_netvsc: Allocate host-visible GPADL buffers as decrypted contiguous chunks
Date: Thu, 6 Aug 2026 13:06:08 -0700 [thread overview]
Message-ID: <002d01dd25df$0b94f8b0$22beea10$@linux.microsoft.com> (raw)
In-Reply-To: <SN6PR02MB41576627C0DBA40FA8BB49C7D4C82@SN6PR02MB4157.namprd02.prod.outlook.com>
On Friday, July 31, 2026 8:47 AM, Michael Kelley wrote:
> From: Kameron Carr <kameroncarr@linux.microsoft.com> Sent: Thursday, July
30, 2026 4:34 PM
> >
> > 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().
> >
> > This approach minimizes scattering of decrypted 4 KiB pages through the
> > kernel direct map and the resulting shattering of large page mappings.
> >
> > Use vmbus_establish_gpadl_caller_decrypted() so there is no attempt
> > to decrypt the virtual address. At teardown, vunmap() the range and
> > re-encrypt and free each chunk individually; any chunk that fails
> > re-encryption is leaked to prevent accidentally freeing decrypted
memory.
> >
> > 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/hv/channel.c | 158 ++++++++++++++++++++++++++++++++
> > drivers/net/hyperv/hyperv_net.h | 8 +-
> > drivers/net/hyperv/netvsc.c | 104 ++++++++++++++-------
> > drivers/net/hyperv/netvsc_drv.c | 6 ++
> > include/linux/hyperv.h | 7 ++
> > 5 files changed, 249 insertions(+), 34 deletions(-)
>
> Arguably, this patch should be broken into two patches. One
> patch adds the buffer allocation/free functions to the VMBus core.
> The second updates netvsc to use the new allocation/free
> functions and to call the free function in the required context.
Ack.
> > diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
[...]
> > +void *vmbus_alloc_buffer(struct vmbus_channel *channel,
> > + u32 size,
> > + struct page ***chunks_out,
> > + u32 *chunk_cnt_out)
> > +{
> > + u32 nr_pages = PFN_UP(size);
>
> Is there a reason for nr_pages to be u32, and then cast it to unsigned
long
> a couple of places below? The first argument to kvmalloc_array() is of
> type size_t. Avoiding the casts would be incrementally cleaner.
Ack.
> > + struct page **chunks = NULL;
> > + struct page **pages = NULL;
> > + unsigned int order;
> > + u32 chunk_cnt = 0;
> > + u32 page_idx = 0;
> > + u32 remaining = nr_pages;
> > + 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((unsigned long)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;
> > +
> > + /*
> > + * @order monotonically decreases across iterations
> > + *
> > + * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing, but try
> > + * harder at order 0 since that is the final fallback.
> > + */
> > + order = min(MAX_PAGE_ORDER, ilog2(nr_pages));
>
> This works, but is more complex than needed. Just set order to
> MAX_PAGE_ORDER, and the min() at the top of the while loop below
> will do the right thing. And you could do order = MAX_PAGE_ORDER
> where order is declared.
Ack.
> > + while (remaining) {
> > + struct page *page;
> > + gfp_t gfp;
> > +
> > + order = min(order, ilog2(remaining));
Context: this fails to compile on x86 since order is unsigned and ilog2()
is signed.
Is it acceptable to avoid the min_t() by changing order to be a signed int?
> > +
> > + for (;;) {
> > + 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)
> > + break;
> > + if (!order)
> > + goto err;
> > + order--;
> > + }
>
> I think this nested for loop can be avoided. At this point in the outer
while
> loop, set the gfp flags and call alloc_pages_node() as you have here. The
> good and normal path is that alloc_pages_node() succeeds. The exception
> path is alloc_pages_node() failing, so you can do:
>
> if (!page) {
> if (!order--)
> goto err;
> continue;
> }
>
> The "continue" just restarts the outer while loop with the decremented
> value of "order" and everything proceeds normally. To me avoiding the
> nested loop is simpler, though "simpler" can be in the eye of the
beholder,
> so if you prefer to keep this as is, I'm OK with that.
I agree. Thank you for the suggestion.
> > +
> > + 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, (unsigned long)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;
> > +}
[...]
> > diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c
> > index 59e95341f9b1e..1192929d93a86 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;
> > +
>
> Does netvsc needs its own workqueue to do the "free" operation,
> or would the system default workqueue (system_dfl_wq) be just
> as good? At first glance, the system_dfl_wq seems like it would work,
> since netvsc free operations are rare and don't have any strict
> latency requirements. But I'm far from being expect in workqueues,
> and there could be subtleties I'm not aware of.
I have to drain the work queue before module unload.
Currently this is handled by destroying the queue.
Originally, I tried using a system wide queue, but I saw that there are
compile time warnings, __warn_flushing_systemwide_wq(), which state:
"WARNING: Flushing system-wide workqueues will be prohibited in near
future."
I got the impression that having a driver specific work queue is best
practice.
- Kameron
prev parent reply other threads:[~2026-08-06 20:06 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-30 23:33 [PATCH v2 0/2] Drivers: hv: decrypt netvsc buffers on contiguous direct-map addresses Kameron Carr
2026-07-30 23:33 ` [PATCH v2 1/2] Drivers: hv: vmbus: add vmbus_establish_gpadl_caller_decrypted() Kameron Carr
2026-07-31 15:46 ` Michael Kelley
2026-07-30 23:33 ` [PATCH v2 2/2] hv_netvsc: Allocate host-visible GPADL buffers as decrypted contiguous chunks Kameron Carr
2026-07-31 15:46 ` Michael Kelley
2026-08-06 20:06 ` Kameron Carr [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='002d01dd25df$0b94f8b0$22beea10$@linux.microsoft.com' \
--to=kameroncarr@linux.microsoft.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=decui@microsoft.com \
--cc=edumazet@google.com \
--cc=haiyangz@microsoft.com \
--cc=kuba@kernel.org \
--cc=kys@microsoft.com \
--cc=linux-hyperv@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=longli@microsoft.com \
--cc=mhklinux@outlook.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--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