[PATCH 3/3] tools/virtio: add --features-array switch

"rom.wang" <[email protected]> Tue, 4 Aug 2026 16:01:25 +0800
Newsgroups dev.linux.lists.virtualization,org.kernel.vger.kvm,org.kernel.vger.linux-kernel,org.kernel.vger.netdev
Message-ID <[email protected]>
From: Yufeng Wang <[email protected]>

Add a --features-array command-line switch that exercises the array-variant
feature ioctls, VHOST_SET_FEATURES_ARRAY and VHOST_GET_FEATURES_ARRAY,
instead of the legacy VHOST_SET_FEATURES.  The legacy path is used when the
switch is omitted, so both code paths stay covered.

SET: features are programmed via VHOST_SET_FEATURES_ARRAY.

GET: VHOST_GET_FEATURES_ARRAY is issued twice with a count above
VIRTIO_FEATURES_U64S, once into a zero-initialized buffer and once into a
sentinel-filled one; the two reads must match.  This verifies the kernel
fills the prefix and clear_user()s the trailing entries, and stays correct
as the kernel's feature-word count grows.

Signed-off-by: Yufeng Wang <[email protected]>
---
 tools/virtio/vhost_net_test.c | 70 +++++++++++++++++++++++++++++++++--
 1 file changed, 66 insertions(+), 4 deletions(-)

diff --git a/tools/virtio/vhost_net_test.c b/tools/virtio/vhost_net_test.c
index 389d99a6d7c7..77191e60a7a3 100644
--- a/tools/virtio/vhost_net_test.c
+++ b/tools/virtio/vhost_net_test.c
@@ -26,6 +26,12 @@
 #define TEST_PTYPE	ETH_P_LOOPBACK
 #define DESC_NUM	256
 
+/* Number of u64 entries that the kernel uses to store all currently-defined
+ * virtio features. Must stay in sync with the kernel's VIRTIO_FEATURES_U64S. */
+#define FEATURES_U64S	2
+/* Above VIRTIO_FEATURES_U64S so trailing entries hit the clear_user() tail. */
+#define GET_FEATURES_ARRAY_COUNT	8
+
 /* Used by implementation of kmalloc() in tools/virtio/linux/kernel.h */
 void *__kmalloc_fake, *__kfree_ignore_start, *__kfree_ignore_end;
 
@@ -205,8 +211,10 @@ static void vq_info_add(struct vdev_info *dev, int idx, int num, int fd)
 	assert(!r);
 }
 
-static void vdev_info_init(struct vdev_info *dev, unsigned long long features)
+static void vdev_info_init(struct vdev_info *dev, unsigned long long features,
+			   bool features_array)
 {
+	struct vhost_features_array *fa;
 	struct ether_header *eh;
 	int i, r;
 
@@ -248,8 +256,20 @@ static void vdev_info_init(struct vdev_info *dev, unsigned long long features)
 	r = ioctl(dev->control, VHOST_SET_MEM_TABLE, dev->mem);
 	assert(r >= 0);
 
-	r = ioctl(dev->control, VHOST_SET_FEATURES, &features);
-	assert(r >= 0);
+	if (features_array) {
+		fa = calloc(1, struct_size(fa, features, FEATURES_U64S));
+		assert(fa);
+		fa->count = FEATURES_U64S;
+		fa->features[0] = features;
+
+		r = ioctl(dev->control, VHOST_SET_FEATURES_ARRAY, fa);
+		assert(r >= 0);
+
+		free(fa);
+	} else {
+		r = ioctl(dev->control, VHOST_SET_FEATURES, &features);
+		assert(r >= 0);
+	}
 
 	dev->nvqs = 2;
 }
@@ -455,6 +475,10 @@ static const struct option longopts[] = {
 		.val = 'b',
 		.has_arg = required_argument,
 	},
+	{
+		.name = "features-array",
+		.val = 'F',
+	},
 	{
 	}
 };
@@ -467,11 +491,43 @@ static void help(int status)
 		" [--no-virtio-1]"
 		" [--delayed-interrupt]"
 		" [--buf-num]"
+		" [--features-array]"
 		"\n");
 
 	exit(status);
 }
 
+static void verify_get_features_array(struct vdev_info *dev)
+{
+	struct vhost_features_array *clean, *dirty;
+	int r, i;
+
+	clean = calloc(1, struct_size(clean, features, GET_FEATURES_ARRAY_COUNT));
+	assert(clean);
+	clean->count = GET_FEATURES_ARRAY_COUNT;
+	r = ioctl(dev->control, VHOST_GET_FEATURES_ARRAY, clean);
+	assert(r >= 0);
+
+	/* Sentinel-fill so the kernel's clear_user() of the tail is visible. */
+	dirty = calloc(1, struct_size(dirty, features, GET_FEATURES_ARRAY_COUNT));
+	assert(dirty);
+	dirty->count = GET_FEATURES_ARRAY_COUNT;
+	memset(dirty->features, 0xa5, GET_FEATURES_ARRAY_COUNT * sizeof(dirty->features[0]));
+	r = ioctl(dev->control, VHOST_GET_FEATURES_ARRAY, dirty);
+	assert(r >= 0);
+
+	/* Must match; a wrong clear_user() start would leave them unequal. */
+	for (i = 0; i < GET_FEATURES_ARRAY_COUNT; i++)
+		assert(clean->features[i] == dirty->features[i]);
+
+	printf("GET_FEATURES_ARRAY: 0x%llx 0x%llx (tail zeroed) OK\n",
+	       (unsigned long long)clean->features[0],
+	       (unsigned long long)clean->features[1]);
+
+	free(clean);
+	free(dirty);
+}
+
 int main(int argc, char **argv)
 {
 	unsigned long long features = (1ULL << VIRTIO_RING_F_INDIRECT_DESC) |
@@ -480,6 +536,7 @@ int main(int argc, char **argv)
 	long nbufs = 0x100000;
 	struct vdev_info dev;
 	bool delayed = false;
+	bool features_array = false;
 	int o, fd;
 
 	for (;;) {
@@ -503,6 +560,9 @@ int main(int argc, char **argv)
 		case 'D':
 			delayed = true;
 			break;
+		case 'F':
+			features_array = true;
+			break;
 		case 'n':
 			nbufs = strtol(optarg, NULL, 10);
 			assert(nbufs > 0);
@@ -520,7 +580,9 @@ int main(int argc, char **argv)
 	fd = tun_alloc(&dev, tun_name);
 	assert(fd >= 0);
 
-	vdev_info_init(&dev, features);
+	vdev_info_init(&dev, features, features_array);
+	if (features_array)
+		verify_get_features_array(&dev);
 	vq_info_add(&dev, 0, DESC_NUM, fd);
 	vq_info_add(&dev, 1, DESC_NUM, fd);
 	vdev_create_socket(&dev, tun_name);
-- 
2.34.1