* [PATCH 1/3] virtio: Pass iovec arrays as separate parameters to vu_queue_pop()
2026-03-13 7:21 [PATCH 0/3] Decouple iovec management from virtqueue elements Laurent Vivier
@ 2026-03-13 7:21 ` Laurent Vivier
2026-03-13 7:21 ` [PATCH 2/3] vu_handle_tx: Pass actual remaining out_sg capacity " Laurent Vivier
2026-03-13 7:21 ` [PATCH 3/3] vu_common: Move iovec management into vu_collect() Laurent Vivier
2 siblings, 0 replies; 4+ messages in thread
From: Laurent Vivier @ 2026-03-13 7:21 UTC (permalink / raw)
To: passt-dev; +Cc: Laurent Vivier
Currently vu_queue_pop() and vu_queue_map_desc() read the iovec arrays
(in_sg/out_sg) and their sizes (in_num/out_num) from the vu_virtq_element
struct. This couples the iovec storage to the element, requiring callers
like vu_handle_tx() to pre-initialize the element fields before calling
vu_queue_pop().
Pass the iovec arrays and their maximum sizes as separate parameters
instead. vu_queue_map_desc() now writes the actual descriptor count
and iovec pointers back into the element after mapping, rather than
using the element as both input and output.
This decouples the iovec storage from the element, which is a
prerequisite for multi-buffer support where a single frame can span
multiple virtqueue elements sharing a common iovec pool.
No functional change.
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
virtio.c | 29 ++++++++++++++++++++++-------
virtio.h | 4 +++-
vu_common.c | 14 +++++++-------
3 files changed, 32 insertions(+), 15 deletions(-)
diff --git a/virtio.c b/virtio.c
index 447137ee83dd..a671163c27a0 100644
--- a/virtio.c
+++ b/virtio.c
@@ -428,12 +428,18 @@ static bool virtqueue_map_desc(const struct vu_dev *dev,
* @vq: Virtqueue
* @idx: First descriptor ring entry to map
* @elem: Virtqueue element to store descriptor ring iov
+ * @in_sg: Incoming iovec array for device-writable descriptors
+ * @max_in_sg: Maximum number of entries in @in_sg
+ * @out_sg: Outgoing iovec array for device-readable descriptors
+ * @max_out_sg: Maximum number of entries in @out_sg
*
* Return: -1 if there is an error, 0 otherwise
*/
static int vu_queue_map_desc(const struct vu_dev *dev,
struct vu_virtq *vq, unsigned int idx,
- struct vu_virtq_element *elem)
+ struct vu_virtq_element *elem,
+ struct iovec *in_sg, size_t max_in_sg,
+ struct iovec *out_sg, size_t max_out_sg)
{
const struct vring_desc *desc = vq->vring.desc;
struct vring_desc desc_buf[VIRTQUEUE_MAX_SIZE];
@@ -470,16 +476,16 @@ static int vu_queue_map_desc(const struct vu_dev *dev,
/* Collect all the descriptors */
do {
if (le16toh(desc[i].flags) & VRING_DESC_F_WRITE) {
- if (!virtqueue_map_desc(dev, &in_num, elem->in_sg,
- elem->in_num,
+ if (!virtqueue_map_desc(dev, &in_num, in_sg,
+ max_in_sg,
le64toh(desc[i].addr),
le32toh(desc[i].len)))
return -1;
} else {
if (in_num)
die("Incorrect order for descriptors");
- if (!virtqueue_map_desc(dev, &out_num, elem->out_sg,
- elem->out_num,
+ if (!virtqueue_map_desc(dev, &out_num, out_sg,
+ max_out_sg,
le64toh(desc[i].addr),
le32toh(desc[i].len))) {
return -1;
@@ -496,7 +502,9 @@ static int vu_queue_map_desc(const struct vu_dev *dev,
die("vhost-user: Failed to read descriptor list");
elem->index = idx;
+ elem->in_sg = in_sg;
elem->in_num = in_num;
+ elem->out_sg = out_sg;
elem->out_num = out_num;
return 0;
@@ -507,11 +515,17 @@ static int vu_queue_map_desc(const struct vu_dev *dev,
* @dev: Vhost-user device
* @vq: Virtqueue
* @elem: Virtqueue element to fill with the entry information
+ * @in_sg: Incoming iovec array for device-writable descriptors
+ * @max_in_sg: Maximum number of entries in @in_sg
+ * @out_sg: Outgoing iovec array for device-readable descriptors
+ * @max_out_sg: Maximum number of entries in @out_sg
*
* Return: -1 if there is an error, 0 otherwise
*/
int vu_queue_pop(const struct vu_dev *dev, struct vu_virtq *vq,
- struct vu_virtq_element *elem)
+ struct vu_virtq_element *elem,
+ struct iovec *in_sg, size_t max_in_sg,
+ struct iovec *out_sg, size_t max_out_sg)
{
unsigned int head;
int ret;
@@ -535,7 +549,8 @@ int vu_queue_pop(const struct vu_dev *dev, struct vu_virtq *vq,
if (vu_has_feature(dev, VIRTIO_RING_F_EVENT_IDX))
vring_set_avail_event(vq, vq->last_avail_idx);
- ret = vu_queue_map_desc(dev, vq, head, elem);
+ ret = vu_queue_map_desc(dev, vq, head, elem, in_sg, max_in_sg,
+ out_sg, max_out_sg);
if (ret < 0)
return ret;
diff --git a/virtio.h b/virtio.h
index d04bbe84e5c4..c7e447d59860 100644
--- a/virtio.h
+++ b/virtio.h
@@ -188,7 +188,9 @@ static inline bool vu_has_protocol_feature(const struct vu_dev *vdev,
void vu_queue_notify(const struct vu_dev *dev, struct vu_virtq *vq);
int vu_queue_pop(const struct vu_dev *dev, struct vu_virtq *vq,
- struct vu_virtq_element *elem);
+ struct vu_virtq_element *elem,
+ struct iovec *in_sg, size_t max_in_sg,
+ struct iovec *out_sg, size_t max_out_sg);
void vu_queue_detach_element(struct vu_virtq *vq);
void vu_queue_unpop(struct vu_virtq *vq);
bool vu_queue_rewind(struct vu_virtq *vq, unsigned int num);
diff --git a/vu_common.c b/vu_common.c
index 5f2ce18e5b71..4d809ac38a4b 100644
--- a/vu_common.c
+++ b/vu_common.c
@@ -91,7 +91,11 @@ int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
struct iovec *iov;
int ret;
- ret = vu_queue_pop(vdev, vq, &elem[elem_cnt]);
+ ret = vu_queue_pop(vdev, vq, &elem[elem_cnt],
+ elem[elem_cnt].in_sg,
+ elem[elem_cnt].in_num,
+ elem[elem_cnt].out_sg,
+ elem[elem_cnt].out_num);
if (ret < 0)
break;
@@ -178,12 +182,8 @@ static void vu_handle_tx(struct vu_dev *vdev, int index,
int ret;
struct iov_tail data;
- elem[count].out_num = VU_MAX_TX_BUFFER_NB;
- elem[count].out_sg = &out_sg[out_sg_count];
- elem[count].in_num = 0;
- elem[count].in_sg = NULL;
-
- ret = vu_queue_pop(vdev, vq, &elem[count]);
+ ret = vu_queue_pop(vdev, vq, &elem[count], NULL, 0,
+ &out_sg[out_sg_count], VU_MAX_TX_BUFFER_NB);
if (ret < 0)
break;
out_sg_count += elem[count].out_num;
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] vu_common: Move iovec management into vu_collect()
2026-03-13 7:21 [PATCH 0/3] Decouple iovec management from virtqueue elements Laurent Vivier
2026-03-13 7:21 ` [PATCH 1/3] virtio: Pass iovec arrays as separate parameters to vu_queue_pop() Laurent Vivier
2026-03-13 7:21 ` [PATCH 2/3] vu_handle_tx: Pass actual remaining out_sg capacity " Laurent Vivier
@ 2026-03-13 7:21 ` Laurent Vivier
2 siblings, 0 replies; 4+ messages in thread
From: Laurent Vivier @ 2026-03-13 7:21 UTC (permalink / raw)
To: passt-dev; +Cc: Laurent Vivier
Previously, callers had to pre-initialize virtqueue elements with iovec
entries using vu_set_element() or vu_init_elem() before calling
vu_collect(). This meant each element owned a fixed, pre-assigned iovec
slot.
Move the iovec array into vu_collect() as explicit parameters (in_sg,
max_in_sg, and in_num), letting it pass the remaining iovec capacity
directly to vu_queue_pop(). A running current_iov counter tracks
consumed entries across elements, so multiple elements share a single
iovec pool. The optional in_num output parameter reports how many iovec
entries were consumed, allowing callers to track usage across multiple
vu_collect() calls.
This removes vu_set_element() and vu_init_elem() which are no longer
needed, and is a prerequisite for multi-buffer support where a single
virtqueue element can use more than one iovec entry. For now, callers
assert the current single-iovec-per-element invariant until they are
updated to handle multiple iovecs.
Signed-off-by: Laurent Vivier <lvivier@redhat.com>
---
tcp_vu.c | 23 ++++++++++--------
udp_vu.c | 21 ++++++++++-------
vu_common.c | 68 ++++++++++++++++++++++++-----------------------------
vu_common.h | 22 +++--------------
4 files changed, 59 insertions(+), 75 deletions(-)
diff --git a/tcp_vu.c b/tcp_vu.c
index fd734e857b3b..ff826e53355f 100644
--- a/tcp_vu.c
+++ b/tcp_vu.c
@@ -87,13 +87,13 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
hdrlen = tcp_vu_hdrlen(CONN_V6(conn));
- vu_set_element(&flags_elem[0], NULL, &flags_iov[0]);
-
elem_cnt = vu_collect(vdev, vq, &flags_elem[0], 1,
+ &flags_iov[0], 1, NULL,
MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN), NULL);
if (elem_cnt != 1)
return -1;
+ ASSERT(flags_elem[0].in_num == 1);
ASSERT(flags_elem[0].in_sg[0].iov_len >=
MAX(hdrlen + sizeof(*opts), ETH_ZLEN + VNET_HLEN));
@@ -148,9 +148,8 @@ int tcp_vu_send_flag(const struct ctx *c, struct tcp_tap_conn *conn, int flags)
nb_ack = 1;
if (flags & DUP_ACK) {
- vu_set_element(&flags_elem[1], NULL, &flags_iov[1]);
-
elem_cnt = vu_collect(vdev, vq, &flags_elem[1], 1,
+ &flags_iov[1], 1, NULL,
flags_elem[0].in_sg[0].iov_len, NULL);
if (elem_cnt == 1 &&
flags_elem[1].in_sg[0].iov_len >=
@@ -191,8 +190,8 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq,
const struct vu_dev *vdev = c->vdev;
struct msghdr mh_sock = { 0 };
uint16_t mss = MSS_GET(conn);
+ size_t hdrlen, iov_used;
int s = conn->sock;
- size_t hdrlen;
int elem_cnt;
ssize_t ret;
int i;
@@ -201,22 +200,26 @@ static ssize_t tcp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq,
hdrlen = tcp_vu_hdrlen(v6);
- vu_init_elem(elem, &iov_vu[DISCARD_IOV_NUM], VIRTQUEUE_MAX_SIZE);
-
+ iov_used = 0;
elem_cnt = 0;
*head_cnt = 0;
- while (fillsize > 0 && elem_cnt < VIRTQUEUE_MAX_SIZE) {
+ while (fillsize > 0 && elem_cnt < ARRAY_SIZE(elem) &&
+ iov_used < VIRTQUEUE_MAX_SIZE) {
+ size_t frame_size, dlen, in_num;
struct iovec *iov;
- size_t frame_size, dlen;
int cnt;
cnt = vu_collect(vdev, vq, &elem[elem_cnt],
- VIRTQUEUE_MAX_SIZE - elem_cnt,
+ ARRAY_SIZE(elem) - elem_cnt,
+ &iov_vu[DISCARD_IOV_NUM + iov_used],
+ VIRTQUEUE_MAX_SIZE - iov_used, &in_num,
MAX(MIN(mss, fillsize) + hdrlen, ETH_ZLEN + VNET_HLEN),
&frame_size);
if (cnt == 0)
break;
+ ASSERT((size_t)cnt == in_num); /* one iovec per element */
+ iov_used += in_num;
dlen = frame_size - hdrlen;
/* reserve space for headers in iov */
diff --git a/udp_vu.c b/udp_vu.c
index 5effca777e0a..acc18d375a0f 100644
--- a/udp_vu.c
+++ b/udp_vu.c
@@ -71,9 +71,10 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
bool v6, ssize_t *dlen)
{
const struct vu_dev *vdev = c->vdev;
+ int elem_cnt, elem_used, iov_used;
struct msghdr msg = { 0 };
- int iov_cnt, iov_used;
size_t hdrlen, l2len;
+ size_t iov_cnt;
ASSERT(!c->no_udp);
@@ -89,13 +90,14 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
/* compute L2 header length */
hdrlen = udp_vu_hdrlen(v6);
- vu_init_elem(elem, iov_vu, VIRTQUEUE_MAX_SIZE);
-
- iov_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE,
- IP_MAX_MTU + ETH_HLEN + VNET_HLEN, NULL);
- if (iov_cnt == 0)
+ elem_cnt = vu_collect(vdev, vq, elem, ARRAY_SIZE(elem),
+ iov_vu, ARRAY_SIZE(iov_vu), &iov_cnt,
+ IP_MAX_MTU + ETH_HLEN + VNET_HLEN, NULL);
+ if (elem_cnt == 0)
return -1;
+ ASSERT((size_t)elem_cnt == iov_cnt); /* one iovec per element */
+
/* reserve space for the headers */
ASSERT(iov_vu[0].iov_len >= MAX(hdrlen, ETH_ZLEN + VNET_HLEN));
iov_vu[0].iov_base = (char *)iov_vu[0].iov_base + hdrlen;
@@ -107,7 +109,7 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
*dlen = recvmsg(s, &msg, 0);
if (*dlen < 0) {
- vu_queue_rewind(vq, iov_cnt);
+ vu_queue_rewind(vq, elem_cnt);
return -1;
}
@@ -116,15 +118,16 @@ static int udp_vu_sock_recv(const struct ctx *c, struct vu_virtq *vq, int s,
iov_vu[0].iov_len += hdrlen;
iov_used = iov_truncate(iov_vu, iov_cnt, *dlen + hdrlen);
+ elem_used = iov_used; /* one iovec per element */
/* pad frame to 60 bytes: first buffer is at least ETH_ZLEN long */
l2len = *dlen + hdrlen - VNET_HLEN;
vu_pad(&iov_vu[0], l2len);
- vu_set_vnethdr(iov_vu[0].iov_base, iov_used);
+ vu_set_vnethdr(iov_vu[0].iov_base, elem_used);
/* release unused buffers */
- vu_queue_rewind(vq, iov_cnt - iov_used);
+ vu_queue_rewind(vq, elem_cnt - elem_used);
return iov_used;
}
diff --git a/vu_common.c b/vu_common.c
index ed0033d6bb11..d5fca9e52004 100644
--- a/vu_common.c
+++ b/vu_common.c
@@ -51,28 +51,15 @@ int vu_packet_check_range(struct vdev_memory *memory,
return -1;
}
-/**
- * vu_init_elem() - initialize an array of virtqueue elements with 1 iov in each
- * @elem: Array of virtqueue elements to initialize
- * @iov: Array of iovec to assign to virtqueue element
- * @elem_cnt: Number of virtqueue element
- */
-void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov, int elem_cnt)
-{
- int i;
-
- for (i = 0; i < elem_cnt; i++)
- vu_set_element(&elem[i], NULL, &iov[i]);
-}
-
/**
* vu_collect() - collect virtio buffers from a given virtqueue
* @vdev: vhost-user device
* @vq: virtqueue to collect from
- * @elem: Array of virtqueue element
- * each element must be initialized with one iovec entry
- * in the in_sg array.
+ * @elem: Array of @max_elem virtqueue elements
* @max_elem: Number of virtqueue elements in the array
+ * @in_sg: Incoming iovec array for device-writable descriptors
+ * @max_in_sg: Maximum number of entries in @in_sg
+ * @in_num: Number of collected entries from @in_sg (output)
* @size: Maximum size of the data in the frame
* @collected: Collected buffer length, up to @size, set on return
*
@@ -80,20 +67,21 @@ void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov, int elem_cnt
*/
int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
struct vu_virtq_element *elem, int max_elem,
+ struct iovec *in_sg, size_t max_in_sg, size_t *in_num,
size_t size, size_t *collected)
{
size_t current_size = 0;
+ size_t current_iov = 0;
int elem_cnt = 0;
- while (current_size < size && elem_cnt < max_elem) {
- struct iovec *iov;
+ while (current_size < size && elem_cnt < max_elem &&
+ current_iov < max_in_sg) {
int ret;
ret = vu_queue_pop(vdev, vq, &elem[elem_cnt],
- elem[elem_cnt].in_sg,
- elem[elem_cnt].in_num,
- elem[elem_cnt].out_sg,
- elem[elem_cnt].out_num);
+ &in_sg[current_iov],
+ max_in_sg - current_iov,
+ NULL, 0);
if (ret < 0)
break;
@@ -103,18 +91,22 @@ int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
break;
}
- iov = &elem[elem_cnt].in_sg[0];
-
- if (iov->iov_len > size - current_size)
- iov->iov_len = size - current_size;
+ elem[elem_cnt].in_num = iov_truncate(elem[elem_cnt].in_sg,
+ elem[elem_cnt].in_num,
+ size - current_size);
- current_size += iov->iov_len;
+ current_size += iov_size(elem[elem_cnt].in_sg,
+ elem[elem_cnt].in_num);
+ current_iov += elem[elem_cnt].in_num;
elem_cnt++;
if (!vu_has_feature(vdev, VIRTIO_NET_F_MRG_RXBUF))
break;
}
+ if (in_num)
+ *in_num = current_iov;
+
if (collected)
*collected = current_size;
@@ -147,8 +139,11 @@ void vu_flush(const struct vu_dev *vdev, struct vu_virtq *vq,
{
int i;
- for (i = 0; i < elem_cnt; i++)
- vu_queue_fill(vdev, vq, &elem[i], elem[i].in_sg[0].iov_len, i);
+ for (i = 0; i < elem_cnt; i++) {
+ size_t elem_size = iov_size(elem[i].in_sg, elem[i].in_num);
+
+ vu_queue_fill(vdev, vq, &elem[i], elem_size, i);
+ }
vu_queue_flush(vdev, vq, elem_cnt);
vu_queue_notify(vdev, vq);
@@ -246,7 +241,7 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size)
struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE];
struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE];
struct iovec in_sg[VIRTQUEUE_MAX_SIZE];
- size_t total;
+ size_t total, in_num;
int elem_cnt;
int i;
@@ -257,11 +252,10 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size)
return -1;
}
- vu_init_elem(elem, in_sg, VIRTQUEUE_MAX_SIZE);
-
size += VNET_HLEN;
- elem_cnt = vu_collect(vdev, vq, elem, VIRTQUEUE_MAX_SIZE, size, &total);
- if (total < size) {
+ elem_cnt = vu_collect(vdev, vq, elem, ARRAY_SIZE(elem), in_sg,
+ ARRAY_SIZE(in_sg), &in_num, size, &total);
+ if (elem_cnt == 0 || total < size) {
debug("vu_send_single: no space to send the data "
"elem_cnt %d size %zd", elem_cnt, total);
goto err;
@@ -272,10 +266,10 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size)
total -= VNET_HLEN;
/* copy data from the buffer to the iovec */
- iov_from_buf(in_sg, elem_cnt, VNET_HLEN, buf, total);
+ iov_from_buf(in_sg, in_num, VNET_HLEN, buf, total);
if (*c->pcap)
- pcap_iov(in_sg, elem_cnt, VNET_HLEN);
+ pcap_iov(in_sg, in_num, VNET_HLEN);
vu_flush(vdev, vq, elem, elem_cnt);
diff --git a/vu_common.h b/vu_common.h
index 865d9771fa89..6c31630e8712 100644
--- a/vu_common.h
+++ b/vu_common.h
@@ -35,26 +35,10 @@ static inline void *vu_payloadv6(void *base)
return (struct ipv6hdr *)vu_ip(base) + 1;
}
-/**
- * vu_set_element() - Initialize a vu_virtq_element
- * @elem: Element to initialize
- * @out_sg: One out iovec entry to set in elem
- * @in_sg: One in iovec entry to set in elem
- */
-static inline void vu_set_element(struct vu_virtq_element *elem,
- struct iovec *out_sg, struct iovec *in_sg)
-{
- elem->out_num = !!out_sg;
- elem->out_sg = out_sg;
- elem->in_num = !!in_sg;
- elem->in_sg = in_sg;
-}
-
-void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov,
- int elem_cnt);
int vu_collect(const struct vu_dev *vdev, struct vu_virtq *vq,
- struct vu_virtq_element *elem, int max_elem, size_t size,
- size_t *collected);
+ struct vu_virtq_element *elem, int max_elem,
+ struct iovec *in_sg, size_t max_in_sg, size_t *in_num,
+ size_t size, size_t *collected);
void vu_set_vnethdr(struct virtio_net_hdr_mrg_rxbuf *vnethdr, int num_buffers);
void vu_flush(const struct vu_dev *vdev, struct vu_virtq *vq,
struct vu_virtq_element *elem, int elem_cnt);
--
2.53.0
^ permalink raw reply [flat|nested] 4+ messages in thread