Files
rt-thread/components/drivers/virtio/virtio-net.c
T
GuEe-GUI 8517f2d086 [dm][virtio] support virtio dm
1. version for v1.2
2. support packed queue
1. support MMIO/PCI over bus
2. new virtio queue api with dma/cpu sync
3. update for virtio device drivers:
  - net
  - block
  - console
  - input
  - gpu
4. new virtio device drivers:
  - 9p
  - crypto
  - rng
  - scmi
  - scsi

Signed-off-by: GuEe-GUI <2991707448@qq.com>
2026-06-15 10:00:57 +08:00

424 lines
9.7 KiB
C

/*
* Copyright (c) 2006-2023, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2021-11-11 GuEe-GUI the first version
* 2023-02-25 GuEe-GUI using virtio dm
*/
#include <rthw.h>
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "virtio.dev.net"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#include <mm_aspace.h>
#include <netif/ethernetif.h>
#include "virtio_config/virtio-net.h"
#define QUEUE_RX(n) (n)
#define QUEUE_TX(n) ((n) + 1)
rt_packed(struct net_packet
{
struct virtio_net_hdr hdr;
rt_uint8_t mss[VIRTIO_NET_MSS];
});
struct virtio_net
{
struct eth_device parent;
struct rt_virtio_device *vdev;
rt_uint16_t status;
int max_pairs;
int request_num;
void (*virtqueue_add_net_packet)(struct rt_virtqueue *, struct net_packet *, rt_size_t);
struct rt_virtqueue *vqs[RT_CPUS_NR * 2];
rt_list_t rx_vq_nodes;
struct rt_spinlock rx_lock;
struct net_packet *tx_packet;
struct net_packet *rx_packet;
};
#define raw_to_virtio_net(raw) \
rt_container_of(rt_container_of(raw, struct eth_device, parent), struct virtio_net, parent)
static void virtqueue_add_net_packet_split(struct rt_virtqueue *vq,
struct net_packet *packet, rt_size_t len)
{
rt_virtqueue_add_outbuf(vq, packet, sizeof(packet->hdr) + len);
}
static void virtqueue_add_net_packet_packed(struct rt_virtqueue *vq,
struct net_packet *packet, rt_size_t len)
{
rt_virtqueue_add_outbuf(vq, &packet->hdr, sizeof(packet->hdr));
rt_virtqueue_add_outbuf(vq, &packet->mss, len);
}
static rt_err_t virtio_net_tx(rt_device_t dev, struct pbuf *p)
{
struct rt_virtqueue *vq;
struct net_packet *packet;
struct virtio_net *vnet = raw_to_virtio_net(dev);
vq = vnet->vqs[QUEUE_TX(rt_hw_cpu_id() % vnet->max_pairs)];
rt_virtqueue_wait_prepare(vq, vnet->request_num);
packet = &vnet->tx_packet[vq->index * rt_virtqueue_next_buf_index(vq)];
packet->hdr.flags = 0;
packet->hdr.gso_type = 0;
packet->hdr.hdr_len = 0;
packet->hdr.gso_size = 0;
packet->hdr.csum_start = 0;
packet->hdr.csum_offset = 0;
packet->hdr.num_buffers = 0;
RT_ASSERT(p->tot_len <= sizeof(packet->mss));
pbuf_copy_partial(p, &packet->mss, p->tot_len, 0);
vnet->virtqueue_add_net_packet(vq, packet, p->tot_len);
rt_virtqueue_kick(vq);
return RT_EOK;
}
static struct pbuf *virtio_net_rx(rt_device_t dev)
{
rt_size_t size;
rt_ubase_t level;
struct pbuf *p = RT_NULL;
struct net_packet *packet;
struct rt_virtqueue *vq;
struct virtio_net *vnet = raw_to_virtio_net(dev);
level = rt_spin_lock_irqsave(&vnet->rx_lock);
rt_list_for_each_entry(vq, &vnet->rx_vq_nodes, user_list)
{
packet = rt_virtqueue_read_buf(vq, &size);
if (!packet)
{
rt_list_remove(&vq->user_list);
break;
}
size -= sizeof(packet->hdr);
rt_spin_unlock_irqrestore(&vnet->rx_lock, level);
p = pbuf_alloc(PBUF_RAW, size, PBUF_RAM);
if (!p)
{
return RT_NULL;
}
rt_memcpy(p->payload, &packet->mss, size);
level = rt_spin_lock_irqsave(&vnet->rx_lock);
if (!rt_virtqueue_poll(vq, packet - vnet->rx_packet))
{
rt_list_remove(&vq->user_list);
}
rt_virtqueue_add_inbuf(vq, packet, sizeof(*packet));
rt_virtqueue_kick(vq);
break;
}
rt_spin_unlock_irqrestore(&vnet->rx_lock, level);
return p;
}
static rt_err_t virtio_net_control(rt_device_t dev, int cmd, void *args)
{
rt_err_t err = RT_EOK;
rt_uint8_t *mac;
struct virtio_net *vnet = raw_to_virtio_net(dev);
switch (cmd)
{
case NIOCTL_GADDR:
mac = args;
if (!mac)
{
err = -RT_EINVAL;
break;
}
for (int i = 0; i < 6; ++i)
{
rt_virtio_read_config(vnet->vdev, struct virtio_net_config, mac[i], &mac[i]);
}
break;
default:
err = -RT_EINVAL;
break;
}
return err;
}
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops virtio_net_ops =
{
.control = virtio_net_control,
};
#endif
static void virtio_net_rx_done(struct rt_virtqueue *vq)
{
rt_ubase_t level;
struct virtio_net *vnet = vq->vdev->priv;
level = rt_spin_lock_irqsave(&vnet->rx_lock);
rt_list_insert_before(&vnet->rx_vq_nodes, &vq->user_list);
rt_spin_unlock_irqrestore(&vnet->rx_lock, level);
eth_device_ready(&vnet->parent);
}
static void virtio_net_tx_done(struct rt_virtqueue *vq)
{
rt_virtqueue_read_buf(vq, RT_NULL);
}
static void virtio_net_config_changed(struct rt_virtio_device *vdev)
{
rt_uint16_t status;
rt_bool_t link_up;
struct virtio_net *vnet = vdev->priv;
if (!rt_virtio_has_feature(vdev, VIRTIO_NET_F_STATUS))
{
return;
}
rt_virtio_read_config(vdev, struct virtio_net_config, status, &status);
/* Remove other status bit */
status &= VIRTIO_NET_S_LINK_UP;
if (vnet->status == status)
{
/* Status no change */
return;
}
link_up = !!((vnet->status = status) & VIRTIO_NET_S_LINK_UP);
LOG_D("%s linkchange to %s", rt_dm_dev_get_name(&vdev->parent), link_up ? "up" : "down");
eth_device_linkchange(&vnet->parent, link_up);
}
static rt_err_t virtio_net_vq_init(struct virtio_net *vnet)
{
rt_err_t err;
rt_size_t vqs_nr = 2, qsize;
const char *names[RT_ARRAY_SIZE(vnet->vqs)];
rt_virtqueue_callback cbs[RT_ARRAY_SIZE(vnet->vqs)];
struct rt_virtio_device *vdev = vnet->vdev;
if (rt_virtio_has_feature(vdev, VIRTIO_NET_F_MQ) ||
rt_virtio_has_feature(vdev, VIRTIO_NET_F_RSS))
{
rt_virtio_read_config(vdev, struct virtio_net_config, max_virtqueue_pairs, &vqs_nr);
vqs_nr *= 2;
}
for (int i = 0; i < vqs_nr; i += 2)
{
int rx = QUEUE_RX(i), tx = QUEUE_TX(i);
names[rx] = "rx";
names[tx] = "tx";
cbs[rx] = &virtio_net_rx_done;
cbs[tx] = &virtio_net_tx_done;
}
vnet->max_pairs = vqs_nr / 2;
err = rt_virtio_virtqueue_install(vdev, vqs_nr, vnet->vqs, names, cbs);
if (err)
{
return err;
}
qsize = rt_virtqueue_get_virtq_size(vnet->vqs[QUEUE_TX(0)]);
if (!(vnet->tx_packet = rt_malloc(sizeof(vnet->tx_packet[0]) * vqs_nr * qsize)))
{
return -RT_ENOMEM;
}
qsize = rt_virtqueue_get_virtq_size(vnet->vqs[QUEUE_RX(0)]);
if (!(vnet->rx_packet = rt_malloc(sizeof(vnet->rx_packet[0]) * vqs_nr * qsize)))
{
return -RT_ENOMEM;
}
for (int i = 0; i < vqs_nr; i += 2)
{
int rx = QUEUE_RX(i);
struct net_packet *packet;
struct rt_virtqueue *vq = vnet->vqs[rx];
packet = &vnet->rx_packet[rx * qsize];
for (int idx = 0; idx < qsize; ++idx, ++packet)
{
rt_virtqueue_add_inbuf(vq, packet, sizeof(*packet));
rt_virtqueue_submit(vq);
}
rt_virtqueue_notify(vq);
}
return RT_EOK;
}
static void virtio_net_vq_finit(struct virtio_net *vnet)
{
if (vnet->vqs[0])
{
rt_virtio_virtqueue_release(vnet->vdev);
}
if (vnet->tx_packet)
{
rt_free(vnet->tx_packet);
}
if (vnet->rx_packet)
{
rt_free(vnet->rx_packet);
}
}
static rt_err_t virtio_net_probe(struct rt_virtio_device *vdev)
{
rt_err_t err = RT_EOK;
struct virtio_net *vnet = rt_calloc(1, sizeof(*vnet));
if (!vnet)
{
return -RT_ENOMEM;
}
vdev->priv = vnet;
vnet->vdev = vdev;
vdev->parent.user_data = vnet;
#ifdef RT_USING_DEVICE_OPS
vnet->parent.parent.ops = &virtio_net_ops;
#else
vnet->parent.parent.control = virtio_net_control;
#endif
vnet->parent.eth_tx = virtio_net_tx;
vnet->parent.eth_rx = virtio_net_rx;
if (rt_virtio_has_feature(vdev, VIRTIO_F_RING_PACKED))
{
vnet->request_num = 2;
vnet->virtqueue_add_net_packet = &virtqueue_add_net_packet_packed;
}
else
{
vnet->request_num = 1;
vnet->virtqueue_add_net_packet = &virtqueue_add_net_packet_split;
}
if ((err = virtio_net_vq_init(vnet)))
{
goto _fail;
}
rt_list_init(&vnet->rx_vq_nodes);
rt_spin_lock_init(&vnet->rx_lock);
if ((err = rt_dm_dev_set_name_auto(&vnet->parent.parent, "e")) < 0)
{
goto _fail;
}
if ((err = eth_device_init(&vnet->parent, rt_dm_dev_get_name(&vnet->parent.parent))))
{
goto _fail;
}
eth_device_linkchange(&vnet->parent, RT_TRUE);
return RT_EOK;
_fail:
virtio_net_vq_finit(vnet);
rt_free(vnet);
return err;
}
static rt_err_t virtio_net_remove(struct rt_virtio_device *vdev)
{
struct virtio_net *vnet = vdev->parent.user_data;
eth_device_linkchange(&vnet->parent, RT_FALSE);
eth_device_deinit(&vnet->parent);
virtio_net_vq_finit(vnet);
rt_free(vnet);
return RT_EOK;
}
static const struct rt_virtio_device_id virtio_net_ids[] =
{
{ VIRTIO_DEVICE_ID_NET, VIRTIO_DEVICE_ANY_ID },
{ /* sentinel */ }
};
static struct rt_virtio_driver virtio_net_driver =
{
.ids = virtio_net_ids,
.features =
RT_BIT(VIRTIO_NET_F_MTU)
| RT_BIT(VIRTIO_NET_F_MAC)
| RT_BIT(VIRTIO_NET_F_MRG_RXBUF)
| RT_BIT(VIRTIO_NET_F_STATUS)
| RT_BIT(VIRTIO_NET_F_CTRL_RX)
| RT_BIT(VIRTIO_NET_F_CTRL_VLAN)
| RT_BIT(VIRTIO_NET_F_CTRL_RX_EXTRA)
| RT_BIT(VIRTIO_NET_F_GUEST_ANNOUNCE)
| RT_BIT(VIRTIO_NET_F_MQ)
| RT_BIT(VIRTIO_NET_F_CTRL_MAC_ADDR)
| RT_BIT(VIRTIO_NET_F_SPEED_DUPLEX)
| RT_BIT(VIRTIO_F_ANY_LAYOUT),
.probe = virtio_net_probe,
.remove = virtio_net_remove,
.config_changed = virtio_net_config_changed,
};
RT_VIRTIO_DRIVER_EXPORT(virtio_net_driver);