|SUCCESS| [v11,5/5] vhost_user: enable configure memory slots

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Tue May 5 15:33:09 CEST 2026


Test-Label: ai-code-review
Test-Status: SUCCESS
https://dpdk.org/patch/163566

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-05-05

Reviewing patch series for DPDK vhost-user add/remove memory region support.

I have several concerns about resource management and error handling in this
patch series.

> From: Pravin M Bathija <pravin.bathija at dell.com>
> 
> - add user to mailmap file.
> - define a bit-field called VHOST_USER_PROTOCOL_F_CONFIGURE_MEM_SLOTS
>   that depicts if the feature/capability to add/remove memory regions
>   is supported. This is a part of the overall support for add/remove
>   memory region feature in this patchset.

[ ... ]

> diff --git a/lib/vhost/vhost_user.h b/lib/vhost/vhost_user.h
> index ef486545ba..f8d921f7f1 100644
> --- a/lib/vhost/vhost_user.h
> +++ b/lib/vhost/vhost_user.h
> @@ -67,6 +67,9 @@ typedef enum VhostUserRequest {
>  	VHOST_USER_POSTCOPY_END = 30,
>  	VHOST_USER_GET_INFLIGHT_FD = 31,
>  	VHOST_USER_SET_INFLIGHT_FD = 32,
> +	VHOST_USER_GET_MAX_MEM_SLOTS = 36,
> +	VHOST_USER_ADD_MEM_REG = 37,
> +	VHOST_USER_REM_MEM_REG = 38,
>  	VHOST_USER_SET_STATUS = 39,
>  	VHOST_USER_GET_STATUS = 40,
>  } VhostUserRequest;
> @@ -91,6 +94,11 @@ typedef struct VhostUserMemory {
>  	VhostUserMemoryRegion regions[VHOST_MEMORY_MAX_NREGIONS];
>  } VhostUserMemory;
>  
> +typedef struct VhostUserSingleMemReg {
> +	uint64_t padding;
> +	VhostUserMemoryRegion region;
> +} VhostUserSingleMemReg;

Does this struct need __rte_packed annotation? The VhostUserMsg is declared
as __rte_packed_begin, but this structure might have unexpected padding from
the compiler.

[ ... ]

> diff --git a/lib/vhost/vhost_user.c b/lib/vhost/vhost_user.c
> index 4bfb13fb98..1f96ecf963 100644
> --- a/lib/vhost/vhost_user.c
> +++ b/lib/vhost/vhost_user.c

[ ... ]

> +static int
> +async_dma_map_region(struct virtio_net *dev, struct rte_vhost_mem_region *reg, bool do_map)
> +{
> +	uint32_t i;
> +	int ret;
> +	uint64_t reg_start = reg->host_user_addr;
> +	uint64_t reg_end = reg_start + reg->size;

Can reg_start + reg->size overflow? Should this validate that the addition
does not wrap around?

> +
> +	for (i = 0; i < dev->nr_guest_pages; i++) {
> +		struct guest_page *page = &dev->guest_pages[i];
> +
> +		/* Only process pages belonging to this region */
> +		if (page->host_user_addr < reg_start ||
> +		    page->host_user_addr >= reg_end)
> +			continue;
> +
> +		if (do_map) {
> +			ret = rte_vfio_container_dma_map(RTE_VFIO_DEFAULT_CONTAINER_FD,
> +					page->host_user_addr,
> +					page->host_iova,
> +					page->size);
> +			if (ret) {
> +				if (rte_errno == ENODEV)
> +					return 0;
> +
> +				VHOST_CONFIG_LOG(dev->ifname, ERR, "DMA engine map failed");
> +				return -1;
> +			}
> +		} else {
> +			ret = rte_vfio_container_dma_unmap(RTE_VFIO_DEFAULT_CONTAINER_FD,
> +					page->host_user_addr,
> +					page->host_iova,
> +					page->size);
> +			if (ret) {
> +				if (rte_errno == EINVAL)
> +					return 0;
> +
> +				VHOST_CONFIG_LOG(dev->ifname, ERR, "DMA engine unmap failed");
> +				return -1;
> +			}
> +		}
> +	}
> +
> +	return 0;
> +}

If async_dma_map_region() is called with do_map=true and fails partway
through the loop, does this code leave some pages mapped and others
unmapped? Should there be rollback logic to unmap any pages that were
successfully mapped before the failure?

> +
>  static void
>  async_dma_map(struct virtio_net *dev, bool do_map)
>  {

[ ... ]

>  static void
> -free_mem_region(struct virtio_net *dev)
> +free_mem_region(struct rte_vhost_mem_region *reg)
> +{
> +	if (reg != NULL && reg->mmap_addr) {
> +		munmap(reg->mmap_addr, reg->mmap_size);
> +		close(reg->fd);
> +		memset(reg, 0, sizeof(struct rte_vhost_mem_region));
> +	}
> +}

Does this code check if munmap() or close() fail? Should failures be logged?

> +
> +static void
> +free_all_mem_regions(struct virtio_net *dev)
>  {
>  	uint32_t i;
>  	struct rte_vhost_mem_region *reg;
> @@ -236,12 +292,10 @@ free_mem_region(struct virtio_net *dev)
>  	if (dev->async_copy && rte_vfio_is_enabled("vfio"))
>  		async_dma_map(dev, false);
>  
> -	for (i = 0; i < dev->mem->nregions; i++) {
> +	for (i = 0; i < VHOST_MEMORY_MAX_NREGIONS; i++) {
>  		reg = &dev->mem->regions[i];
> -		if (reg->host_user_addr) {
> -			munmap(reg->mmap_addr, reg->mmap_size);
> -			close(reg->fd);
> -		}
> +		if (reg->mmap_addr)
> +			free_mem_region(reg);

Why does the new code check reg->mmap_addr while the old code checked
reg->host_user_addr? Can a region have one set but not the other?

[ ... ]

> @@ -1136,6 +1192,24 @@ add_guest_pages(struct virtio_net *dev, struct rte_vhost_mem_region *reg,
>  	return 0;
>  }
>  
> +static void
> +remove_guest_pages(struct virtio_net *dev, struct rte_vhost_mem_region *reg)
> +{
> +	uint64_t reg_start = reg->host_user_addr;
> +	uint64_t reg_end = reg_start + reg->size;

Again, can reg_start + reg->size overflow?

> +	uint32_t i, j = 0;
> +
> +	for (i = 0; i < dev->nr_guest_pages; i++) {
> +		if (dev->guest_pages[i].host_user_addr >= reg_start &&
> +		    dev->guest_pages[i].host_user_addr < reg_end)
> +			continue;
> +		if (j != i)
> +			dev->guest_pages[j] = dev->guest_pages[i];
> +		j++;
> +	}
> +	dev->nr_guest_pages = j;
> +}

This function compacts the guest_pages array by removing entries in the
specified region. Does this correctly handle the case where
guest_pages[i].host_user_addr + guest_pages[i].size crosses the region
boundary? The check only looks at host_user_addr but not the page size.

[ ... ]

> @@ -1246,10 +1320,14 @@ vhost_user_postcopy_register(struct virtio_net *dev, int main_fd,
>  	 * DPDK's virtual address with Qemu, so that Qemu can
>  	 * retrieve the region offset when handling userfaults.
>  	 */
> +	int reg_msg_index = 0;
>  	memory = &ctx->msg.payload.memory;
>  	for (i = 0; i < memory->nregions; i++) {
>  		reg = &dev->mem->regions[i];
> -		memory->regions[i].userspace_addr = reg->host_user_addr;
> +		if (reg->host_user_addr == 0)
> +			continue;
> +		memory->regions[reg_msg_index].userspace_addr = reg->host_user_addr;
> +		reg_msg_index++;

If regions are skipped when host_user_addr is 0, does this code update
memory->nregions to reflect the actual number of regions being sent? The
loop updates reg_msg_index but I don't see where memory->nregions is
adjusted.

[ ... ]

>  	/* Now userfault register and we can use the memory */
>  	for (i = 0; i < memory->nregions; i++) {
>  		reg = &dev->mem->regions[i];
> +		if (reg->host_user_addr == 0)
> +			continue;

Same question: if some regions are skipped, does the loop index i still
match correctly with what was sent to QEMU in the previous loop?

[ ... ]

> +static int
> +vhost_user_initialize_memory(struct virtio_net **pdev)
> +{
> +	struct virtio_net *dev = *pdev;
> +	int numa_node = SOCKET_ID_ANY;
> +
> +	if (dev->mem != NULL) {
> +		VHOST_CONFIG_LOG(dev->ifname, ERR,
> +			"memory already initialized, free it first");
> +		return -1;
> +	}
> +
> +	/*
> +	 * If VQ 0 has already been allocated, try to allocate on the same
> +	 * NUMA node. It can be reallocated later in numa_realloc().
> +	 */
> +	if (dev->nr_vring > 0)
> +		numa_node = dev->virtqueue[0]->numa_node;
> +
> +	dev->nr_guest_pages = 0;
> +	if (dev->guest_pages == NULL) {
> +		dev->max_guest_pages = 8;
> +		dev->guest_pages = rte_zmalloc_socket(NULL,
> +					dev->max_guest_pages *
> +					sizeof(struct guest_page),
> +					RTE_CACHE_LINE_SIZE,
> +					numa_node);
> +		if (dev->guest_pages == NULL) {
> +			VHOST_CONFIG_LOG(dev->ifname, ERR,
> +				"failed to allocate memory for dev->guest_pages");
> +			return -1;
> +		}
> +	}
> +
> +	dev->mem = rte_zmalloc_socket("vhost-mem-table", sizeof(struct rte_vhost_memory) +
> +		sizeof(struct rte_vhost_mem_region) * VHOST_MEMORY_MAX_NREGIONS, 0, numa_node);
> +	if (dev->mem == NULL) {
> +		VHOST_CONFIG_LOG(dev->ifname, ERR, "failed to allocate memory for dev->mem");
> +		rte_free(dev->guest_pages);
> +		dev->guest_pages = NULL;
> +		return -1;
> +	}
> +
> +	return 0;
> +}

If dev->mem allocation fails, does this code also need to reset
dev->max_guest_pages to 0? The failure path frees dev->guest_pages and sets
it to NULL, but leaves max_guest_pages at 8.

[ ... ]

> +static int
> +vhost_user_add_mem_reg(struct virtio_net **pdev,
> +			struct vhu_msg_context *ctx,
> +			int main_fd __rte_unused)
> +{
> +	uint32_t i;
> +	struct virtio_net *dev = *pdev;
> +	struct VhostUserMemoryRegion *region = &ctx->msg.payload.memory_single.region;
> +
> +	/* convert first region add to normal memory table set */
> +	if (dev->mem == NULL) {
> +		if (vhost_user_initialize_memory(pdev) < 0)
> +			goto close_msg_fds;
> +	}
> +
> +	/* make sure new region will fit */
> +	if (dev->mem->nregions >= VHOST_MEMORY_MAX_NREGIONS) {
> +		VHOST_CONFIG_LOG(dev->ifname, ERR, "too many memory regions already (%u)",
> +									dev->mem->nregions);
> +		goto close_msg_fds;
> +	}
> +
> +	/* make sure supplied memory fd present */
> +	if (ctx->fd_num != 1) {
> +		VHOST_CONFIG_LOG(dev->ifname, ERR, "fd count makes no sense (%u)", ctx->fd_num);
> +		goto close_msg_fds;
> +	}
> +
> +	/* Make sure no overlap in guest virtual address space */
> +	for (i = 0; i < dev->mem->nregions; i++) {
> +		struct rte_vhost_mem_region *current_region = &dev->mem->regions[i];
> +		uint64_t current_region_guest_start = current_region->guest_user_addr;
> +		uint64_t current_region_guest_end = current_region_guest_start
> +							+ current_region->size - 1;
> +		uint64_t proposed_region_guest_start = region->userspace_addr;
> +		uint64_t proposed_region_guest_end = proposed_region_guest_start
> +							+ region->memory_size - 1;

Can the additions in current_region_guest_end and proposed_region_guest_end
overflow? Should the code validate size values are non-zero before
subtracting 1?

> +
> +		if (!((proposed_region_guest_end < current_region_guest_start) ||
> +			(proposed_region_guest_start > current_region_guest_end))) {

The parentheses around each comparison are unnecessary according to DPDK
coding style.

[ ... ]

> +	}
> +
> +	/* New region goes at the end of the contiguous array */
> +	struct rte_vhost_mem_region *reg = &dev->mem->regions[dev->mem->nregions];
> +
> +	reg->guest_phys_addr = region->guest_phys_addr;
> +	reg->guest_user_addr = region->userspace_addr;
> +	reg->size            = region->memory_size;
> +	reg->fd              = ctx->fds[0];
> +	ctx->fds[0]          = -1;
> +
> +	if (vhost_user_mmap_region


More information about the test-report mailing list