diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 9b53f6c2e1c..0b9bcdfd7bd 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -88,6 +88,7 @@ config ARCH_CHIP_AM67 select ARCH_CORTEXR5 select ARCH_HAVE_LOWVECTORS select ARCH_HAVE_TICKLESS + select ARCH_HAVE_VHOST_IOMAP select SCHED_HPWORK if RPTUN ---help--- TI AM67 family diff --git a/arch/arm/src/am67/am67_rat.c b/arch/arm/src/am67/am67_rat.c index 867abc7ba62..35f0d69cbbb 100644 --- a/arch/arm/src/am67/am67_rat.c +++ b/arch/arm/src/am67/am67_rat.c @@ -53,6 +53,7 @@ #include "arm_internal.h" #include "am67_rat.h" +#include /**************************************************************************** * Pre-processor Definitions @@ -112,3 +113,19 @@ FAR void *am67_rat_map(uint64_t pa, FAR size_t *avail) return (FAR void *)(AM67_RAT_WIN_BASE + offset); } + +/**************************************************************************** + * Name: up_vhost_iomap + * + * Description: + * Arch hook used by the vhost drivers to reach peer buffers by 64-bit + * physical address (see ARCH_HAVE_VHOST_IOMAP). + * + ****************************************************************************/ + +#ifdef CONFIG_DRIVERS_VHOST +FAR void *up_vhost_iomap(uint64_t pa, FAR size_t *avail) +{ + return am67_rat_map(pa, avail); +} +#endif diff --git a/drivers/vhost/CMakeLists.txt b/drivers/vhost/CMakeLists.txt index 4669b175218..d630c4d0fc2 100644 --- a/drivers/vhost/CMakeLists.txt +++ b/drivers/vhost/CMakeLists.txt @@ -29,6 +29,10 @@ if(CONFIG_DRIVERS_VHOST_RNG) list(APPEND SRCS vhost-rng.c) endif() +if(CONFIG_DRIVERS_VHOST_NET) + list(APPEND SRCS vhost-net.c) +endif() + if(CONFIG_DRIVERS_VHOST_RPMSG) list(APPEND SRCS vhost-rpmsg.c) endif() diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig index 3622cc0e20b..d3581b7fbe6 100644 --- a/drivers/vhost/Kconfig +++ b/drivers/vhost/Kconfig @@ -3,17 +3,47 @@ # see the file kconfig-language.txt in the NuttX tools repository. # -config DRIVERS_VHOST +config ARCH_HAVE_VHOST_IOMAP bool + default n + +config DRIVERS_VHOST + bool "Virtual Host (device-role virtio) support" select OPENAMP select SCHED_WORKQUEUE default n + ---help--- + Framework for implementing the DEVICE side of virtio links + (the peer's driver uses us as the device, e.g. a remoteproc + master like Linux). rptun registers device-role vdevs from the + resource table through this bus; without it such vdevs are + rejected with -ENODEV. config DRIVERS_VHOST_RNG bool "Virtual Host Rng Device Support" default n select DRIVERS_VHOST +config DRIVERS_VHOST_NET + bool "Virtual Host Network Device Support" + default n + depends on NETDEVICES + select DRIVERS_VHOST + ---help--- + Device-role virtio-net: expose this side as a network card to a + peer processor running a standard virtio-net driver (e.g. Linux + via remoteproc), providing a NuttX ethN interface. + +config DRIVERS_VHOST_NET_MACADDR + hex "vhost-net MAC address" + default 0x025433000001 + depends on DRIVERS_VHOST_NET + ---help--- + No virtio-net features are negotiated on this link, so there is + no MAC config space to read; this fixed, software-assigned, + locally administered address is presented to the network stack + instead. + config DRIVERS_VHOST_RPMSG bool "Virtual Host Rpmsg Device Support" default n diff --git a/drivers/vhost/Make.defs b/drivers/vhost/Make.defs index d48ca2b45c8..87063aefb3a 100644 --- a/drivers/vhost/Make.defs +++ b/drivers/vhost/Make.defs @@ -30,6 +30,10 @@ ifeq ($(CONFIG_DRIVERS_VHOST_RNG),y) CSRCS += vhost-rng.c endif +ifeq ($(CONFIG_DRIVERS_VHOST_NET),y) +CSRCS += vhost-net.c +endif + ifeq ($(CONFIG_DRIVERS_VHOST_RPMSG),y) CSRCS += vhost-rpmsg.c endif diff --git a/drivers/vhost/vhost-net.c b/drivers/vhost/vhost-net.c new file mode 100644 index 00000000000..712e45e270d --- /dev/null +++ b/drivers/vhost/vhost-net.c @@ -0,0 +1,495 @@ +/**************************************************************************** + * drivers/vhost/vhost-net.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* Device-role virtio network driver ("vhost-net"): implements the DEVICE + * end of a virtio-net link, so a peer processor running a stock virtio-net + * DRIVER (e.g. Linux via remoteproc/rproc-virtio) sees this side as a + * network card. Registers a NuttX netdev lowerhalf (ethN). + * + * Ring layout is fixed by the peer driver's point of view: + * vq[0] = peer driver RX queue: the peer posts empty buffers; the + * device side fills them to transmit toward the peer. + * vq[1] = peer driver TX queue: the peer posts filled buffers; the + * device side harvests them as its receive path. + * + * No virtio-net features are negotiated (the resource table advertises + * none), so every packet is prefixed by the legacy 10-byte + * struct virtio_net_hdr with all fields zero (gso_type NONE). + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include + +#include "vhost-net.h" + +/* Peer buffers are referenced by 64-bit guest physical addresses that may + * exceed the CPU's direct reach; arches that provide a translation window + * implement up_vhost_iomap() (ARCH_HAVE_VHOST_IOMAP), others use the + * identity mapping. + */ + +#ifdef CONFIG_ARCH_HAVE_VHOST_IOMAP +# define vhost_net_map(pa, avl) up_vhost_iomap((pa), (avl)) +#else +static inline FAR void *vhost_net_map(uint64_t pa, FAR size_t *avail) +{ + if (avail != NULL) + { + *avail = SIZE_MAX; + } + + return (FAR void *)(uintptr_t)pa; +} +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Queue indices (peer driver's numbering, see file header) */ + +#define VHOST_NET_PEER_RXQ 0 /* device-side transmit lane */ +#define VHOST_NET_PEER_TXQ 1 /* device-side receive lane */ +#define VHOST_NET_NUM 2 + +/* Legacy struct virtio_net_hdr (no VIRTIO_NET_F_MRG_RXBUF): flags(1) + + * gso_type(1) + hdr_len(2) + gso_size(2) + csum_start(2) + csum_offset(2) + */ + +#define VHOST_NET_HDRSIZE 10 + +/* netpkt quota per direction and the longest peer descriptor chain we + * accept on receive (Linux commonly splits header and payload). + */ + +#define VHOST_NET_NPKTS 8 +#define VHOST_NET_MAXCHAIN 8 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct vhost_net_priv_s +{ + struct netdev_lowerhalf_s lower; /* Must be first for casts */ + FAR struct vhost_device *hdev; + FAR struct virtqueue *txq; /* peer RX ring (filled here) */ + FAR struct virtqueue *rxq; /* peer TX ring (drained here) */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int vhost_net_ifup(FAR struct netdev_lowerhalf_s *dev); +static int vhost_net_ifdown(FAR struct netdev_lowerhalf_s *dev); +static int vhost_net_transmit(FAR struct netdev_lowerhalf_s *dev, + FAR netpkt_t *pkt); +static FAR netpkt_t *vhost_net_receive(FAR struct netdev_lowerhalf_s *dev); +static int vhost_net_probe(FAR struct vhost_device *hdev); +static void vhost_net_remove(FAR struct vhost_device *hdev); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct netdev_ops_s g_vhost_net_ops = +{ + vhost_net_ifup, + vhost_net_ifdown, + vhost_net_transmit, + vhost_net_receive, +#ifdef CONFIG_NET_MCASTGROUP + NULL, + NULL, +#endif +#ifdef CONFIG_NETDEV_IOCTL + NULL, +#endif + NULL +}; + +static struct vhost_driver g_vhost_net_driver = +{ + LIST_INITIAL_VALUE(g_vhost_net_driver.node), /* node */ + VIRTIO_ID_NETWORK, /* device id */ + vhost_net_probe, /* probe */ + vhost_net_remove, /* remove */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: vhost_net_rxready / vhost_net_txdone + * + * Description: + * Virtqueue kick callbacks (transport notification context, thread + * level). Notify the upper half that ring work is pending; the rings + * are processed in transmit()/receive(). + * + ****************************************************************************/ + +static void vhost_net_rxready(FAR struct virtqueue *vq) +{ + FAR struct vhost_net_priv_s *priv = vq->vq_dev->priv; + + netdev_lower_rxready(&priv->lower); +} + +static void vhost_net_txdone(FAR struct virtqueue *vq) +{ + FAR struct vhost_net_priv_s *priv = vq->vq_dev->priv; + + netdev_lower_txdone(&priv->lower); +} + +/**************************************************************************** + * Name: vhost_net_ifup / vhost_net_ifdown + ****************************************************************************/ + +static int vhost_net_ifup(FAR struct netdev_lowerhalf_s *dev) +{ + netdev_lower_carrier_on(dev); + return OK; +} + +static int vhost_net_ifdown(FAR struct netdev_lowerhalf_s *dev) +{ + netdev_lower_carrier_off(dev); + return OK; +} + +/**************************************************************************** + * Name: vhost_net_transmit + * + * Description: + * Fill one peer-posted RX buffer with the frame and complete it. + * Completion is synchronous: the netpkt is consumed and freed before + * returning. + * + ****************************************************************************/ + +static int vhost_net_transmit(FAR struct netdev_lowerhalf_s *dev, + FAR netpkt_t *pkt) +{ + FAR struct vhost_net_priv_s *priv = (FAR struct vhost_net_priv_s *)dev; + struct vhost_buf_s vb[1]; + unsigned int len; + unsigned int pos; + size_t cnt; + int head; + + head = vhost_get_vq_buffers_pa(priv->txq, vb, nitems(vb), &cnt); + if (head < 0) + { + /* Peer has not posted buffers (yet). Re-enable its notifications; + * enable_cb reports buffers that arrived in the race window (their + * kick was suppressed), so grab them now if so. + */ + + if (!virtqueue_enable_cb(priv->txq)) + { + return -ENOBUFS; + } + + head = vhost_get_vq_buffers_pa(priv->txq, vb, nitems(vb), &cnt); + if (head < 0) + { + return -ENOBUFS; + } + } + + len = netpkt_getdatalen(dev, pkt); + if (len + VHOST_NET_HDRSIZE > vb[0].len) + { + /* Frame cannot fit the peer's buffer: complete it empty (drop) */ + + vhosterr("frame %u exceeds peer buffer %" PRIu32 ", dropped\n", + len, vb[0].len); + len = 0; + } + else + { + /* Serialize the zero header + frame into the peer buffer through + * the translation window, honoring window-boundary splits. + */ + + for (pos = 0; pos < len + VHOST_NET_HDRSIZE; ) + { + size_t avail; + FAR uint8_t *dst = vhost_net_map(vb[0].addr + pos, &avail); + unsigned int chunk = MIN(len + VHOST_NET_HDRSIZE - pos, avail); + unsigned int hdrlen = 0; + int ret = OK; + + if (pos < VHOST_NET_HDRSIZE) + { + hdrlen = MIN(VHOST_NET_HDRSIZE - pos, chunk); + memset(dst, 0, hdrlen); + } + + if (chunk > hdrlen) + { + ret = netpkt_copyout(dev, dst + hdrlen, pkt, chunk - hdrlen, + pos + hdrlen - VHOST_NET_HDRSIZE); + } + + if (ret < 0) + { + vhosterr("netpkt_copyout failed, ret=%d, dropped\n", ret); + len = 0; + break; + } + + pos += chunk; + } + } + + virtqueue_add_consumed_buffer(priv->txq, head, + len ? len + VHOST_NET_HDRSIZE : 0); + virtqueue_kick(priv->txq); + + netpkt_free(dev, pkt, NETPKT_TX); + netdev_lower_txdone(dev); + return OK; +} + +/**************************************************************************** + * Name: vhost_net_receive + * + * Description: + * Harvest one frame (possibly a descriptor chain) from the peer TX + * ring, copy it into a fresh netpkt (stripping the virtio-net header) + * and return the buffers to the peer. + * + ****************************************************************************/ + +static FAR netpkt_t *vhost_net_receive(FAR struct netdev_lowerhalf_s *dev) +{ + FAR struct vhost_net_priv_s *priv = (FAR struct vhost_net_priv_s *)dev; + struct vhost_buf_s vb[VHOST_NET_MAXCHAIN]; + FAR netpkt_t *pkt = NULL; + unsigned int total = 0; + unsigned int skip = VHOST_NET_HDRSIZE; + int offset = 0; + size_t cnt; + size_t i; + int head; + + head = vhost_get_vq_buffers_pa(priv->rxq, vb, nitems(vb), &cnt); + if (head < 0) + { + /* See vhost_net_transmit() for the enable_cb recheck rationale */ + + if (!virtqueue_enable_cb(priv->rxq)) + { + return NULL; + } + + head = vhost_get_vq_buffers_pa(priv->rxq, vb, nitems(vb), &cnt); + if (head < 0) + { + return NULL; + } + } + + for (i = 0; i < cnt; i++) + { + total += vb[i].len; + } + + if (total > skip) + { + pkt = netpkt_alloc(dev, NETPKT_RX); + } + + if (pkt != NULL && + netpkt_setdatalen(dev, pkt, total - skip) < total - skip) + { + vhosterr("rx dropped: cannot size netpkt to %u\n", total - skip); + netpkt_free(dev, pkt, NETPKT_RX); + pkt = NULL; + } + + if (pkt != NULL) + { + for (i = 0; i < cnt; i++) + { + uint64_t pa = vb[i].addr; + uint32_t blen = vb[i].len; + + if (skip > 0) + { + uint32_t skiplen = MIN(skip, blen); + + pa += skiplen; + blen -= skiplen; + skip -= skiplen; + } + + /* Copy through the translation window, honoring + * window-boundary splits. + */ + + while (blen > 0) + { + size_t avail; + FAR const uint8_t *src = vhost_net_map(pa, &avail); + uint32_t chunk = MIN(blen, avail); + + if (netpkt_copyin(dev, pkt, src, chunk, offset) < 0) + { + vhosterr("netpkt_copyin failed, rx dropped\n"); + netpkt_free(dev, pkt, NETPKT_RX); + pkt = NULL; + goto out; + } + + offset += chunk; + pa += chunk; + blen -= chunk; + } + } + } + else + { + vhosterr("rx dropped: total=%u (no netpkt)\n", total); + } + +out: + + /* Hand the buffers back to the peer either way */ + + virtqueue_add_consumed_buffer(priv->rxq, head, total); + virtqueue_kick(priv->rxq); + + return pkt; +} + +/**************************************************************************** + * Name: vhost_net_probe + ****************************************************************************/ + +static int vhost_net_probe(FAR struct vhost_device *hdev) +{ + FAR struct vhost_net_priv_s *priv; + FAR const char *vqnames[VHOST_NET_NUM]; + vq_callback callbacks[VHOST_NET_NUM]; + FAR uint8_t *mac; + int ret; + + priv = kmm_zalloc(sizeof(*priv)); + if (priv == NULL) + { + return -ENOMEM; + } + + priv->hdev = hdev; + hdev->priv = priv; + + vqnames[VHOST_NET_PEER_RXQ] = "vhost_net_peer_rx"; + vqnames[VHOST_NET_PEER_TXQ] = "vhost_net_peer_tx"; + callbacks[VHOST_NET_PEER_RXQ] = vhost_net_txdone; + callbacks[VHOST_NET_PEER_TXQ] = vhost_net_rxready; + ret = vhost_create_virtqueues(hdev, 0, VHOST_NET_NUM, vqnames, + callbacks, NULL); + if (ret < 0) + { + vhosterr("vhost_create_virtqueues failed, ret=%d\n", ret); + goto err_with_priv; + } + + priv->txq = hdev->vrings_info[VHOST_NET_PEER_RXQ].vq; + priv->rxq = hdev->vrings_info[VHOST_NET_PEER_TXQ].vq; + + priv->lower.quota[NETPKT_RX] = VHOST_NET_NPKTS; + priv->lower.quota[NETPKT_TX] = VHOST_NET_NPKTS; + priv->lower.ops = &g_vhost_net_ops; + + /* Software-assigned MAC (no MAC config space without negotiated + * features); see CONFIG_DRIVERS_VHOST_NET_MACADDR. + */ + + mac = priv->lower.netdev.d_mac.ether.ether_addr_octet; + mac[0] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 5)) & 0xff; + mac[1] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 4)) & 0xff; + mac[2] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 3)) & 0xff; + mac[3] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 2)) & 0xff; + mac[4] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 1)) & 0xff; + mac[5] = (CONFIG_DRIVERS_VHOST_NET_MACADDR >> (8 * 0)) & 0xff; + + ret = netdev_lower_register(&priv->lower, NET_LL_ETHERNET); + if (ret < 0) + { + vhosterr("netdev_lower_register failed, ret=%d\n", ret); + goto err_with_vqs; + } + + return OK; + +err_with_vqs: + vhost_delete_virtqueues(hdev); +err_with_priv: + kmm_free(priv); + hdev->priv = NULL; + return ret; +} + +/**************************************************************************** + * Name: vhost_net_remove + ****************************************************************************/ + +static void vhost_net_remove(FAR struct vhost_device *hdev) +{ + FAR struct vhost_net_priv_s *priv = hdev->priv; + + netdev_lower_unregister(&priv->lower); + vhost_delete_virtqueues(hdev); + kmm_free(priv); + hdev->priv = NULL; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: vhost_register_net_driver + ****************************************************************************/ + +int vhost_register_net_driver(void) +{ + return vhost_register_driver(&g_vhost_net_driver); +} diff --git a/drivers/vhost/vhost-net.h b/drivers/vhost/vhost-net.h new file mode 100644 index 00000000000..17133e6c1c6 --- /dev/null +++ b/drivers/vhost/vhost-net.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * drivers/vhost/vhost-net.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_VHOST_VHOST_NET_H +#define __DRIVERS_VHOST_VHOST_NET_H + +int vhost_register_net_driver(void); + +#endif /* __DRIVERS_VHOST_VHOST_NET_H */ diff --git a/include/nuttx/vhost/vhost.h b/include/nuttx/vhost/vhost.h index 60f32db1cc8..e46cd1015e8 100644 --- a/include/nuttx/vhost/vhost.h +++ b/include/nuttx/vhost/vhost.h @@ -109,6 +109,17 @@ int vhost_get_vq_buffers_pa(FAR struct virtqueue *vq, FAR struct vhost_buf_s *vb, size_t vbsize, FAR size_t *vbcnt); +#ifdef CONFIG_ARCH_HAVE_VHOST_IOMAP +/* Arch-provided: map a peer 64-bit physical address into CPU-reachable + * memory. Returns the mapped VA; *avail (if non-NULL) receives the number + * of contiguous bytes reachable from it. The mapping may be invalidated + * by the next call (e.g. a sliding hardware window), so callers must + * serialize use. + */ + +FAR void *up_vhost_iomap(uint64_t pa, FAR size_t *avail); +#endif + /**************************************************************************** * Name: vhost_register_drivers ****************************************************************************/