Skip to contentSkip to conversation

Add vduse live migration features

PATCH Applied Eugenio Pérez 2 participants 13 messages Oct 19

lore
Share & tools
lore
#0 lore raw

v5 cover Add vduse live migration features

This series introduces features to the VDUSE (vDPA Device in Userspace) driver to support Live Migration.

Currently, DPDK does not support VDUSE devices live migration because the driver lacks a mechanism to suspend the device and quiesce the rings to initiate the switchover. This series implements the suspend operation to address this limitation.

Furthermore, enabling Live Migration for devices with control virtqueue requires two additional features. Both of them are included in this series.

* Address Spaces ID (ASID) support: This allows QEMU to isolate and intercept the device's CVQ. By doing so, QEMU is able to migrate the device status transparently, without requiring the device to support state save and restore.
* QUEUE_READY: This allows QEMU to control when the dataplane virtqueues are enabled. This ensures the dataplane is started after the device configuration has been fully restored via the CVQ.

It also enables the VIRTIO_NET_F_STATUS feature. This allows the device to signal the driver that it needs to send gratuitous ARP with VIRTIO_NET_S_ANNOUNCE, reducing the Live Migration downtime.

v5:
* Update headers to 7.3
* Rebase on top of main, merge conflicts.

v4:
* Rebase on top of origin/main, which already includes the __counted_by error fix.
* Add VDUSE_SUSPEND to lib/vhost/vduse.c:vduse_reqs_str (Maxime).

v3:
* Include uapi/linux/stddef.h and revert the -D__counted_by from v2.

v2:
* Add -D__counted_by(X)= if the compiler does not support it. It is pulled by the kernel headers.

PATCH v1:
* Move to VDUSE_SET_FEATURES ioctl instead of config space field.
* Add a few bounds control.
* Update kernel headers to v7.3-rc3.

RFC v4:
* Sync headers with Linux's latest existing and proposed UAPI. Both files constants and new ioctl VDUSE_SET_FEATURES.
* Check for more error conditions and clarified some error messages in ready message processing.
* Add relevant release notes.
* Fix cosmetic whitespaces & checkpath errors.
* Fix error path of vhost_user_iotlb_init and vhost_user_iotlb_init_one.
* Fix commits author.

RFC v3:
* Replace incorrect '%lx' DEBUG print format specifier with PRIx64

RFC v2:
* Following latest comments on kernel series about VDUSE features, not checking API version but only check if VDUSE_GET_FEATURES success.
* Move the start and last declarations in the braces as gcc 8 does not like them interleaved with statements. Actually, I think the move was a mistake in the first version.
https://mails.dpdk.org/archives/test-report/2026-February/958175.html

Eugenio Pérez (3):
uapi: import VDUSE and VFIO header from v7.3 kernel
vhost: Support VDUSE QUEUE_READY feature
vhost: Support vduse suspend feature

Maxime Coquelin (6):
vhost: introduce ASID support
vhost: add VDUSE API version negotiation
vhost: add virtqueues groups support to VDUSE
vhost: add ASID support to VDUSE IOTLB operations
vhost: claim VDUSE support for API version 1
vhost: add net status feature to VDUSE

 kernel/linux/uapi/linux/iommufd.h | 105 +++++++++++-
 kernel/linux/uapi/linux/stddef.h  |   4 +
 kernel/linux/uapi/linux/vduse.h   | 113 ++++++++++++-
 kernel/linux/uapi/linux/vfio.h    |  51 +++++-
 kernel/linux/uapi/version         |   2 +-
 lib/vhost/iotlb.c                 | 233 +++++++++++++++++----------
 lib/vhost/iotlb.h                 |  14 +-
 lib/vhost/vduse.c                 | 259 +++++++++++++++++++++++++++---
 lib/vhost/vduse.h                 |   3 +-
 lib/vhost/vhost.c                 |  16 +-
 lib/vhost/vhost.h                 |  16 +-
 lib/vhost/vhost_user.c            |  13 +-
 12 files changed, 676 insertions(+), 153 deletions(-)
Signature · 1 line
2.56.0
#1 lore raw

PATCH v5 1/9 uapi: import VDUSE and VFIO header from v7.3 kernel

This header will be used by the Vhost library.

---
 kernel/linux/uapi/linux/iommufd.h | 105 +++++++++++++++++++++++++--
 kernel/linux/uapi/linux/stddef.h  |   4 ++
 kernel/linux/uapi/linux/vduse.h   | 113 ++++++++++++++++++++++++++++--
 kernel/linux/uapi/linux/vfio.h    |  51 +++++++++++++-
 kernel/linux/uapi/version         |   2 +-
 5 files changed, 261 insertions(+), 14 deletions(-)
kernel/linux/uapi/linux/iommufd.h +98 -7
index 5ee08de08388..26b1cb9465e2 100644
@@ -57,6 +57,7 @@ enum {
 	IOMMUFD_CMD_IOAS_CHANGE_PROCESS = 0x92,
 	IOMMUFD_CMD_VEVENTQ_ALLOC = 0x93,
 	IOMMUFD_CMD_HW_QUEUE_ALLOC = 0x94,
+	IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA = 0x95,
 };
 
 /**
@@ -219,18 +220,49 @@ struct iommu_ioas_map {
 };
 #define IOMMU_IOAS_MAP _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_MAP)
 
+/**
+ * struct iommu_ioas_noiommu_get_pa - ioctl(IOMMU_IOAS_NOIOMMU_GET_PA)
+ * @size: sizeof(struct iommu_ioas_noiommu_get_pa)
+ * @flags: Reserved, must be 0 for now
+ * @ioas_id: IOAS ID to query IOVA to PA mapping from
+ * @__reserved: Must be 0
+ * @iova: IOVA to query
+ * @length: On input, non-zero maximum number of bytes to query starting from
+ *          @iova. On output, number of physically contiguous bytes starting
+ *          from @out_phys, capped by the input length.
+ * @out_phys: Output physical address the IOVA maps to
+ *
+ * Query the physical address backing an IOVA range. The beginning of the
+ * range must be mapped already and length must be non-zero. For noiommu
+ * devices doing unsafe DMA only.
+ */
+struct iommu_ioas_noiommu_get_pa {
+	__u32 size;
+	__u32 flags;
+	__u32 ioas_id;
+	__u32 __reserved;
+	__aligned_u64 iova;
+	__aligned_u64 length;
+	__aligned_u64 out_phys;
+};
+#define IOMMU_IOAS_NOIOMMU_GET_PA _IO(IOMMUFD_TYPE, IOMMUFD_CMD_IOAS_NOIOMMU_GET_PA)
+
 /**
  * struct iommu_ioas_map_file - ioctl(IOMMU_IOAS_MAP_FILE)
  * @size: sizeof(struct iommu_ioas_map_file)
  * @flags: same as for iommu_ioas_map
  * @ioas_id: same as for iommu_ioas_map
- * @fd: the memfd to map
- * @start: byte offset from start of file to map from
+ * @fd: the memfd or supported dma-buf file to map
+ * @start: byte offset from start of the file to map from
  * @length: same as for iommu_ioas_map
  * @iova: same as for iommu_ioas_map
  *
- * Set an IOVA mapping from a memfd file.  All other arguments and semantics
- * match those of IOMMU_IOAS_MAP.
+ * Set an IOVA mapping from a memfd file. On kernels with dma-buf support,
+ * supported dma-buf files may also be accepted. This is not a generic
+ * dma-buf import path; currently supported dma-bufs include single-range
+ * VFIO PCI dma-bufs exported through VFIO_DEVICE_FEATURE_DMA_BUF, and
+ * other dma-bufs may be rejected. All other arguments and semantics match
+ * those of IOMMU_IOAS_MAP.
  */
 struct iommu_ioas_map_file {
 	__u32 size;
@@ -465,16 +497,27 @@ struct iommu_hwpt_arm_smmuv3 {
 	__aligned_le64 ste[2];
 };
 
+/**
+ * struct iommu_hwpt_amd_guest - AMD IOMMU guest I/O page table data
+ *				 (IOMMU_HWPT_DATA_AMD_GUEST)
+ * @dte: Guest Device Table Entry (DTE)
+ */
+struct iommu_hwpt_amd_guest {
+	__aligned_u64 dte[4];
+};
+
 /**
  * enum iommu_hwpt_data_type - IOMMU HWPT Data Type
  * @IOMMU_HWPT_DATA_NONE: no data
  * @IOMMU_HWPT_DATA_VTD_S1: Intel VT-d stage-1 page table
  * @IOMMU_HWPT_DATA_ARM_SMMUV3: ARM SMMUv3 Context Descriptor Table
+ * @IOMMU_HWPT_DATA_AMD_GUEST: AMD IOMMU guest page table
  */
 enum iommu_hwpt_data_type {
 	IOMMU_HWPT_DATA_NONE = 0,
 	IOMMU_HWPT_DATA_VTD_S1 = 1,
 	IOMMU_HWPT_DATA_ARM_SMMUV3 = 2,
+	IOMMU_HWPT_DATA_AMD_GUEST = 3,
 };
 
 /**
@@ -559,11 +602,22 @@ struct iommu_hw_info_vtd {
 	__aligned_u64 ecap_reg;
 };
 
+/**
+ * enum iommu_hw_info_arm_smmuv3_flags - Flags for ARM SMMUv3 hw_info
+ * @IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI:
+ *    If set, user space must issue TLBI/CFGI+SYNC commands twice due to
+ *    hardware erratum T264-SMMU-3. See the description at
+ *    arm_smmu_erratum_repeat_tlbi_cfgi_key.
+ */
+enum iommu_hw_info_arm_smmuv3_flags {
+	IOMMU_HW_INFO_ARM_SMMUV3_ERRATA_REPEAT_TLBI_CFGI = 1 << 0,
+};
+
 /**
  * struct iommu_hw_info_arm_smmuv3 - ARM SMMUv3 hardware information
  *                                   (IOMMU_HW_INFO_TYPE_ARM_SMMUV3)
  *
- * @flags: Must be set to 0
+ * @flags: Combination of enum iommu_hw_info_arm_smmuv3_flags
  * @__reserved: Must be 0
  * @idr: Implemented features for ARM SMMU Non-secure programming interface
  * @iidr: Information about the implementation and implementer of ARM SMMU,
@@ -579,7 +633,7 @@ struct iommu_hw_info_vtd {
  * idr[0]: ST_LEVEL, TERM_MODEL, STALL_MODEL, TTENDIAN , CD2L, ASID16, TTF
  * idr[1]: SIDSIZE, SSIDSIZE
  * idr[3]: BBML, RIL
- * idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K
+ * idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K, DS
  *
  * - S1P should be assumed to be true if a NESTED HWPT can be created
  * - VFIO/iommufd only support platforms with COHACC, it should be assumed to be
@@ -587,7 +641,7 @@ struct iommu_hw_info_vtd {
  * - ATS is a per-device property. If the VMM describes any devices as ATS
  *   capable in ACPI/DT it should set the corresponding idr.
  *
- * This list may expand in future (eg E0PD, AIE, PBHA, D128, DS etc). It is
+ * This list may expand in future (eg E0PD, AIE, PBHA, D128 etc). It is
  * important that VMMs do not read bits outside the list to allow for
  * compatibility with future kernels. Several features in the SMMUv3
  * architecture are not currently supported by the kernel for nesting: HTTU,
@@ -623,6 +677,32 @@ struct iommu_hw_info_tegra241_cmdqv {
 	__u8 __reserved;
 };
 
+/**
+ * struct iommu_hw_info_amd - AMD IOMMU device info
+ *
+ * @efr : Value of AMD IOMMU Extended Feature Register (EFR)
+ * @efr2: Value of AMD IOMMU Extended Feature 2 Register (EFR2)
+ *
+ * Please See description of these registers in the following sections of
+ * the AMD I/O Virtualization Technology (IOMMU) Specification.
+ * (https://docs.amd.com/v/u/en-US/48882_3.10_PUB)
+ *
+ * - MMIO Offset 0030h IOMMU Extended Feature Register
+ * - MMIO Offset 01A0h IOMMU Extended Feature 2 Register
+ *
+ * Note: The EFR and EFR2 are raw values reported by hardware.
+ * VMM is responsible to determine the appropriate flags to be exposed to
+ * the VM since cetertain features are not currently supported by the kernel
+ * for HW-vIOMMU.
+ *
+ * Current VMM-allowed list of feature flags are:
+ * - EFR[GTSup, GASup, GioSup, PPRSup, EPHSup, GATS, GLX, PASmax]
+ */
+struct iommu_hw_info_amd {
+	__aligned_u64 efr;
+	__aligned_u64 efr2;
+};
+
 /**
  * enum iommu_hw_info_type - IOMMU Hardware Info Types
  * @IOMMU_HW_INFO_TYPE_NONE: Output by the drivers that do not report hardware
@@ -632,6 +712,7 @@ struct iommu_hw_info_tegra241_cmdqv {
  * @IOMMU_HW_INFO_TYPE_ARM_SMMUV3: ARM SMMUv3 iommu info type
  * @IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV: NVIDIA Tegra241 CMDQV (extension for ARM
  *                                     SMMUv3) info type
+ * @IOMMU_HW_INFO_TYPE_AMD: AMD IOMMU info type
  */
 enum iommu_hw_info_type {
 	IOMMU_HW_INFO_TYPE_NONE = 0,
@@ -639,6 +720,7 @@ enum iommu_hw_info_type {
 	IOMMU_HW_INFO_TYPE_INTEL_VTD = 1,
 	IOMMU_HW_INFO_TYPE_ARM_SMMUV3 = 2,
 	IOMMU_HW_INFO_TYPE_TEGRA241_CMDQV = 3,
+	IOMMU_HW_INFO_TYPE_AMD = 4,
 };
 
 /**
@@ -656,11 +738,15 @@ enum iommu_hw_info_type {
  * @IOMMU_HW_CAP_PCI_PASID_PRIV: Privileged Mode Supported, user ignores it
  *                               when the struct
  *                               iommu_hw_info::out_max_pasid_log2 is zero.
+ * @IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED: ATS is not supported or cannot be used
+ *                                      on this device (absence implies ATS
+ *                                      may be enabled)
  */
 enum iommufd_hw_capabilities {
 	IOMMU_HW_CAP_DIRTY_TRACKING = 1 << 0,
 	IOMMU_HW_CAP_PCI_PASID_EXEC = 1 << 1,
 	IOMMU_HW_CAP_PCI_PASID_PRIV = 1 << 2,
+	IOMMU_HW_CAP_PCI_ATS_NOT_SUPPORTED = 1 << 3,
 };
 
 /**
@@ -1013,6 +1099,11 @@ struct iommu_fault_alloc {
 enum iommu_viommu_type {
 	IOMMU_VIOMMU_TYPE_DEFAULT = 0,
 	IOMMU_VIOMMU_TYPE_ARM_SMMUV3 = 1,
+	/*
+	 * TEGRA241_CMDQV requirements (otherwise, VCMDQs will not work)
+	 * - Kernel will allocate a VINTF (HYP_OWN=0) to back this VIOMMU. So,
+	 *   VMM must wire the HYP_OWN bit to 0 in guest VINTF_CONFIG register
+	 */
 	IOMMU_VIOMMU_TYPE_TEGRA241_CMDQV = 2,
 };
 
kernel/linux/uapi/linux/stddef.h +4 -0
index 8ecf96def499..e1619f8395a2 100644
@@ -69,6 +69,10 @@
 #define __counted_by_be(m)
 #endif
 
+#ifndef __counted_by_ptr
+#define __counted_by_ptr(m)
+#endif
+
 #define __kernel_nonstring
 
 #endif /* _LINUX_STDDEF_H */
kernel/linux/uapi/linux/vduse.h +108 -5

214 lines not on this page · raw message

kernel/linux/uapi/linux/vfio.h +50 -1

81 lines not on this page · raw message

kernel/linux/uapi/version +1 -1
index 69f29b2320e4..6fd05018ab7c 100644
@@ -1 +1 @@
-v6.19
+v7.3
Signature · 1 line
2.56.0
Reviewed-by on v5 1/9
#2 lore raw

Re: [PATCH v5 1/9] uapi: import VDUSE and VFIO header from v7.3 kernel

Quoted 12 lines · Eugenio Pérez

This header will be used by the Vhost library.

---
 kernel/linux/uapi/linux/iommufd.h | 105 +++++++++++++++++++++++++--
 kernel/linux/uapi/linux/stddef.h  |   4 ++
 kernel/linux/uapi/linux/vduse.h   | 113 ++++++++++++++++++++++++++++--
 kernel/linux/uapi/linux/vfio.h    |  51 +++++++++++++-
 kernel/linux/uapi/version         |   2 +-
 5 files changed, 261 insertions(+), 14 deletions(-)

Note that I tested this is properly imported using the linux-uapi.sh checker.

Thanks,
Maxime

#3 lore raw

PATCH v5 2/9 vhost: introduce ASID support

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

Set all ASID = 0 as it is the default when not set explicitly.

---
 lib/vhost/iotlb.c      | 233 +++++++++++++++++++++++++----------------
 lib/vhost/iotlb.h      |  14 +--
 lib/vhost/vduse.c      |   9 +-
 lib/vhost/vhost.c      |  16 +--
 lib/vhost/vhost.h      |  13 +--
 lib/vhost/vhost_user.c |  13 +--
 6 files changed, 177 insertions(+), 121 deletions(-)
lib/vhost/iotlb.c +145 -88
index f2c275a7d77e..e5e69d11cb65 100644
@@ -11,6 +11,16 @@
 #include "iotlb.h"
 #include "vhost.h"
 
+struct iotlb {
+	rte_rwlock_t			pending_lock;
+	struct vhost_iotlb_entry	*pool;
+	TAILQ_HEAD(, vhost_iotlb_entry)	list;
+	TAILQ_HEAD(, vhost_iotlb_entry)	pending_list;
+	int				cache_nr;
+	rte_spinlock_t			free_lock;
+	SLIST_HEAD(, vhost_iotlb_entry)	free_list;
+};
+
 struct vhost_iotlb_entry {
 	TAILQ_ENTRY(vhost_iotlb_entry) next;
 	SLIST_ENTRY(vhost_iotlb_entry) next_free;
@@ -85,78 +95,78 @@ vhost_user_iotlb_clear_dump(struct virtio_net *dev, struct vhost_iotlb_entry *no
 }
 
 static struct vhost_iotlb_entry *
-vhost_user_iotlb_pool_get(struct virtio_net *dev)
+vhost_user_iotlb_pool_get(struct virtio_net *dev, int asid)
 {
 	struct vhost_iotlb_entry *node;
 
-	rte_spinlock_lock(&dev->iotlb_free_lock);
-	node = SLIST_FIRST(&dev->iotlb_free_list);
+	rte_spinlock_lock(&dev->iotlb[asid]->free_lock);
+	node = SLIST_FIRST(&dev->iotlb[asid]->free_list);
 	if (node != NULL)
-		SLIST_REMOVE_HEAD(&dev->iotlb_free_list, next_free);
-	rte_spinlock_unlock(&dev->iotlb_free_lock);
+		SLIST_REMOVE_HEAD(&dev->iotlb[asid]->free_list, next_free);
+	rte_spinlock_unlock(&dev->iotlb[asid]->free_lock);
 	return node;
 }
 
 static void
-vhost_user_iotlb_pool_put(struct virtio_net *dev, struct vhost_iotlb_entry *node)
+vhost_user_iotlb_pool_put(struct virtio_net *dev, int asid, struct vhost_iotlb_entry *node)
 {
-	rte_spinlock_lock(&dev->iotlb_free_lock);
-	SLIST_INSERT_HEAD(&dev->iotlb_free_list, node, next_free);
-	rte_spinlock_unlock(&dev->iotlb_free_lock);
+	rte_spinlock_lock(&dev->iotlb[asid]->free_lock);
+	SLIST_INSERT_HEAD(&dev->iotlb[asid]->free_list, node, next_free);
+	rte_spinlock_unlock(&dev->iotlb[asid]->free_lock);
 }
 
 static void
-vhost_user_iotlb_cache_random_evict(struct virtio_net *dev);
+vhost_user_iotlb_cache_random_evict(struct virtio_net *dev, int asid);
 
 static void
-vhost_user_iotlb_pending_remove_all(struct virtio_net *dev)
+vhost_user_iotlb_pending_remove_all(struct virtio_net *dev, int asid)
 {
 	struct vhost_iotlb_entry *node, *temp_node;
 
-	rte_rwlock_write_lock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_lock(&dev->iotlb[asid]->pending_lock);
 
-	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb_pending_list, next, temp_node) {
-		TAILQ_REMOVE(&dev->iotlb_pending_list, node, next);
-		vhost_user_iotlb_pool_put(dev, node);
+	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb[asid]->pending_list, next, temp_node) {
+		TAILQ_REMOVE(&dev->iotlb[asid]->pending_list, node, next);
+		vhost_user_iotlb_pool_put(dev, asid, node);
 	}
 
-	rte_rwlock_write_unlock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_unlock(&dev->iotlb[asid]->pending_lock);
 }
 
 bool
-vhost_user_iotlb_pending_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm)
+vhost_user_iotlb_pending_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm)
 {
 	struct vhost_iotlb_entry *node;
 	bool found = false;
 
-	rte_rwlock_read_lock(&dev->iotlb_pending_lock);
+	rte_rwlock_read_lock(&dev->iotlb[asid]->pending_lock);
 
-	TAILQ_FOREACH(node, &dev->iotlb_pending_list, next) {
+	TAILQ_FOREACH(node, &dev->iotlb[asid]->pending_list, next) {
 		if ((node->iova == iova) && (node->perm == perm)) {
 			found = true;
 			break;
 		}
 	}
 
-	rte_rwlock_read_unlock(&dev->iotlb_pending_lock);
+	rte_rwlock_read_unlock(&dev->iotlb[asid]->pending_lock);
 
 	return found;
 }
 
 void
-vhost_user_iotlb_pending_insert(struct virtio_net *dev, uint64_t iova, uint8_t perm)
+vhost_user_iotlb_pending_insert(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm)
 {
 	struct vhost_iotlb_entry *node;
 
-	node = vhost_user_iotlb_pool_get(dev);
+	node = vhost_user_iotlb_pool_get(dev, asid);
 	if (node == NULL) {
 		VHOST_CONFIG_LOG(dev->ifname, DEBUG,
 			"IOTLB pool empty, clear entries for pending insertion");
-		if (!TAILQ_EMPTY(&dev->iotlb_pending_list))
-			vhost_user_iotlb_pending_remove_all(dev);
+		if (!TAILQ_EMPTY(&dev->iotlb[asid]->pending_list))
+			vhost_user_iotlb_pending_remove_all(dev, asid);
 		else
-			vhost_user_iotlb_cache_random_evict(dev);
-		node = vhost_user_iotlb_pool_get(dev);
+			vhost_user_iotlb_cache_random_evict(dev, asid);
+		node = vhost_user_iotlb_pool_get(dev, asid);
 		if (node == NULL) {
 			VHOST_CONFIG_LOG(dev->ifname, ERR,
 				"IOTLB pool still empty, pending insertion failure");
@@ -167,21 +177,22 @@ vhost_user_iotlb_pending_insert(struct virtio_net *dev, uint64_t iova, uint8_t p
 	node->iova = iova;
 	node->perm = perm;
 
-	rte_rwlock_write_lock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_lock(&dev->iotlb[asid]->pending_lock);
 
-	TAILQ_INSERT_TAIL(&dev->iotlb_pending_list, node, next);
+	TAILQ_INSERT_TAIL(&dev->iotlb[asid]->pending_list, node, next);
 
-	rte_rwlock_write_unlock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_unlock(&dev->iotlb[asid]->pending_lock);
 }
 
 void
-vhost_user_iotlb_pending_remove(struct virtio_net *dev, uint64_t iova, uint64_t size, uint8_t perm)
+vhost_user_iotlb_pending_remove(struct virtio_net *dev, int asid,
+				uint64_t iova, uint64_t size, uint8_t perm)
 {
 	struct vhost_iotlb_entry *node, *temp_node;
 
-	rte_rwlock_write_lock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_lock(&dev->iotlb[asid]->pending_lock);
 
-	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb_pending_list, next,
+	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb[asid]->pending_list, next,
 				temp_node) {
 		if (node->iova < iova)
 			continue;
@@ -189,53 +200,53 @@ vhost_user_iotlb_pending_remove(struct virtio_net *dev, uint64_t iova, uint64_t
 			continue;
 		if ((node->perm & perm) != node->perm)
 			continue;
-		TAILQ_REMOVE(&dev->iotlb_pending_list, node, next);
-		vhost_user_iotlb_pool_put(dev, node);
+		TAILQ_REMOVE(&dev->iotlb[asid]->pending_list, node, next);
+		vhost_user_iotlb_pool_put(dev, asid, node);
 	}
 
-	rte_rwlock_write_unlock(&dev->iotlb_pending_lock);
+	rte_rwlock_write_unlock(&dev->iotlb[asid]->pending_lock);
 }
 
 static void
-vhost_user_iotlb_cache_remove_all(struct virtio_net *dev)
+vhost_user_iotlb_cache_remove_all(struct virtio_net *dev, int asid)
 {
 	struct vhost_iotlb_entry *node, *temp_node;
 
 	vhost_user_iotlb_wr_lock_all(dev);
 
-	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb_list, next, temp_node) {
+	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb[asid]->list, next, temp_node) {
 		vhost_user_iotlb_clear_dump(dev, node, NULL, NULL);
 
-		TAILQ_REMOVE(&dev->iotlb_list, node, next);
+		TAILQ_REMOVE(&dev->iotlb[asid]->list, node, next);
 		vhost_user_iotlb_remove_notify(dev, node);
-		vhost_user_iotlb_pool_put(dev, node);
+		vhost_user_iotlb_pool_put(dev, asid, node);
 	}
 
-	dev->iotlb_cache_nr = 0;
+	dev->iotlb[asid]->cache_nr = 0;
 
 	vhost_user_iotlb_wr_unlock_all(dev);
 }
 
 static void
-vhost_user_iotlb_cache_random_evict(struct virtio_net *dev)
+vhost_user_iotlb_cache_random_evict(struct virtio_net *dev, int asid)
 {
 	struct vhost_iotlb_entry *node, *temp_node, *prev_node = NULL;
 	int entry_idx;
 
 	vhost_user_iotlb_wr_lock_all(dev);
 
-	entry_idx = rte_rand() % dev->iotlb_cache_nr;
+	entry_idx = rte_rand() % dev->iotlb[asid]->cache_nr;
 
-	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb_list, next, temp_node) {
+	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb[asid]->list, next, temp_node) {
 		if (!entry_idx) {
 			struct vhost_iotlb_entry *next_node = RTE_TAILQ_NEXT(node, next);
 
 			vhost_user_iotlb_clear_dump(dev, node, prev_node, next_node);
 
-			TAILQ_REMOVE(&dev->iotlb_list, node, next);
+			TAILQ_REMOVE(&dev->iotlb[asid]->list, node, next);
 			vhost_user_iotlb_remove_notify(dev, node);
-			vhost_user_iotlb_pool_put(dev, node);
-			dev->iotlb_cache_nr--;
+			vhost_user_iotlb_pool_put(dev, asid, node);
+			dev->iotlb[asid]->cache_nr--;
 			break;
 		}
 		prev_node = node;
@@ -246,20 +257,20 @@ vhost_user_iotlb_cache_random_evict(struct virtio_net *dev)
 }
 
 void
-vhost_user_iotlb_cache_insert(struct virtio_net *dev, uint64_t iova, uint64_t uaddr,
+vhost_user_iotlb_cache_insert(struct virtio_net *dev, int asid, uint64_t iova, uint64_t uaddr,
 				uint64_t uoffset, uint64_t size, uint64_t page_size, uint8_t perm)
 {
 	struct vhost_iotlb_entry *node, *new_node;
 
-	new_node = vhost_user_iotlb_pool_get(dev);
+	new_node = vhost_user_iotlb_pool_get(dev, asid);
 	if (new_node == NULL) {
 		VHOST_CONFIG_LOG(dev->ifname, DEBUG,
 			"IOTLB pool empty, clear entries for cache insertion");
-		if (!TAILQ_EMPTY(&dev->iotlb_list))
-			vhost_user_iotlb_cache_random_evict(dev);
+		if (!TAILQ_EMPTY(&dev->iotlb[asid]->list))
+			vhost_user_iotlb_cache_random_evict(dev, asid);
 		else
-			vhost_user_iotlb_pending_remove_all(dev);
-		new_node = vhost_user_iotlb_pool_get(dev);
+			vhost_user_iotlb_pending_remove_all(dev, asid);
+		new_node = vhost_user_iotlb_pool_get(dev, asid);
 		if (new_node == NULL) {
 			VHOST_CONFIG_LOG(dev->ifname, ERR,
 				"IOTLB pool still empty, cache insertion failed");
@@ -276,36 +287,36 @@ vhost_user_iotlb_cache_insert(struct virtio_net *dev, uint64_t iova, uint64_t ua
 
 	vhost_user_iotlb_wr_lock_all(dev);
 
-	TAILQ_FOREACH(node, &dev->iotlb_list, next) {
+	TAILQ_FOREACH(node, &dev->iotlb[asid]->list, next) {
 		/*
 		 * Entries must be invalidated before being updated.
 		 * So if iova already in list, assume identical.
 		 */
 		if (node->iova == new_node->iova) {
-			vhost_user_iotlb_pool_put(dev, new_node);
+			vhost_user_iotlb_pool_put(dev, asid, new_node);
 			goto unlock;
 		} else if (node->iova > new_node->iova) {
 			vhost_user_iotlb_set_dump(dev, new_node);
 
 			TAILQ_INSERT_BEFORE(node, new_node, next);
-			dev->iotlb_cache_nr++;
+			dev->iotlb[asid]->cache_nr++;
 			goto unlock;
 		}
 	}
 
 	vhost_user_iotlb_set_dump(dev, new_node);
 
-	TAILQ_INSERT_TAIL(&dev->iotlb_list, new_node, next);
-	dev->iotlb_cache_nr++;
+	TAILQ_INSERT_TAIL(&dev->iotlb[asid]->list, new_node, next);
+	dev->iotlb[asid]->cache_nr++;
 
 unlock:
-	vhost_user_iotlb_pending_remove(dev, iova, size, perm);
+	vhost_user_iotlb_pending_remove(dev, asid, iova, size, perm);
 
 	vhost_user_iotlb_wr_unlock_all(dev);
 }
 
 void
-vhost_user_iotlb_cache_remove(struct virtio_net *dev, uint64_t iova, uint64_t size)
+vhost_user_iotlb_cache_remove(struct virtio_net *dev, int asid, uint64_t iova, uint64_t size)
 {
 	struct vhost_iotlb_entry *node, *temp_node, *prev_node = NULL;
 
@@ -314,7 +325,7 @@ vhost_user_iotlb_cache_remove(struct virtio_net *dev, uint64_t iova, uint64_t si
 
 	vhost_user_iotlb_wr_lock_all(dev);
 
-	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb_list, next, temp_node) {
+	RTE_TAILQ_FOREACH_SAFE(node, &dev->iotlb[asid]->list, next, temp_node) {
 		/* Sorted list */
 		if (unlikely(iova + size < node->iova))
 			break;
@@ -324,10 +335,10 @@ vhost_user_iotlb_cache_remove(struct virtio_net *dev, uint64_t iova, uint64_t si
 
 			vhost_user_iotlb_clear_dump(dev, node, prev_node, next_node);
 
-			TAILQ_REMOVE(&dev->iotlb_list, node, next);
+			TAILQ_REMOVE(&dev->iotlb[asid]->list, node, next);
 			vhost_user_iotlb_remove_notify(dev, node);
-			vhost_user_iotlb_pool_put(dev, node);
-			dev->iotlb_cache_nr--;
+			vhost_user_iotlb_pool_put(dev, asid, node);
+			dev->iotlb[asid]->cache_nr--;
 		} else {
 			prev_node = node;
 		}
@@ -337,7 +348,8 @@ vhost_user_iotlb_cache_remove(struct virtio_net *dev, uint64_t iova, uint64_t si
 }
 
 uint64_t
-vhost_user_iotlb_cache_find(struct virtio_net *dev, uint64_t iova, uint64_t *size, uint8_t perm)
+vhost_user_iotlb_cache_find(struct virtio_net *dev, int asid,
+			    uint64_t iova, uint64_t *size, uint8_t perm)
 {
 	struct vhost_iotlb_entry *node;
 	uint64_t offset, vva = 0, mapped = 0;
@@ -345,7 +357,7 @@ vhost_user_iotlb_cache_find(struct virtio_net *dev, uint64_t iova, uint64_t *siz
 	if (unlikely(!*size))
 		goto out;
 
-	TAILQ_FOREACH(node, &dev->iotlb_list, next) {
+	TAILQ_FOREACH(node, &dev->iotlb[asid]->list, next) {
 		/* List sorted by iova */
 		if (unlikely(iova < node->iova))
 			break;
@@ -378,25 +390,28 @@ vhost_user_iotlb_cache_find(struct virtio_net *dev, uint64_t iova, uint64_t *siz
 }
 
 void
-vhost_user_iotlb_flush_all(struct virtio_net *dev)
+vhost_user_iotlb_flush_all(struct virtio_net *dev, int asid)
 {
-	vhost_user_iotlb_cache_remove_all(dev);
-	vhost_user_iotlb_pending_remove_all(dev);
+	vhost_user_iotlb_cache_remove_all(dev, asid);
+	vhost_user_iotlb_pending_remove_all(dev, asid);
 }
 
-int
-vhost_user_iotlb_init(struct virtio_net *dev)
+static int
+vhost_user_iotlb_init_one(struct virtio_net *dev, int asid)
 {
 	unsigned int i;
 	int socket = 0;
 
-	if (dev->iotlb_pool) {
-		/*
-		 * The cache has already been initialized,
-		 * just drop all cached and pending entries.
-		 */
-		vhost_user_iotlb_flush_all(dev);
-		rte_free(dev->iotlb_pool);
+	if (dev->iotlb[asid] != NULL) {
+		if (dev->iotlb[asid]->pool != NULL) {
+			/*
+			 * The cache has already been initialized,
+			 * just drop all cached and pending entries.
+			 */
+			vhost_user_iotlb_flush_all(dev, asid);
+			rte_free(dev->iotlb[asid]->pool);
+		}
+		rte_free(dev->iotlb[asid]);
 	}
 
 #ifdef RTE_LIBRTE_VHOST_NUMA
@@ -404,31 +419,73 @@ vhost_user_iotlb_init(struct virtio_net *dev)
 		socket = 0;
 #endif
 
-	rte_spinlock_init(&dev->iotlb_free_lock);
-	rte_rwlock_init(&dev->iotlb_pending_lock);
+	dev->iotlb[asid] = rte_malloc_socket("iotlb", sizeof(struct iotlb), 0, socket);
+	if (!dev->iotlb[asid]) {
+		VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to allocate IOTLB");
+		return -1;
+	}
+
+	rte_spinlock_init(&dev->iotlb[asid]->free_lock);
+	rte_rwlock_init(&dev->iotlb[asid]->pending_lock);
 
-	SLIST_INIT(&dev->iotlb_free_list);
-	TAILQ_INIT(&dev->iotlb_list);
-	TAILQ_INIT(&dev->iotlb_pending_list);
+	SLIST_INIT(&dev->iotlb[asid]->free_list);
+	TAILQ_INIT(&dev->iotlb[asid]->list);
+	TAILQ_INIT(&dev->iotlb[asid]->pending_list);
 
 	if (dev->flags & VIRTIO_DEV_SUPPORT_IOMMU) {
-		dev->iotlb_pool = rte_calloc_socket("iotlb", IOTLB_CACHE_SIZE,
+		dev->iotlb[asid]->pool = rte_calloc_socket("iotlb_pool", IOTLB_CACHE_SIZE,
 			sizeof(struct vhost_iotlb_entry), 0, socket);
-		if (!dev->iotlb_pool) {
+		if (!dev->iotlb[asid]->pool) {
 			VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to create IOTLB cache pool");
-			return -1;
+			goto free_iotlb;
 		}
 		for (i = 0; i < IOTLB_CACHE_SIZE; i++)
-			vhost_user_iotlb_pool_put(dev, &dev->iotlb_pool[i]);
+			vhost_user_iotlb_pool_put(dev, asid, &dev->iotlb[asid]->pool[i]);
 	}
 
-	dev->iotlb_cache_nr = 0;
+	dev->iotlb[asid]->cache_nr = 0;
+
+	return 0;
+
+free_iotlb:
+	rte_free(dev->iotlb[asid]);
+	dev->iotlb[asid] = NULL;
+	return -1;
+}
+
+int
+vhost_user_iotlb_init(struct virtio_net *dev)
+{
+	int i;
+
+	for (i = 0; i < IOTLB_MAX_ASID; i++)
+		if (vhost_user_iotlb_init_one(dev, i) < 0)
+			goto fail;
 
 	return 0;
+fail:
+	while (i--) {
+		rte_free(dev->iotlb[i]->pool);
+		dev->iotlb[i]->pool = NULL;
+		rte_free(dev->iotlb[i]);
+		dev->iotlb[i] = NULL;
+	}
+
+	return -1;
 }
 
 void
 vhost_user_iotlb_destroy(struct virtio_net *dev)
 {
-	rte_free(dev->iotlb_pool);
+	int i;
+
+	for (i = 0; i < IOTLB_MAX_ASID; i++) {
+		if (dev->iotlb[i]) {
+			rte_free(dev->iotlb[i]->pool);
+			dev->iotlb[i]->pool = NULL;
+
+			rte_free(dev->iotlb[i]);
+			dev->iotlb[i] = NULL;
+		}
+	}
 }
lib/vhost/iotlb.h +7 -7
index 72232b0dcf08..52963d6c4de0 100644
@@ -57,16 +57,16 @@ vhost_user_iotlb_wr_unlock_all(struct virtio_net *dev)
 		rte_rwlock_write_unlock(&dev->virtqueue[i]->iotlb_lock);
 }
 
-void vhost_user_iotlb_cache_insert(struct virtio_net *dev, uint64_t iova, uint64_t uaddr,
+void vhost_user_iotlb_cache_insert(struct virtio_net *dev, int asid, uint64_t iova, uint64_t uaddr,
 		uint64_t uoffset, uint64_t size, uint64_t page_size, uint8_t perm);
-void vhost_user_iotlb_cache_remove(struct virtio_net *dev, uint64_t iova, uint64_t size);
-uint64_t vhost_user_iotlb_cache_find(struct virtio_net *dev, uint64_t iova,
+void vhost_user_iotlb_cache_remove(struct virtio_net *dev, int asid, uint64_t iova, uint64_t size);
+uint64_t vhost_user_iotlb_cache_find(struct virtio_net *dev, int asid, uint64_t iova,
 					uint64_t *size, uint8_t perm);
-bool vhost_user_iotlb_pending_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm);
-void vhost_user_iotlb_pending_insert(struct virtio_net *dev, uint64_t iova, uint8_t perm);
-void vhost_user_iotlb_pending_remove(struct virtio_net *dev, uint64_t iova,
+bool vhost_user_iotlb_pending_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm);
+void vhost_user_iotlb_pending_insert(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm);
+void vhost_user_iotlb_pending_remove(struct virtio_net *dev, int asid, uint64_t iova,
 						uint64_t size, uint8_t perm);
-void vhost_user_iotlb_flush_all(struct virtio_net *dev);
+void vhost_user_iotlb_flush_all(struct virtio_net *dev, int asid);
 int vhost_user_iotlb_init(struct virtio_net *dev);
 void vhost_user_iotlb_destroy(struct virtio_net *dev);
 
lib/vhost/vduse.c +5 -4
index 58c8f4090b2e..53a201cf3a16 100644
@@ -57,7 +57,7 @@ vduse_iotlb_remove_notify(uint64_t addr, uint64_t offset, uint64_t size)
 }
 
 static int
-vduse_iotlb_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm __rte_unused)
+vduse_iotlb_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm __rte_unused)
 {
 	struct vduse_iotlb_entry entry;
 	uint64_t size, page_size;
@@ -102,7 +102,7 @@ vduse_iotlb_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm __rte_unuse
 	}
 	page_size = (uint64_t)stat.st_blksize;
 
-	vhost_user_iotlb_cache_insert(dev, entry.start, (uint64_t)(uintptr_t)mmap_addr,
+	vhost_user_iotlb_cache_insert(dev, asid, entry.start, (uint64_t)(uintptr_t)mmap_addr,
 		entry.offset, size, page_size, entry.perm);
 
 	ret = 0;
@@ -398,7 +398,8 @@ vduse_device_stop(struct virtio_net *dev)
 	for (i = 0; i < dev->nr_vring; i++)
 		vduse_vring_cleanup(dev, i);
 
-	vhost_user_iotlb_flush_all(dev);
+	for (i = 0; i < IOTLB_MAX_ASID; i++)
+		vhost_user_iotlb_flush_all(dev, i);
 }
 
 static void
@@ -448,7 +449,7 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 
 		VHOST_CONFIG_LOG(dev->ifname, INFO, "\tIOVA range: %" PRIx64 " - %" PRIx64,
 				(uint64_t)req.iova.start, (uint64_t)req.iova.last);
-		vhost_user_iotlb_cache_remove(dev, req.iova.start,
+		vhost_user_iotlb_cache_remove(dev, 0, req.iova.start,
 				req.iova.last - req.iova.start + 1);
 
 		/* Invalidate vring translations if affected by this range */
lib/vhost/vhost.c +8 -8
index 918e50ed43aa..8d93feb9df09 100644
@@ -62,9 +62,9 @@ static const struct vhost_vq_stats_name_off vhost_vq_stat_strings[] = {
 #define VHOST_NB_VQ_STATS RTE_DIM(vhost_vq_stat_strings)
 
 static int
-vhost_iotlb_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm)
+vhost_iotlb_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm)
 {
-	return dev->backend_ops->iotlb_miss(dev, iova, perm);
+	return dev->backend_ops->iotlb_miss(dev, asid, iova, perm);
 }
 
 uint64_t
@@ -78,7 +78,7 @@ __vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
 
 	tmp_size = *size;
 
-	vva = vhost_user_iotlb_cache_find(dev, iova, &tmp_size, perm);
+	vva = vhost_user_iotlb_cache_find(dev, vq->asid, iova, &tmp_size, perm);
 	if (tmp_size == *size) {
 		if (dev->flags & VIRTIO_DEV_STATS_ENABLED)
 			vq->stats.iotlb_hits++;
@@ -90,7 +90,7 @@ __vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
 
 	iova += tmp_size;
 
-	if (!vhost_user_iotlb_pending_miss(dev, iova, perm)) {
+	if (!vhost_user_iotlb_pending_miss(dev, vq->asid, iova, perm)) {
 		/*
 		 * iotlb_lock is read-locked for a full burst,
 		 * but it only protects the iotlb cache.
@@ -100,12 +100,12 @@ __vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
 		 */
 		vhost_user_iotlb_rd_unlock(vq);
 
-		vhost_user_iotlb_pending_insert(dev, iova, perm);
-		if (vhost_iotlb_miss(dev, iova, perm)) {
+		vhost_user_iotlb_pending_insert(dev, vq->asid, iova, perm);
+		if (vhost_iotlb_miss(dev, vq->asid, iova, perm)) {
 			VHOST_DATA_LOG(dev->ifname, ERR,
 				"IOTLB miss req failed for IOVA 0x%" PRIx64,
 				iova);
-			vhost_user_iotlb_pending_remove(dev, iova, 1, perm);
+			vhost_user_iotlb_pending_remove(dev, vq->asid, iova, 1, perm);
 		}
 
 		vhost_user_iotlb_rd_lock(vq);
@@ -113,7 +113,7 @@ __vhost_iova_to_vva(struct virtio_net *dev, struct vhost_virtqueue *vq,
 
 	tmp_size = *size;
 	/* Retry in case of VDUSE, as it is synchronous */
-	vva = vhost_user_iotlb_cache_find(dev, iova, &tmp_size, perm);
+	vva = vhost_user_iotlb_cache_find(dev, vq->asid, iova, &tmp_size, perm);
 	if (tmp_size == *size)
 		return vva;
 
lib/vhost/vhost.h +5 -8
index 1f0568dcb5d4..a9f657e8140d 100644
@@ -85,7 +85,7 @@ struct vhost_virtqueue;
 
 typedef void (*vhost_iotlb_remove_notify)(uint64_t addr, uint64_t off, uint64_t size);
 
-typedef int (*vhost_iotlb_miss_cb)(struct virtio_net *dev, uint64_t iova, uint8_t perm);
+typedef int (*vhost_iotlb_miss_cb)(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm);
 
 typedef int (*vhost_vring_inject_irq_cb)(struct virtio_net *dev, struct vhost_virtqueue *vq);
 /**
@@ -326,6 +326,7 @@ struct __rte_cache_aligned vhost_virtqueue {
 	uint16_t		batch_copy_nb_elems;
 	struct batch_copy_elem	*batch_copy_elems;
 	int			numa_node;
+	int			asid;
 	bool			used_wrap_counter;
 	bool			avail_wrap_counter;
 
@@ -483,6 +484,8 @@ struct inflight_mem_info {
 	uint64_t	size;
 };
 
+#define IOTLB_MAX_ASID 2
+
 /**
  * Device structure contains all configuration information relating
  * to the device.
@@ -505,13 +508,7 @@ struct __rte_cache_aligned virtio_net {
 	int			linearbuf;
 	struct vhost_virtqueue	*virtqueue[VHOST_MAX_VRING];
 
-	rte_rwlock_t	iotlb_pending_lock;
-	struct vhost_iotlb_entry *iotlb_pool;
-	TAILQ_HEAD(, vhost_iotlb_entry) iotlb_list;
-	TAILQ_HEAD(, vhost_iotlb_entry) iotlb_pending_list;
-	int				iotlb_cache_nr;
-	rte_spinlock_t	iotlb_free_lock;
-	SLIST_HEAD(, vhost_iotlb_entry) iotlb_free_list;
+	struct iotlb		*iotlb[IOTLB_MAX_ASID];
 
 	struct inflight_mem_info *inflight_info;
 #define IF_NAME_SZ (PATH_MAX > IFNAMSIZ ? PATH_MAX : IFNAMSIZ)
lib/vhost/vhost_user.c +7 -6
index 0d09dfb041fd..cba3a90a2255 100644
@@ -1552,7 +1552,8 @@ vhost_user_set_mem_table(struct virtio_net **pdev,
 
 		/* Flush IOTLB cache as previous HVAs are now invalid */
 		if (dev->features & (1ULL << VIRTIO_F_IOMMU_PLATFORM))
-			vhost_user_iotlb_flush_all(dev);
+			for (i = 0; i < IOTLB_MAX_ASID; i++)
+				vhost_user_iotlb_flush_all(dev, i);
 
 		free_all_mem_regions(dev);
 		rte_free(dev->mem);
@@ -1852,7 +1853,7 @@ vhost_user_rem_mem_reg(struct virtio_net **pdev,
 			if (dev->async_copy && dev->dma_map_available)
 				async_dma_map_region(dev, current_region, false);
 			if (dev->features & (1ULL << VIRTIO_F_IOMMU_PLATFORM))
-				vhost_user_iotlb_cache_remove(dev,
+				vhost_user_iotlb_cache_remove(dev, 0,
 					current_region->guest_phys_addr,
 					current_region->size);
 			remove_guest_pages(dev, current_region);
@@ -2603,7 +2604,7 @@ vhost_user_get_vring_base(struct virtio_net **pdev,
 	ctx->msg.size = sizeof(ctx->msg.payload.state);
 	ctx->fd_num = 0;
 
-	vhost_user_iotlb_flush_all(dev);
+	vhost_user_iotlb_flush_all(dev, vq->asid);
 
 	rte_rwlock_write_lock(&vq->access_lock);
 	vring_invalidate(dev, vq);
@@ -2978,7 +2979,7 @@ vhost_user_iotlb_msg(struct virtio_net **pdev,
 
 		pg_sz = hua_to_alignment(dev->mem, (void *)(uintptr_t)vva);
 
-		vhost_user_iotlb_cache_insert(dev, imsg->iova, vva, 0, len, pg_sz, imsg->perm);
+		vhost_user_iotlb_cache_insert(dev, 0, imsg->iova, vva, 0, len, pg_sz, imsg->perm);
 
 		for (i = 0; i < dev->nr_vring; i++) {
 			struct vhost_virtqueue *vq = dev->virtqueue[i];
@@ -2995,7 +2996,7 @@ vhost_user_iotlb_msg(struct virtio_net **pdev,
 		}
 		break;
 	case VHOST_IOTLB_INVALIDATE:
-		vhost_user_iotlb_cache_remove(dev, imsg->iova, imsg->size);
+		vhost_user_iotlb_cache_remove(dev, 0, imsg->iova, imsg->size);
 
 		for (i = 0; i < dev->nr_vring; i++) {
 			struct vhost_virtqueue *vq = dev->virtqueue[i];
@@ -3594,7 +3595,7 @@ vhost_user_msg_handler(int vid, int fd)
 }
 
 static int
-vhost_user_iotlb_miss(struct virtio_net *dev, uint64_t iova, uint8_t perm)
+vhost_user_iotlb_miss(struct virtio_net *dev, int asid __rte_unused, uint64_t iova, uint8_t perm)
 {
 	int ret;
 	struct vhu_msg_context ctx = {
Signature · 1 line
2.56.0
#4 lore raw

Re: [PATCH v5 2/9] vhost: introduce ASID support

Quoted 22 lines · Eugenio Pérez

From: Maxime Coquelin <[email protected]>

Set all ASID = 0 as it is the default when not set explicitly.

---
 lib/vhost/iotlb.c      | 233 +++++++++++++++++++++++++----------------
 lib/vhost/iotlb.h      |  14 +--
 lib/vhost/vduse.c      |   9 +-
 lib/vhost/vhost.c      |  16 +--
 lib/vhost/vhost.h      |  13 +--
 lib/vhost/vhost_user.c |  13 +--
 6 files changed, 177 insertions(+), 121 deletions(-)
lib/vhost/iotlb.c +0 -0
index f2c275a7d77e..e5e69d11cb65 100644

lib/vhost/iotlb.c line 14

Quoted 4 lines
lib/vhost/iotlb.c +1 -0
@@ -11,6 +11,16 @@
 #include "iotlb.h"
 #include "vhost.h"
 
+struct iotlb {

I'm changing to struct vhost_iotlb, for consistency with vhost_iotlb entry and also to avoid potential collision in the future.

Quoted 9 lines
lib/vhost/iotlb.c +9 -0
+       rte_rwlock_t                    pending_lock;
+       struct vhost_iotlb_entry        *pool;
+       TAILQ_HEAD(, vhost_iotlb_entry) list;
+       TAILQ_HEAD(, vhost_iotlb_entry) pending_list;
+       int                             cache_nr;
+       rte_spinlock_t                  free_lock;
+       SLIST_HEAD(, vhost_iotlb_entry) free_list;
+};
+
#5 lore raw

PATCH v5 3/9 vhost: add VDUSE API version negotiation

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

As preliminary step to support new VDUSE API version introducing ASID support, this patch adds API version negotiation to keep compatibility with older kernels.

---
 lib/vhost/vduse.c | 14 ++++++++++++--
 lib/vhost/vhost.h |  1 +
 2 files changed, 13 insertions(+), 2 deletions(-)
lib/vhost/vduse.c +12 -2
index 53a201cf3a16..db267e2c42f3 100644
@@ -25,7 +25,7 @@
 #include "vhost.h"
 #include "virtio_net_ctrl.h"
 
-#define VHOST_VDUSE_API_VERSION 0
+#define VHOST_VDUSE_API_VERSION 0ULL
 #define VDUSE_CTRL_PATH "/dev/vduse/control"
 
 struct vduse {
@@ -702,7 +702,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 	uint32_t i, max_queue_pairs, total_queues;
 	struct virtio_net *dev;
 	struct virtio_net_config vnet_config = {{ 0 }};
-	uint64_t ver = VHOST_VDUSE_API_VERSION;
+	uint64_t ver;
 	uint64_t features;
 	const char *name = path + strlen("/dev/vduse/");
 	bool reconnect = false;
@@ -722,6 +722,15 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 		return -1;
 	}
 
+	if (ioctl(control_fd, VDUSE_GET_API_VERSION, &ver)) {
+		VHOST_CONFIG_LOG(name, ERR, "Failed to get API version: %s", strerror(errno));
+		ret = -1;
+		goto out_ctrl_close;
+	}
+
+	ver = RTE_MIN(ver, VHOST_VDUSE_API_VERSION);
+	VHOST_CONFIG_LOG(name, INFO, "Using VDUSE API version %" PRIu64 "", ver);
+
 	if (ioctl(control_fd, VDUSE_SET_API_VERSION, &ver)) {
 		VHOST_CONFIG_LOG(name, ERR, "Failed to set API version: %" PRIu64 ": %s",
 				ver, strerror(errno));
@@ -822,6 +831,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 	strlcpy(dev->ifname, path, sizeof(dev->ifname));
 	dev->vduse_ctrl_fd = control_fd;
 	dev->vduse_dev_fd = dev_fd;
+	dev->vduse_api_ver = ver;
 
 	ret = vduse_reconnect_log_map(dev, !reconnect);
 	if (ret < 0)
lib/vhost/vhost.h +1 -0
index a9f657e8140d..8e58fedb3d0e 100644
@@ -533,6 +533,7 @@ struct __rte_cache_aligned virtio_net {
 	int			postcopy_listening;
 	int			vduse_ctrl_fd;
 	int			vduse_dev_fd;
+	uint64_t		vduse_api_ver;
 
 	struct vhost_virtqueue	*cvq;
 
Signature · 1 line
2.56.0
#6 lore raw

PATCH v5 4/9 vhost: add virtqueues groups support to VDUSE

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

VDUSE API version 1 introduces the notion of virtqueue groups, which once supported, enables the support of multiple addresses spaces.

For VDUSE networking devices, we need two groups, one for the datapath queues, and one for the control queue.

---
 lib/vhost/vduse.c | 54 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 2 deletions(-)
lib/vhost/vduse.c +52 -2
index db267e2c42f3..80b9be89b6b4 100644
@@ -38,12 +38,24 @@ static const char * const vduse_reqs_str[] = {
 	"VDUSE_GET_VQ_STATE",
 	"VDUSE_SET_STATUS",
 	"VDUSE_UPDATE_IOTLB",
+	"VDUSE_SET_VQ_GROUP_ASID",
 };
 
 #define vduse_req_id_to_str(id) \
 	(id < RTE_DIM(vduse_reqs_str) ? \
 	vduse_reqs_str[id] : "Unknown")
 
+static uint64_t vduse_vq_to_group(struct virtio_net *dev, struct vhost_virtqueue *vq)
+{
+	if (dev->vduse_api_ver < 1)
+		return 0;
+
+	if (vq == dev->cvq)
+		return 1;
+
+	return 0;
+}
+
 static int
 vduse_inject_irq(struct virtio_net *dev, struct vhost_virtqueue *vq)
 {
@@ -271,6 +283,7 @@ vduse_vring_cleanup(struct virtio_net *dev, unsigned int index)
 	vq->size = 0;
 	vq->last_used_idx = 0;
 	vq->last_avail_idx = 0;
+	vq->asid = 0;
 }
 
 /*
@@ -472,6 +485,37 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 		resp.result = VDUSE_REQ_RESULT_OK;
 		break;
 	}
+	case VDUSE_SET_VQ_GROUP_ASID: {
+		uint32_t i;
+
+		if (dev->vduse_api_ver < 1) {
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		if (req.vq_group_asid.asid >= RTE_DIM(dev->iotlb)) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"Invalid ASID %u for group %u",
+				req.vq_group_asid.asid, req.vq_group_asid.group);
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		VHOST_CONFIG_LOG(dev->ifname, INFO, "\tAssigning ASID %d to group %d",
+				req.vq_group_asid.asid, req.vq_group_asid.group);
+
+		for (i = 0; i < dev->nr_vring; i++) {
+			vq = dev->virtqueue[i];
+
+			if (vduse_vq_to_group(dev, vq) == req.vq_group_asid.group) {
+				vq->asid = req.vq_group_asid.asid;
+				VHOST_CONFIG_LOG(dev->ifname, INFO, "\t\tVQ %d gets ASID %d",
+						i, req.vq_group_asid.asid);
+			}
+		}
+		resp.result = VDUSE_REQ_RESULT_OK;
+		break;
+	}
 	default:
 		resp.result = VDUSE_REQ_RESULT_FAILED;
 		break;
@@ -782,6 +826,10 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 		dev_config->features = features;
 		dev_config->vq_num = total_queues;
 		dev_config->vq_align = rte_mem_page_size();
+		if (ver >= 1) {
+			dev_config->ngroups = 2;
+			dev_config->nas = 2;
+		}
 		dev_config->config_size = sizeof(struct virtio_net_config);
 		memcpy(dev_config->config, &vnet_config, sizeof(vnet_config));
 
@@ -870,11 +918,15 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 		vq = dev->virtqueue[i];
 		vq->reconnect_log = &dev->reconnect_log->vring[i];
 
+		if (i == max_queue_pairs * 2)
+			dev->cvq = vq;
+
 		if (reconnect)
 			continue;
 
 		vq_cfg.index = i;
 		vq_cfg.max_size = 1024;
+		vq_cfg.group = vduse_vq_to_group(dev, vq);
 
 		ret = ioctl(dev->vduse_dev_fd, VDUSE_VQ_SETUP, &vq_cfg);
 		if (ret) {
@@ -883,8 +935,6 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 		}
 	}
 
-	dev->cvq = dev->virtqueue[max_queue_pairs * 2];
-
 	ret = fdset_add(vduse.fdset, dev->vduse_dev_fd, vduse_events_handler, NULL, dev);
 	if (ret) {
 		VHOST_CONFIG_LOG(name, ERR, "Failed to add fd %d to vduse fdset",
Signature · 1 line
2.56.0
#7 lore raw

PATCH v5 5/9 vhost: add ASID support to VDUSE IOTLB operations

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

Make use of the newly introduced address space ID when calling Vhost IOTLB API.

---
v5: Merge vring_invalidate call conflict to invalidate vrings.

---
 lib/vhost/vduse.c | 51 ++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 39 insertions(+), 12 deletions(-)
lib/vhost/vduse.c +39 -12
index 80b9be89b6b4..34ed6fce88ee 100644
@@ -71,7 +71,7 @@ vduse_iotlb_remove_notify(uint64_t addr, uint64_t offset, uint64_t size)
 static int
 vduse_iotlb_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm __rte_unused)
 {
-	struct vduse_iotlb_entry entry;
+	struct vduse_iotlb_entry_v2 entry = {};
 	uint64_t size, page_size;
 	struct stat stat;
 	void *mmap_addr;
@@ -79,8 +79,13 @@ vduse_iotlb_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm _
 
 	entry.start = iova;
 	entry.last = iova + 1;
+	entry.asid = asid;
 
-	ret = ioctl(dev->vduse_dev_fd, VDUSE_IOTLB_GET_FD, &entry);
+	if (dev->vduse_api_ver < 1) {
+		ret = ioctl(dev->vduse_dev_fd, VDUSE_IOTLB_GET_FD, &entry);
+	} else {
+		ret = ioctl(dev->vduse_dev_fd, VDUSE_IOTLB_GET_FD2, &entry);
+	}
 	if (ret < 0) {
 		VHOST_CONFIG_LOG(dev->ifname, ERR, "Failed to get IOTLB entry for 0x%" PRIx64,
 				iova);
@@ -90,6 +95,7 @@ vduse_iotlb_miss(struct virtio_net *dev, int asid, uint64_t iova, uint8_t perm _
 	fd = ret;
 
 	VHOST_CONFIG_LOG(dev->ifname, DEBUG, "New IOTLB entry:");
+	VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\tASID: %d", entry.asid);
 	VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\tIOVA: %" PRIx64 " - %" PRIx64,
 			(uint64_t)entry.start, (uint64_t)entry.last);
 	VHOST_CONFIG_LOG(dev->ifname, DEBUG, "\toffset: %" PRIx64, (uint64_t)entry.offset);
@@ -458,27 +464,48 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 		break;
 	case VDUSE_UPDATE_IOTLB: {
 		struct vhost_iotlb_msg imsg;
+		uint64_t start, last;
+		uint32_t asid;
 		uint32_t i;
 
-		VHOST_CONFIG_LOG(dev->ifname, INFO, "\tIOVA range: %" PRIx64 " - %" PRIx64,
-				(uint64_t)req.iova.start, (uint64_t)req.iova.last);
-		vhost_user_iotlb_cache_remove(dev, 0, req.iova.start,
-				req.iova.last - req.iova.start + 1);
+		if (dev->vduse_api_ver < 1) {
+			start = req.iova.start;
+			last = req.iova.last;
+			asid = 0;
+		} else {
+			start = req.iova_v2.start;
+			last = req.iova_v2.last;
+			asid = req.iova_v2.asid;
+		}
+
+		if (asid >= RTE_DIM(dev->iotlb)) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"Invalid ASID %u in IOTLB update", asid);
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		VHOST_CONFIG_LOG(dev->ifname, INFO, "\t(ASID %d) IOVA range: %" PRIx64 " - %" PRIx64,
+			asid, start, last);
+		vhost_user_iotlb_cache_remove(dev, asid, start, last - start + 1);
 
 		/* Invalidate vring translations if affected by this range */
-		imsg.iova = req.iova.start;
-		imsg.size = req.iova.last - req.iova.start + 1;
+		imsg.iova = start;
+		imsg.size = last - start + 1;
 
 		for (i = 0; i < dev->nr_vring; i++) {
-			vq = dev->virtqueue[i];
+		        vq = dev->virtqueue[i];
 
 			if (!vq)
 				continue;
 
+			if ((uint32_t)vq->asid != asid)
+				continue;
+
 			if (is_vring_iotlb(dev, vq, &imsg)) {
-				rte_rwlock_write_lock(&vq->access_lock);
-				vring_invalidate(dev, vq);
-				rte_rwlock_write_unlock(&vq->access_lock);
+			        rte_rwlock_write_lock(&vq->access_lock);
+			        vring_invalidate(dev, vq);
+			        rte_rwlock_write_unlock(&vq->access_lock);
 			}
 		}
 
Signature · 1 line
2.56.0
#8 lore raw

PATCH v5 6/9 vhost: claim VDUSE support for API version 1

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

---
 lib/vhost/vduse.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
lib/vhost/vduse.c +1 -1
index 34ed6fce88ee..daa41ee42761 100644
@@ -25,7 +25,7 @@
 #include "vhost.h"
 #include "virtio_net_ctrl.h"
 
-#define VHOST_VDUSE_API_VERSION 0ULL
+#define VHOST_VDUSE_API_VERSION 1ULL
 #define VDUSE_CTRL_PATH "/dev/vduse/control"
 
 struct vduse {
Signature · 1 line
2.56.0
#9 lore raw

PATCH v5 7/9 vhost: add net status feature to VDUSE

In reply to Eugenio Pérez

From: Maxime Coquelin <[email protected]>

Enable the VIRTIO_NET_F_STATUS feature for VDUSE devices. This allows the device to report link status (e.g., VIRTIO_NET_S_LINK_UP). It also allows the device to signal the driver that it needs to send gratuitous ARP with VIRTIO_NET_S_ANNOUNCE.

---
 lib/vhost/vduse.c | 1 +
 lib/vhost/vduse.h | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)
lib/vhost/vduse.c +1 -0
index daa41ee42761..02ea64ce0cbb 100644
@@ -844,6 +844,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 			goto out_ctrl_close;
 		}
 
+		vnet_config.status = VIRTIO_NET_S_LINK_UP;
 		vnet_config.max_virtqueue_pairs = max_queue_pairs;
 		memset(dev_config, 0, sizeof(struct vduse_dev_config));
 
lib/vhost/vduse.h +2 -1
index b2515bb9df76..d697f85be5cc 100644
@@ -7,7 +7,8 @@
 
 #include "vhost.h"
 
-#define VDUSE_NET_SUPPORTED_FEATURES VIRTIO_NET_SUPPORTED_FEATURES
+#define VDUSE_NET_SUPPORTED_FEATURES (VIRTIO_NET_SUPPORTED_FEATURES | \
+		(1ULL << VIRTIO_NET_F_STATUS))
 
 int vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf);
 int vduse_device_destroy(const char *path);
Signature · 1 line
2.56.0
#10 lore raw

PATCH v5 8/9 vhost: Support VDUSE QUEUE_READY feature

In reply to Eugenio Pérez

Add support for the VDUSE_F_QUEUE_READY feature.

In VDUSE, the dataplane is enabled only after control virtqueue so the device is fully configured in the destination of a live migration before the dataplane starts. This message signals the VDUSE device when the dataplane queues should be enabled.

---
 lib/vhost/vduse.c | 89 +++++++++++++++++++++++++++++++++++++++++++++--
 lib/vhost/vhost.h |  1 +
 2 files changed, 88 insertions(+), 2 deletions(-)
lib/vhost/vduse.c +87 -2
index 02ea64ce0cbb..784f103953dd 100644
@@ -39,12 +39,15 @@ static const char * const vduse_reqs_str[] = {
 	"VDUSE_SET_STATUS",
 	"VDUSE_UPDATE_IOTLB",
 	"VDUSE_SET_VQ_GROUP_ASID",
+	"VDUSE_SET_VQ_READY",
 };
 
 #define vduse_req_id_to_str(id) \
 	(id < RTE_DIM(vduse_reqs_str) ? \
 	vduse_reqs_str[id] : "Unknown")
 
+static const uint64_t supported_vduse_features = RTE_BIT64(VDUSE_F_QUEUE_READY);
+
 static uint64_t vduse_vq_to_group(struct virtio_net *dev, struct vhost_virtqueue *vq)
 {
 	if (dev->vduse_api_ver < 1)
@@ -543,6 +546,54 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 		resp.result = VDUSE_REQ_RESULT_OK;
 		break;
 	}
+	case VDUSE_SET_VQ_READY: {
+		uint32_t i;
+
+		if (!(dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK)) {
+			/*
+			 * dev->notify_ops is NULL if !S_DRIVER_OK,
+			 * vduse_device_start will check the queue readiness.
+			 */
+			resp.result = VDUSE_REQ_RESULT_OK;
+			break;
+		}
+		if (!(dev->vduse_features & RTE_BIT64(VDUSE_F_QUEUE_READY))) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"Unexpected ready message with no ready feature acked");
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		i = req.vq_ready.num;
+		if (i >= dev->nr_vring) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+					"Invalid virtqueue index: %u", i);
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		vq = dev->virtqueue[i];
+		if (dev->notify_ops == NULL || dev->notify_ops->vring_state_changed == NULL) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+					 "No ops->vring_state_changed");
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		ret = dev->notify_ops->vring_state_changed(dev->vid, i,
+				req.vq_ready.ready);
+		VHOST_CONFIG_LOG(dev->ifname, INFO,
+				"\t\t VQ %d gets ready %d ok %d", i,
+				req.vq_ready.ready, ret);
+		if (ret != 0) {
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+
+		vq->enabled = req.vq_ready.ready;
+		resp.result = VDUSE_REQ_RESULT_OK;
+		break;
+	}
 	default:
 		resp.result = VDUSE_REQ_RESULT_FAILED;
 		break;
@@ -560,7 +611,8 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 	if ((old_status ^ dev->status) & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
 		if (dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK) {
 			/* Poll virtqueues ready states before starting device */
-			ret = vduse_wait_for_virtqueues_ready(dev);
+			ret = dev->vduse_features & RTE_BIT64(VDUSE_F_QUEUE_READY) ? 0
+				: vduse_wait_for_virtqueues_ready(dev);
 			if (ret < 0) {
 				VHOST_CONFIG_LOG(dev->ifname, ERR,
 					"Failed to wait for virtqueues ready, aborting device start");
@@ -766,6 +818,26 @@ vduse_reconnect_start_device(struct virtio_net *dev)
 	return ret;
 }
 
+/* If some error occurs just continue as if the kernel exposed no features */
+static uint64_t
+vduse_device_get_vduse_features(int control_fd, const char *log_name)
+{
+	uint64_t vduse_kernel_features;
+	int ret;
+
+	ret = ioctl(control_fd, VDUSE_GET_FEATURES, &vduse_kernel_features);
+	if (ret < 0) {
+		VHOST_CONFIG_LOG(log_name, INFO,
+			"Failed to get kernel VDUSE features, assuming not supported: %d(%s)",
+			errno, strerror(errno));
+		return 0;
+	}
+
+	VHOST_CONFIG_LOG(log_name, DEBUG, "Setting vhost kernel features: %"PRIx64,
+			vduse_kernel_features & supported_vduse_features);
+	return vduse_kernel_features & supported_vduse_features;
+}
+
 int
 vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool linearbuf)
 {
@@ -774,7 +846,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 	struct virtio_net *dev;
 	struct virtio_net_config vnet_config = {{ 0 }};
 	uint64_t ver;
-	uint64_t features;
+	uint64_t features, vduse_features = 0;
 	const char *name = path + strlen("/dev/vduse/");
 	bool reconnect = false;
 
@@ -858,6 +930,18 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 			dev_config->ngroups = 2;
 			dev_config->nas = 2;
 		}
+
+		vduse_features = vduse_device_get_vduse_features(control_fd, name);
+		if (vduse_features) {
+			ret = ioctl(control_fd, VDUSE_SET_FEATURES, &vduse_features);
+			if (ret < 0) {
+				VHOST_CONFIG_LOG(name, ERR, "Failed to set VDUSE features: %s",
+						strerror(errno));
+				free(dev_config);
+				goto out_ctrl_close;
+			}
+		}
+
 		dev_config->config_size = sizeof(struct virtio_net_config);
 		memcpy(dev_config->config, &vnet_config, sizeof(vnet_config));
 
@@ -908,6 +992,7 @@ vduse_device_create(const char *path, bool compliant_ol_flags, bool extbuf, bool
 	dev->vduse_ctrl_fd = control_fd;
 	dev->vduse_dev_fd = dev_fd;
 	dev->vduse_api_ver = ver;
+	dev->vduse_features = vduse_features;
 
 	ret = vduse_reconnect_log_map(dev, !reconnect);
 	if (ret < 0)
lib/vhost/vhost.h +1 -0
index 8e58fedb3d0e..9cdc2bb58d4b 100644
@@ -534,6 +534,7 @@ struct __rte_cache_aligned virtio_net {
 	int			vduse_ctrl_fd;
 	int			vduse_dev_fd;
 	uint64_t		vduse_api_ver;
+	uint64_t		vduse_features;
 
 	struct vhost_virtqueue	*cvq;
 
Signature · 1 line
2.56.0
#11 lore raw

PATCH v5 9/9 vhost: Support vduse suspend feature

In reply to Eugenio Pérez

Add support for the VDUSE_F_SUSPEND feature.

The suspend feature allows the driver to stop the device from processing the virtqueues. This ensures that the virtqueue state can be fetched reliably in a live migration.

---
 lib/vhost/vduse.c | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 lib/vhost/vhost.h |  1 +
 2 files changed, 45 insertions(+), 1 deletion(-)
lib/vhost/vduse.c +44 -1
index 784f103953dd..8ba3ec34a958 100644
@@ -40,13 +40,15 @@ static const char * const vduse_reqs_str[] = {
 	"VDUSE_UPDATE_IOTLB",
 	"VDUSE_SET_VQ_GROUP_ASID",
 	"VDUSE_SET_VQ_READY",
+	"VDUSE_SUSPEND",
 };
 
 #define vduse_req_id_to_str(id) \
 	(id < RTE_DIM(vduse_reqs_str) ? \
 	vduse_reqs_str[id] : "Unknown")
 
-static const uint64_t supported_vduse_features = RTE_BIT64(VDUSE_F_QUEUE_READY);
+static const uint64_t supported_vduse_features =
+	RTE_BIT64(VDUSE_F_QUEUE_READY) | RTE_BIT64(VDUSE_F_SUSPEND);
 
 static uint64_t vduse_vq_to_group(struct virtio_net *dev, struct vhost_virtqueue *vq)
 {
@@ -416,6 +418,7 @@ vduse_device_stop(struct virtio_net *dev)
 	vhost_destroy_device_notify(dev);
 
 	dev->flags &= ~VIRTIO_DEV_READY;
+	dev->vduse_suspended = false;
 
 	for (i = 0; i < dev->nr_vring; i++)
 		vduse_vring_cleanup(dev, i);
@@ -563,6 +566,12 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 			resp.result = VDUSE_REQ_RESULT_FAILED;
 			break;
 		}
+		if (dev->vduse_suspended) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"SET_VQ_READY received on suspended device");
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
 
 		i = req.vq_ready.num;
 		if (i >= dev->nr_vring) {
@@ -594,11 +603,45 @@ vduse_events_handler(int fd, void *arg, int *close __rte_unused)
 		resp.result = VDUSE_REQ_RESULT_OK;
 		break;
 	}
+	case VDUSE_SUSPEND: {
+		uint32_t i;
+
+		if (!(dev->vduse_features & RTE_BIT64(VDUSE_F_SUSPEND))) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"Unnegotiated suspend message");
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+		if (!(dev->status & VIRTIO_DEVICE_STATUS_DRIVER_OK)) {
+			VHOST_CONFIG_LOG(dev->ifname, ERR,
+				"Unexpected suspend message with no DRIVER_OK");
+			resp.result = VDUSE_REQ_RESULT_FAILED;
+			break;
+		}
+		for (i = 0; dev->notify_ops != NULL &&
+			    dev->notify_ops->vring_state_changed != NULL &&
+			    i < dev->nr_vring; i++) {
+			if (dev->virtqueue[i] == dev->cvq)
+				continue;
+
+			ret = dev->notify_ops->vring_state_changed(dev->vid, i, false);
+			if (ret) {
+				VHOST_CONFIG_LOG(dev->ifname, ERR,
+					"Failed to disable VQ %u for suspend", i);
+				resp.result = VDUSE_REQ_RESULT_FAILED;
+				goto out;
+			}
+		}
+		dev->vduse_suspended = true;
+		resp.result = VDUSE_REQ_RESULT_OK;
+		break;
+	}
 	default:
 		resp.result = VDUSE_REQ_RESULT_FAILED;
 		break;
 	}
 
+out:
 	resp.request_id = req.request_id;
 
 	ret = write(dev->vduse_dev_fd, &resp, sizeof(resp));
lib/vhost/vhost.h +1 -0
index 9cdc2bb58d4b..bc6370623812 100644
@@ -535,6 +535,7 @@ struct __rte_cache_aligned virtio_net {
 	int			vduse_dev_fd;
 	uint64_t		vduse_api_ver;
 	uint64_t		vduse_features;
+	bool			vduse_suspended;
 
 	struct vhost_virtqueue	*cvq;
 
Signature · 1 line
2.56.0
#12 lore raw
In reply to Eugenio Pérez
Quoted 93 lines · Eugenio Pérez

This series introduces features to the VDUSE (vDPA Device in Userspace) driver to support Live Migration.

Currently, DPDK does not support VDUSE devices live migration because the driver lacks a mechanism to suspend the device and quiesce the rings to initiate the switchover. This series implements the suspend operation to address this limitation.

Furthermore, enabling Live Migration for devices with control virtqueue requires two additional features. Both of them are included in this series.

* Address Spaces ID (ASID) support: This allows QEMU to isolate and intercept the device's CVQ. By doing so, QEMU is able to migrate the device status transparently, without requiring the device to support state save and restore.
* QUEUE_READY: This allows QEMU to control when the dataplane virtqueues are enabled. This ensures the dataplane is started after the device configuration has been fully restored via the CVQ.

It also enables the VIRTIO_NET_F_STATUS feature. This allows the device to signal the driver that it needs to send gratuitous ARP with VIRTIO_NET_S_ANNOUNCE, reducing the Live Migration downtime.

v5:
* Update headers to 7.3
* Rebase on top of main, merge conflicts.

v4:
* Rebase on top of origin/main, which already includes the __counted_by error fix.
* Add VDUSE_SUSPEND to lib/vhost/vduse.c:vduse_reqs_str (Maxime).

v3:
* Include uapi/linux/stddef.h and revert the -D__counted_by from v2.

v2:
* Add -D__counted_by(X)= if the compiler does not support it. It is pulled by the kernel headers.

PATCH v1:
* Move to VDUSE_SET_FEATURES ioctl instead of config space field.
* Add a few bounds control.
* Update kernel headers to v7.3-rc3.

RFC v4:
* Sync headers with Linux's latest existing and proposed UAPI. Both files constants and new ioctl VDUSE_SET_FEATURES.
* Check for more error conditions and clarified some error messages in ready message processing.
* Add relevant release notes.
* Fix cosmetic whitespaces & checkpath errors.
* Fix error path of vhost_user_iotlb_init and vhost_user_iotlb_init_one.
* Fix commits author.

RFC v3:
* Replace incorrect '%lx' DEBUG print format specifier with PRIx64

RFC v2:
* Following latest comments on kernel series about VDUSE features, not checking API version but only check if VDUSE_GET_FEATURES success.
* Move the start and last declarations in the braces as gcc 8 does not like them interleaved with statements. Actually, I think the move was a mistake in the first version.
https://mails.dpdk.org/archives/test-report/2026-February/958175.html

Eugenio Pérez (3):
uapi: import VDUSE and VFIO header from v7.3 kernel
vhost: Support VDUSE QUEUE_READY feature
vhost: Support vduse suspend feature

Maxime Coquelin (6):
vhost: introduce ASID support
vhost: add VDUSE API version negotiation
vhost: add virtqueues groups support to VDUSE
vhost: add ASID support to VDUSE IOTLB operations
vhost: claim VDUSE support for API version 1
vhost: add net status feature to VDUSE

 kernel/linux/uapi/linux/iommufd.h | 105 +++++++++++-
 kernel/linux/uapi/linux/stddef.h  |   4 +
 kernel/linux/uapi/linux/vduse.h   | 113 ++++++++++++-
 kernel/linux/uapi/linux/vfio.h    |  51 +++++-
 kernel/linux/uapi/version         |   2 +-
 lib/vhost/iotlb.c                 | 233 +++++++++++++++++----------
 lib/vhost/iotlb.h                 |  14 +-
 lib/vhost/vduse.c                 | 259 +++++++++++++++++++++++++++---
 lib/vhost/vduse.h                 |   3 +-
 lib/vhost/vhost.c                 |  16 +-
 lib/vhost/vhost.h                 |  16 +-
 lib/vhost/vhost_user.c            |  13 +-
 12 files changed, 676 insertions(+), 153 deletions(-)
Quoted 3 lines

--
2.56.0

applied to next-virtio/for-next-net.

Thanks!
Maxime