Skip to content

Commit 91ac587

Browse files
Jakub Klimczakhenrikbrixandersen
authored andcommitted
drivers: ethernet: Add VIRTIO Network Device
Add a driver for the VIRTIO Ethernet device. This is a minimal driver which sets a MAC address and transmits packets, but does not support any extra features like the control channel or checksum offloading. Confirmed to work with the networking subsystem samples. For example, the zperf sample shows a result of 85 Mbps download and 14 Mbps upload. Signed-off-by: Jakub Klimczak <[email protected]>
1 parent 3cf2627 commit 91ac587

File tree

5 files changed

+294
-0
lines changed

5 files changed

+294
-0
lines changed

drivers/ethernet/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ zephyr_library_sources_ifdef(CONFIG_ETH_LAN9250 eth_lan9250.c)
4646
zephyr_library_sources_ifdef(CONFIG_ETH_SY1XX eth_sensry_sy1xx_mac.c)
4747
zephyr_library_sources_ifdef(CONFIG_ETH_NXP_ENET eth_nxp_enet.c)
4848
zephyr_library_sources_ifdef(CONFIG_ETH_XILINX_AXIENET eth_xilinx_axienet.c)
49+
zephyr_library_sources_ifdef(CONFIG_ETH_VIRTIO_NET eth_virtio_net.c)
4950

5051
if(CONFIG_ETH_NXP_S32_NETC)
5152
zephyr_library_sources(eth_nxp_s32_netc.c)

drivers/ethernet/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ source "drivers/ethernet/Kconfig.test"
7777
source "drivers/ethernet/Kconfig.lan9250"
7878
source "drivers/ethernet/Kconfig.sy1xx_mac"
7979
source "drivers/ethernet/Kconfig.xilinx_axienet"
80+
source "drivers/ethernet/Kconfig.virtio_net"
8081

8182
source "drivers/ethernet/eth_nxp_enet_qos/Kconfig"
8283

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright (c) 2025 Antmicro
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
menuconfig ETH_VIRTIO_NET
5+
bool "VIRTIO networking paravirtualization device driver"
6+
default y
7+
depends on DT_HAS_VIRTIO_NET_ENABLED
8+
select VIRTIO
9+
10+
if ETH_VIRTIO_NET
11+
12+
config ETH_NIC_MODEL
13+
string
14+
default "virtio"
15+
help
16+
Tells QEMU what network model to use. This value is given as
17+
a parameter to the -nic QEMU command line option.
18+
19+
config ETH_VIRTIO_NET_RX_BUFFERS
20+
int "VIRTIO network device receive buffers"
21+
default 4
22+
23+
endif

drivers/ethernet/eth_virtio_net.c

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
/*
2+
* Copyright (c) 2025 Antmicro <www.antmicro.com>
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/devicetree.h>
8+
#include <zephyr/net/ethernet.h>
9+
#include <zephyr/logging/log.h>
10+
#include <zephyr/drivers/virtio.h>
11+
#include <zephyr/drivers/virtio/virtqueue.h>
12+
#include <zephyr/sys/util.h>
13+
#include <zephyr/sys/atomic.h>
14+
#include <zephyr/random/random.h>
15+
#include "eth.h"
16+
17+
#define DT_DRV_COMPAT virtio_net
18+
LOG_MODULE_REGISTER(virtio_net, CONFIG_ETHERNET_LOG_LEVEL);
19+
20+
enum _virtio_feature_bits {
21+
VIRTIO_NET_F_CSUM,
22+
VIRTIO_NET_F_GUEST_CSUM,
23+
VIRTIO_NET_F_CTRL_GUEST_OFFLOADS,
24+
VIRTIO_NET_F_MTU,
25+
VIRTIO_NET_F_MAC = 5,
26+
VIRTIO_NET_F_GUEST_TSO4 = 7,
27+
VIRTIO_NET_F_GUEST_TSO6,
28+
VIRTIO_NET_F_GUEST_ECN,
29+
VIRTIO_NET_F_GUEST_UFO,
30+
VIRTIO_NET_F_HOST_TSO4,
31+
VIRTIO_NET_F_HOST_TSO6,
32+
VIRTIO_NET_F_HOST_ECN,
33+
VIRTIO_NET_F_HOST_UFO,
34+
VIRTIO_NET_F_MRG_RXBUF,
35+
VIRTIO_NET_F_STATUS,
36+
VIRTIO_NET_F_CTRL_VQ,
37+
VIRTIO_NET_F_CTRL_RX,
38+
VIRTIO_NET_F_CTRL_VLAN,
39+
VIRTIO_NET_F_CTRL_RX_EXTRA,
40+
VIRTIO_NET_F_GUEST_ANNOUNCE,
41+
VIRTIO_NET_F_MQ,
42+
VIRTIO_NET_F_CTRL_MAC_ADDR,
43+
VIRTIO_NET_F_HASH_TUNNEL = 51,
44+
VIRTIO_NET_F_VQ_NOTF_COAL,
45+
VIRTIO_NET_F_NOTF_COAL,
46+
VIRTIO_NET_F_GUEST_USO4,
47+
VIRTIO_NET_F_GUEST_USO6,
48+
VIRTIO_NET_F_HOST_USO,
49+
VIRTIO_NET_F_HASH_REPORT,
50+
VIRTIO_NET_F_GUEST_HDRLEN = 59,
51+
VIRTIO_NET_F_RSS,
52+
VIRTIO_NET_F_RSC_EXT,
53+
VIRTIO_NET_F_STANDBY,
54+
VIRTIO_NET_F_SPEED_DUPLEX
55+
};
56+
57+
struct _virtio_net_config {
58+
uint8_t mac[6];
59+
/* More fields exist if certain features are set by the device */
60+
};
61+
62+
/* Header prepending every sent and received Ethernet frame */
63+
struct _virtio_net_hdr {
64+
uint8_t flags;
65+
uint8_t gso_type;
66+
uint16_t hdr_len;
67+
uint16_t gso_size;
68+
uint16_t csum_start;
69+
uint16_t csum_offset;
70+
uint16_t num_buffers;
71+
/* There are three more fields if device has VIRTIO_NET_F_HASH_REPORT set */
72+
};
73+
74+
enum _virtio_net_hdr_flags {
75+
VIRTIO_NET_HDR_F_NEEDS_CSUM = 1,
76+
VIRTIO_NET_HDR_F_DATA_VALID = 2,
77+
VIRTIO_NET_HDR_F_RSC_INFO = 4
78+
};
79+
80+
enum _virtio_net_hdr_gso_types {
81+
VIRTIO_NET_HDR_GSO_NONE,
82+
VIRTIO_NET_HDR_GSO_TCPV4,
83+
VIRTIO_NET_HDR_GSO_UDP = 3,
84+
VIRTIO_NET_HDR_GSO_TCPV6,
85+
VIRTIO_NET_HDR_GSO_UDP_L4,
86+
VIRTIO_NET_HDR_GSO_ECN = 0x80
87+
};
88+
89+
#define VIRTIO_NET_BUFLEN \
90+
(NET_ETH_MTU + sizeof(struct net_eth_hdr) + sizeof(struct _virtio_net_hdr))
91+
/* virtqueue pairs are numbered from 1 upwards */
92+
/* convert pair number to virtqueue index */
93+
#define VIRTQ_RX(n) ((n - 1) * 2)
94+
#define VIRTQ_TX(n) (VIRTQ_RX(n) + 1)
95+
96+
struct virtnet_config {
97+
const struct device *vdev;
98+
bool random_mac;
99+
unsigned int inst;
100+
};
101+
102+
/* Allows virtnet_rx_cb to know which virtqueue it was called by */
103+
struct _rx_cb_data {
104+
struct virtnet_data *data;
105+
uint16_t buf_no;
106+
};
107+
108+
struct virtnet_data {
109+
const struct device *dev;
110+
struct net_if *iface;
111+
const struct _virtio_net_config *virtio_devcfg;
112+
uint8_t mac[6];
113+
struct _rx_cb_data rx_cb_data[CONFIG_ETH_VIRTIO_NET_RX_BUFFERS];
114+
uint8_t txb[VIRTIO_NET_BUFLEN];
115+
uint8_t rxb[CONFIG_ETH_VIRTIO_NET_RX_BUFFERS][VIRTIO_NET_BUFLEN];
116+
};
117+
118+
static uint16_t virtnet_enum_queues_cb(uint16_t q_index, uint16_t q_size_max, void *)
119+
{
120+
if (q_index % 2 == 0) { /* receiving virtqueue (even-numbered) */
121+
return CONFIG_ETH_VIRTIO_NET_RX_BUFFERS;
122+
} else {
123+
return 1;
124+
}
125+
}
126+
127+
static enum ethernet_hw_caps virtnet_get_capabilities(const struct device *dev)
128+
{
129+
return ETHERNET_LINK_10BASE | ETHERNET_LINK_100BASE | ETHERNET_LINK_1000BASE |
130+
ETHERNET_LINK_2500BASE | ETHERNET_LINK_5000BASE;
131+
}
132+
133+
static int virtnet_send(const struct device *dev, struct net_pkt *pkt)
134+
{
135+
const struct virtnet_config *config = dev->config;
136+
struct virtnet_data *data = dev->data;
137+
size_t len = net_pkt_get_len(pkt);
138+
139+
if (net_pkt_read(pkt, data->txb + sizeof(struct _virtio_net_hdr), len)) {
140+
LOG_ERR("could not read contents of packet to be sent");
141+
return -EIO;
142+
}
143+
144+
struct virtq *vq = virtio_get_virtqueue(config->vdev, VIRTQ_TX(1));
145+
struct virtq_buf vqbuf[] = {
146+
{.addr = data->txb, .len = sizeof(struct _virtio_net_hdr) + len}};
147+
148+
if (virtq_add_buffer_chain(vq, vqbuf, 1, 1, NULL, NULL, K_FOREVER)) {
149+
LOG_ERR("could not send packet");
150+
return -EIO;
151+
}
152+
virtio_notify_virtqueue(config->vdev, VIRTQ_TX(1));
153+
return 0;
154+
}
155+
156+
void virtnet_rx_cb(void *priv, uint32_t len)
157+
{
158+
const struct _rx_cb_data *p = priv;
159+
struct virtnet_data *data = p->data;
160+
uint16_t buf_no = p->buf_no;
161+
const struct virtnet_config *config = data->dev->config;
162+
struct virtq *vq = virtio_get_virtqueue(config->vdev, VIRTQ_RX(1));
163+
164+
len -= sizeof(struct _virtio_net_hdr);
165+
struct net_pkt *pkt =
166+
net_pkt_rx_alloc_with_buffer(data->iface, len, AF_UNSPEC, 0, K_FOREVER);
167+
168+
if (pkt == NULL) {
169+
LOG_ERR("received packet, but could not pass it to the operating system");
170+
} else if (net_pkt_write(pkt, &(data->rxb[buf_no][sizeof(struct _virtio_net_hdr)]), len)) {
171+
LOG_ERR("could not copy entire received packet");
172+
net_pkt_unref(pkt);
173+
} else if (net_recv_data(data->iface, pkt)) {
174+
LOG_ERR("operating system failed to receive packet");
175+
net_pkt_unref(pkt);
176+
} else {
177+
/* Packet received correctly, no error */
178+
}
179+
struct virtq_buf vqbuf[] = {{.addr = &(data->rxb[buf_no]), .len = VIRTIO_NET_BUFLEN}};
180+
181+
virtq_add_buffer_chain(vq, vqbuf, 1, 0, virtnet_rx_cb, priv, K_FOREVER);
182+
virtio_notify_virtqueue(config->vdev, VIRTQ_RX(1));
183+
}
184+
185+
static void virtnet_if_init(struct net_if *iface)
186+
{
187+
ethernet_init(iface);
188+
const struct device *dev = net_if_get_device(iface);
189+
struct virtnet_data *data = dev->data;
190+
const struct virtnet_config *config = dev->config;
191+
192+
if (dev == NULL) {
193+
LOG_ERR("could not access device structure!");
194+
return;
195+
}
196+
data->iface = iface;
197+
net_if_set_link_addr(iface, data->mac, sizeof(data->virtio_devcfg->mac), NET_LINK_ETHERNET);
198+
struct virtq *vq = virtio_get_virtqueue(config->vdev, VIRTQ_RX(1));
199+
200+
for (int i = 0; i < CONFIG_ETH_VIRTIO_NET_RX_BUFFERS; i++) {
201+
data->rx_cb_data[i].data = data;
202+
data->rx_cb_data[i].buf_no = i;
203+
204+
struct virtq_buf vqbuf[] = {{.addr = &(data->rxb[i]), .len = VIRTIO_NET_BUFLEN}};
205+
206+
virtq_add_buffer_chain(vq, vqbuf, 1, 0, virtnet_rx_cb, &(data->rx_cb_data[i]),
207+
K_FOREVER);
208+
virtio_notify_virtqueue(config->vdev, VIRTQ_RX(1));
209+
}
210+
LOG_DBG("initialization finished");
211+
}
212+
213+
static int virtnet_dev_init(const struct device *dev)
214+
{
215+
const struct virtnet_config *config = dev->config;
216+
struct virtnet_data *data = dev->data;
217+
218+
if (config->random_mac) {
219+
sys_rand_get(data->mac, sizeof(data->mac));
220+
/* make it a locally administered, unicast address */
221+
/* (2nd hex digit of 1st byte is 2) */
222+
data->mac[0] &= ~(BIT(0) | BIT(2) | BIT(3));
223+
data->mac[0] |= BIT(1);
224+
}
225+
226+
data->virtio_devcfg = virtio_get_device_specific_config(config->vdev);
227+
if (data->virtio_devcfg == NULL) {
228+
LOG_ERR("could not get config struct");
229+
}
230+
if (virtio_commit_feature_bits(config->vdev)) {
231+
LOG_ERR("could not commit feature bits");
232+
}
233+
LOG_DBG("MAC address is %02x:%02x:%02x:%02x:%02x:%02x", data->mac[0], data->mac[1],
234+
data->mac[2], data->mac[3], data->mac[4], data->mac[5]);
235+
236+
virtio_init_virtqueues(config->vdev, 2, virtnet_enum_queues_cb, NULL);
237+
virtio_finalize_init(config->vdev);
238+
239+
return 0;
240+
}
241+
242+
static struct ethernet_api virtnet_api = {
243+
.iface_api.init = virtnet_if_init,
244+
.get_capabilities = virtnet_get_capabilities,
245+
.send = virtnet_send,
246+
};
247+
248+
#define VIRTIO_NET_DEFINE(inst) \
249+
static struct virtnet_data virtnet_data_##inst = { \
250+
.dev = DEVICE_DT_INST_GET(inst), \
251+
.mac = DT_INST_PROP_OR(inst, local_mac_address, {0}), \
252+
}; \
253+
static const struct virtnet_config virtnet_config_##inst = { \
254+
.vdev = DEVICE_DT_GET(DT_INST_PARENT(inst)), \
255+
.random_mac = DT_INST_PROP(inst, zephyr_random_mac_address), \
256+
}; \
257+
ETH_NET_DEVICE_DT_INST_DEFINE(inst, virtnet_dev_init, NULL, &virtnet_data_##inst, \
258+
&virtnet_config_##inst, CONFIG_ETH_INIT_PRIORITY, \
259+
&virtnet_api, NET_ETH_MTU);
260+
261+
DT_INST_FOREACH_STATUS_OKAY(VIRTIO_NET_DEFINE);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) 2025 Antmicro <www.antmicro.com>
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
description: VIRTIO Network device
5+
6+
compatible: "virtio,net"
7+
8+
include: [ethernet-controller.yaml]

0 commit comments

Comments
 (0)