On Mon, Mar 09, 2026 at 10:47:44AM +0100, Laurent Vivier wrote: > iPXE places the vnet header in one virtqueue descriptor and the payload > in another. When passt maps these descriptors, it needs two iovecs per > virtqueue element to handle this layout. > > Without this, passt crashes with: > > ASSERTION FAILED in virtqueue_map_desc (virtio.c:403): num_sg < max_num_sg > > Signed-off-by: Laurent Vivier > --- > udp_vu.c | 8 ++++---- > vu_common.c | 15 +++++++++++---- > 2 files changed, 15 insertions(+), 8 deletions(-) > > diff --git a/udp_vu.c b/udp_vu.c > index cb5274aa1d81..47659b0402fd 100644 > --- a/udp_vu.c > +++ b/udp_vu.c > @@ -34,7 +34,7 @@ > #include "vu_common.h" > > static struct iovec iov_vu [VIRTQUEUE_MAX_SIZE]; > -static struct vu_virtq_element elem [VIRTQUEUE_MAX_SIZE]; > +static struct vu_virtq_element elem [VIRTQUEUE_MAX_SIZE / IOV_PER_ELEM]; The two level structure of the queues (array of elems, each pointing to an array of iovs) confuses me a bit. I would have thought that VIRTQUEUE_MAX_SIZE represented the maximum number of elements, even if those elements had multiple iovs. Can you enlighten me? > /** > * udp_vu_hdrlen() - Sum size of all headers, from UDP to virtio-net > @@ -214,20 +214,20 @@ void udp_vu_sock_to_tap(const struct ctx *c, int s, int n, flow_sidx_t tosidx) > size_t iov_cnt; > ssize_t dlen; > > - vu_init_elem(elem, iov_vu, ARRAY_SIZE(elem), 1); > + vu_init_elem(elem, iov_vu, ARRAY_SIZE(elem), IOV_PER_ELEM); > > elem_cnt = vu_collect(vdev, vq, elem, ARRAY_SIZE(elem), > IP_MAX_MTU + ETH_HLEN + VNET_HLEN, NULL); > if (elem_cnt == 0) > break; > > - iov_cnt = elem_cnt; > + iov_cnt = (size_t)elem_cnt * IOV_PER_ELEM; > dlen = udp_vu_sock_recv(iov_vu, &iov_cnt, s, v6); > if (dlen < 0) { > vu_queue_rewind(vq, elem_cnt); > break; > } > - elem_used = iov_cnt; > + elem_used = DIV_ROUND_UP(iov_cnt, IOV_PER_ELEM); > > /* release unused buffers */ > vu_queue_rewind(vq, elem_cnt - elem_used); > diff --git a/vu_common.c b/vu_common.c > index 3225aca53ea6..a2867a293184 100644 > --- a/vu_common.c > +++ b/vu_common.c > @@ -63,8 +63,15 @@ void vu_init_elem(struct vu_virtq_element *elem, struct iovec *iov, > { > int i, j; > > - for (i = 0, j = 0; i < elem_cnt; i++, j += iov_per_elem) > + for (i = 0, j = 0; i < elem_cnt; i++, j += iov_per_elem) { > + int k; > + > + for (k = 0; k < iov_per_elem; k++) { > + iov[j + k].iov_base = NULL; > + iov[j + k].iov_len = 0; > + } > vu_set_element(&elem[i], 0, NULL, iov_per_elem, &iov[j]); > + } > } > > /** > @@ -272,7 +279,7 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size) > { > struct vu_dev *vdev = c->vdev; > struct vu_virtq *vq = &vdev->vq[VHOST_USER_RX_QUEUE]; > - struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE]; > + struct vu_virtq_element elem[VIRTQUEUE_MAX_SIZE / IOV_PER_ELEM]; > struct iovec in_sg[VIRTQUEUE_MAX_SIZE]; > struct iov_tail data; > size_t total; > @@ -286,7 +293,7 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size) > return -1; > } > > - vu_init_elem(elem, in_sg, ARRAY_SIZE(elem), 1); > + vu_init_elem(elem, in_sg, ARRAY_SIZE(elem), IOV_PER_ELEM); > > size += VNET_HLEN; > elem_cnt = vu_collect(vdev, vq, elem, ARRAY_SIZE(elem), size, &total); > @@ -303,7 +310,7 @@ int vu_send_single(const struct ctx *c, const void *buf, size_t size) > } > elem_cnt = iov_truncate(in_sg, elem_cnt, size); > > - data = IOV_TAIL(&in_sg[0], elem_cnt, 0); > + data = IOV_TAIL(&in_sg[0], (size_t)(elem_cnt * IOV_PER_ELEM), 0); > vu_set_vnethdr(&data, elem_cnt); > > size -= VNET_HLEN; > -- > 2.53.0 > -- David Gibson (he or they) | I'll have my music baroque, and my code david AT gibson.dropbear.id.au | minimalist, thank you, not the other way | around. http://www.ozlabs.org/~dgibson