aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>2022-06-07 18:02:28 +0100
committerWill Deacon <will@kernel.org>2022-06-09 13:44:15 +0100
commit6daffe57762cb5863eef0441a2d40cc57cdde37b (patch)
tree453faa67969b6f4a88a4d22ac527ab61d8c3f950
parentb231683c336132441496060a81726305ca56b11b (diff)
downloadkvmtool-6daffe57762cb5863eef0441a2d40cc57cdde37b.tar.gz
virtio/net: Implement VIRTIO_F_ANY_LAYOUT feature
Modern virtio demands that devices do not make assumptions about the buffer layouts. Currently the user network backend assumes that TX packets are neatly split between virtio-net header and ethernet frame. Modern virtio-net usually puts everything into one descriptor, but could also split the buffer arbitrarily. Handle arbitrary buffer layouts and advertise the VIRTIO_F_ANY_LAYOUT feature. Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com> Link: https://lore.kernel.org/r/20220607170239.120084-14-jean-philippe.brucker@arm.com Signed-off-by: Will Deacon <will@kernel.org>
-rw-r--r--net/uip/core.c71
-rw-r--r--virtio/net.c21
2 files changed, 57 insertions, 35 deletions
diff --git a/net/uip/core.c b/net/uip/core.c
index 977b9b0c..e409512e 100644
--- a/net/uip/core.c
+++ b/net/uip/core.c
@@ -9,40 +9,56 @@
int uip_tx(struct iovec *iov, u16 out, struct uip_info *info)
{
void *vnet;
+ ssize_t len;
struct uip_tx_arg arg;
- int eth_len, vnet_len;
+ size_t eth_len, vnet_len;
struct uip_eth *eth;
- u8 *buf = NULL;
+ void *vnet_buf = NULL;
+ void *eth_buf = NULL;
+ size_t iovcount = out;
+
u16 proto;
- int i;
/*
* Buffer from guest to device
*/
- vnet_len = iov[0].iov_len;
+ vnet_len = info->vnet_hdr_len;
vnet = iov[0].iov_base;
- eth_len = iov[1].iov_len;
- eth = iov[1].iov_base;
+ len = iov_size(iov, iovcount);
+ if (len <= (ssize_t)vnet_len)
+ return -EINVAL;
+
+ /* Try to avoid memcpy if possible */
+ if (iov[0].iov_len == vnet_len && out == 2) {
+ /* Legacy layout: first descriptor for vnet header */
+ eth = iov[1].iov_base;
+ eth_len = iov[1].iov_len;
+
+ } else if (out == 1) {
+ /* Single descriptor */
+ eth = (void *)vnet + vnet_len;
+ eth_len = iov[0].iov_len - vnet_len;
+
+ } else {
+ /* Any layout */
+ len = vnet_len;
+ vnet = vnet_buf = malloc(len);
+ if (!vnet)
+ return -ENOMEM;
- /*
- * In case, ethernet frame is in more than one iov entry.
- * Copy iov buffer into one linear buffer.
- */
- if (out > 2) {
- eth_len = 0;
- for (i = 1; i < out; i++)
- eth_len += iov[i].iov_len;
+ len = memcpy_fromiovec_safe(vnet_buf, &iov, len, &iovcount);
+ if (len)
+ goto out_free_buf;
- buf = malloc(eth_len);
- if (!buf)
- return -ENOMEM;
+ len = eth_len = iov_size(iov, iovcount);
+ eth = eth_buf = malloc(len);
+ if (!eth)
+ goto out_free_buf;
- eth = (struct uip_eth *)buf;
- for (i = 1; i < out; i++) {
- memcpy(buf, iov[i].iov_base, iov[i].iov_len);
- buf += iov[i].iov_len;
- }
+ len = memcpy_fromiovec_safe(eth_buf, &iov, len, &iovcount);
+ if (len)
+ goto out_free_buf;
}
memset(&arg, 0, sizeof(arg));
@@ -65,14 +81,17 @@ int uip_tx(struct iovec *iov, u16 out, struct uip_info *info)
case UIP_ETH_P_IP:
uip_tx_do_ipv4(&arg);
break;
- default:
- break;
}
- if (out > 2 && buf)
- free(eth);
+ free(vnet_buf);
+ free(eth_buf);
return vnet_len + eth_len;
+
+out_free_buf:
+ free(vnet_buf);
+ free(eth_buf);
+ return -EINVAL;
}
int uip_rx(struct iovec *iov, u16 in, struct uip_info *info)
diff --git a/virtio/net.c b/virtio/net.c
index 985642f6..f8c40d40 100644
--- a/virtio/net.c
+++ b/virtio/net.c
@@ -221,8 +221,9 @@ static void *virtio_net_ctrl_thread(void *p)
struct net_dev *ndev = queue->ndev;
u16 out, in, head;
struct kvm *kvm = ndev->kvm;
- struct virtio_net_ctrl_hdr *ctrl;
- virtio_net_ctrl_ack *ack;
+ struct virtio_net_ctrl_hdr ctrl;
+ virtio_net_ctrl_ack ack;
+ size_t len;
kvm__set_thread_name("virtio-net-ctrl");
@@ -234,18 +235,19 @@ static void *virtio_net_ctrl_thread(void *p)
while (virt_queue__available(vq)) {
head = virt_queue__get_iov(vq, iov, &out, &in, kvm);
- ctrl = iov[0].iov_base;
- ack = iov[out].iov_base;
+ len = min(iov_size(iov, in), sizeof(ctrl));
+ memcpy_fromiovec((void *)&ctrl, iov, len);
- switch (ctrl->class) {
+ switch (ctrl.class) {
case VIRTIO_NET_CTRL_MQ:
- *ack = virtio_net_handle_mq(kvm, ndev, ctrl);
+ ack = virtio_net_handle_mq(kvm, ndev, &ctrl);
break;
default:
- *ack = VIRTIO_NET_ERR;
+ ack = VIRTIO_NET_ERR;
break;
}
- virt_queue__set_used_elem(vq, head, iov[out].iov_len);
+ memcpy_toiovec(iov + in, &ack, sizeof(ack));
+ virt_queue__set_used_elem(vq, head, sizeof(ack));
}
if (virtio_queue__should_signal(vq))
@@ -495,7 +497,8 @@ static u32 get_host_features(struct kvm *kvm, void *dev)
| 1UL << VIRTIO_RING_F_INDIRECT_DESC
| 1UL << VIRTIO_NET_F_CTRL_VQ
| 1UL << VIRTIO_NET_F_MRG_RXBUF
- | 1UL << (ndev->queue_pairs > 1 ? VIRTIO_NET_F_MQ : 0);
+ | 1UL << (ndev->queue_pairs > 1 ? VIRTIO_NET_F_MQ : 0)
+ | 1UL << VIRTIO_F_ANY_LAYOUT;
/*
* The UFO feature for host and guest only can be enabled when the