From: Kameron Carr <[email protected]>

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 <[email protected]>

Modulo the nit, looks good to me.

Reviewed-by: Michael Kelley <[email protected]>

> ---
>  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
> 


Reply via email to