drivers/vhost: Add vhost-net, a device-role virtio network driver.

Implements the device end of virtio-net, so a peer running the stock
virtio-net driver sees this side as a network card, and registers a netdev
lowerhalf.

Ring layout follows the peer's numbering: vq[0] is its RX queue, which we fill
to transmit, and vq[1] its TX queue, which we harvest.  No features are
negotiated, so every frame carries the zeroed legacy virtio_net_hdr.

Peer buffers are reached by raw 64-bit address through an arch-provided
translation window -- the AM67 RAT, identity mapping elsewhere -- splitting
copies that straddle it.

Also gives DRIVERS_VHOST a prompt; it was promptless and so unselectable
without a driver forcing it.

Verified on t3-gem-o1 against an unmodified Linux virtio_net: eth0 registers,
ifup brings it to RUNNING, and the peer pings it 5/5 at 0.27 ms and 60/60 with
0% loss.

Assisted-by: Claude Code:claude-fable-5
Signed-off-by: Ulaş Sertan Kemeç <sertan.usk@gmail.com>
This commit is contained in:
Ulaş Sertan Kemeç 2026-08-05 10:32:55 +03:00 • committed by Alan C. Assis
parent c77c981850
commit fa935ecae1
8 changed files with 591 additions and 1 deletions

View file

@ -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

View file

@ -53,6 +53,7 @@
#include "arm_internal.h"
#include "am67_rat.h"
#include <nuttx/vhost/vhost.h>
/****************************************************************************
* 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

View file

@ -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()

View file

@ -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

View file

@ -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

495
drivers/vhost/vhost-net.c Normal file
View file

@ -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 <nuttx/config.h>
#include <debug.h>
#include <string.h>
#include <sys/param.h>
#include <nuttx/kmalloc.h>
#include <nuttx/net/netdev_lowerhalf.h>
#include <nuttx/vhost/vhost.h>
#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);
}

28
drivers/vhost/vhost-net.h Normal file
View file

@ -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 */

View file

@ -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
****************************************************************************/