Skip to content

Commit 198ea5c

Browse files
Migrate virtio-net from semu
This commit migrates virtio-net support from semu with the following modifications: 1. Implement virtio-net device model The virtio-net implementation follows the VirtIO-MMIO flow used by virtio-blk, including feature negotiation, queue setup, QueueNotify handling, used ring update, interrupt status, and device status reset. The device currently supports a TAP-backend network interface and handles basic RX/TX virtqueue processing for guest network packets. 2. Add TAP backend helper Introduce netdev.c and netdev.h to provide host-side TAP device access. Future work may support other host-side backend. 3. Handle virtio-net header processing For guest TX, the device skips the virtio-net header before writing the Ethernet frame to the TAP backend. For guest RX, the device prepends a virtio-net header before copying the received Ethernet frame into the guest-provided RX buffer. 4. Implement MMIO_VIRTIONET Add MMIO routing for virtio-net and connect the device interrupt status to the PLIC, following the existing virtio-blk and virtio-rng interrupt update model. 5. Introduce new argument '-x vnet:<tap>' When virtio-net is enabled, rv32emu dynamically creates a virtio-mmio node in the generated device tree and assigns an MMIO base address and IRQ for the device. 6. Support coexistence with virtio-blk and virtio-rng Update the dynamic virtio-mmio device tree allocation path so virtio-net can coexist with existing virtio-blk and virtio-rng devices without reusing MMIO base addresses or IRQs. 7. Use virtio-net state Unlike semu's device integration model, rv32emu stores the virtio-net state in vm_attr_t so MMIO routing, interrupt routing, and device cleanup can access the same device instance. The emulator should be run with sudo when using the virtio-net TAP backend. Co-authored-by: Jim Huang <jserv@biilabs.io>
1 parent 02be10d commit 198ea5c

11 files changed

Lines changed: 1091 additions & 5 deletions

File tree

.ci/test-netdev.sh

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/usr/bin/env bash
2+
3+
set -euo pipefail
4+
5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
. "${SCRIPT_DIR}/common.sh"
7+
8+
check_platform
9+
10+
if [[ "${OS_TYPE}" != "Linux" ]]; then
11+
print_warning "Skipping virtio-net test on non-Linux host"
12+
exit 0
13+
fi
14+
15+
register_cleanup cleanup_emulator
16+
17+
TIMEOUT=${BOOT_TIMEOUT:-60}
18+
19+
ASSERT expect <<- DONE
20+
set timeout ${TIMEOUT}
21+
set tap_if ""
22+
23+
spawn sudo -E build/rv32emu \
24+
-k build/linux-image/Image \
25+
-i build/linux-image/rootfs.cpio \
26+
-x vnet:tap
27+
28+
expect {
29+
-re {allocated TAP interface: (tap[0-9]+)} {
30+
set tap_if \$expect_out(1,string)
31+
exp_continue
32+
}
33+
"buildroot login:" {
34+
if { "\$tap_if" == "" } {
35+
set tap_if [exec sh -c {ip -o link show | awk -F': ' '\$2 ~ /^tap[0-9]+/ {print \$2; exit}'}]
36+
}
37+
38+
exec sudo ip addr replace 192.168.100.1/24 dev \$tap_if
39+
exec sudo ip link set \$tap_if up
40+
41+
send "root\\r"
42+
}
43+
timeout {
44+
exit 1
45+
}
46+
}
47+
48+
expect "# "
49+
send "readlink /sys/bus/virtio/devices/virtio0/driver\\r"
50+
expect {
51+
"virtio_net" {}
52+
timeout { exit 2 }
53+
}
54+
55+
expect "# "
56+
send "ip link set eth0 up\\r"
57+
58+
expect "# "
59+
send "ip addr add 192.168.100.2/24 dev eth0\\r"
60+
61+
expect "# "
62+
send "ip addr show eth0\\r"
63+
expect {
64+
"192.168.100.2/24" {}
65+
timeout { exit 3 }
66+
}
67+
68+
expect "# "
69+
send "ping -c 3 -W 5 192.168.100.1\r"
70+
expect {
71+
-re {3 packets transmitted, 3 packets received|3 packets transmitted, 3 received} {
72+
send "\x01"
73+
send "x"
74+
exit 0
75+
}
76+
timeout {
77+
exit 4
78+
}
79+
DONE
80+
81+
print_success "virtio-net boot test passed"

.github/workflows/main.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,14 @@ jobs:
444444
fi
445445
bash -c "${BOOT_LINUX_TEST}"
446446
447+
- name: virtio-net boot test
448+
if: success() && matrix.compiler == 'gcc' && matrix.boot_type == 'interpreter'
449+
env:
450+
CC: ${{ steps.install_cc.outputs.cc }}
451+
BOOT_TIMEOUT: 60
452+
run: |
453+
.ci/test-netdev.sh
454+
447455
# Native AArch64 on GitHub ARM runners - fast, no QEMU overhead
448456
host-arm64:
449457
needs: [detect-code-related-file-changes]

src/devices/netdev.c

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#include "netdev.h"
7+
8+
#include <stdbool.h>
9+
#include <string.h>
10+
11+
#include "utils.h"
12+
13+
#if defined(__linux__) && !defined(__EMSCRIPTEN__)
14+
15+
#include <errno.h>
16+
#include <fcntl.h>
17+
#include <linux/if.h>
18+
#include <linux/if_tun.h>
19+
#include <stdlib.h>
20+
#include <sys/ioctl.h>
21+
#include <unistd.h>
22+
23+
static int net_init_tap(netdev_t *netdev)
24+
{
25+
net_tap_options_t *tap = (net_tap_options_t *) netdev->op;
26+
27+
tap->tap_fd = open("/dev/net/tun", O_RDWR);
28+
if (tap->tap_fd < 0) {
29+
rv_log_error("failed to open TAP device: %s", strerror(errno));
30+
return -1;
31+
}
32+
33+
struct ifreq ifreq = {
34+
.ifr_flags = IFF_TAP | IFF_NO_PI,
35+
};
36+
37+
strncpy(ifreq.ifr_name, "tap%d", sizeof(ifreq.ifr_name) - 1);
38+
39+
if (ioctl(tap->tap_fd, TUNSETIFF, &ifreq) < 0) {
40+
rv_log_error("failed to allocate TAP device: %s", strerror(errno));
41+
close(tap->tap_fd);
42+
tap->tap_fd = -1;
43+
return -1;
44+
}
45+
46+
rv_log_info("allocated TAP interface: %s", ifreq.ifr_name);
47+
48+
if (fcntl(tap->tap_fd, F_SETFL,
49+
fcntl(tap->tap_fd, F_GETFL, 0) | O_NONBLOCK) < 0) {
50+
rv_log_error("failed to set TAP non-blocking mode: %s",
51+
strerror(errno));
52+
close(tap->tap_fd);
53+
tap->tap_fd = -1;
54+
return -1;
55+
}
56+
57+
return 0;
58+
}
59+
60+
bool netdev_init(netdev_t *netdev, const char *net_type)
61+
{
62+
if (!netdev || !net_type || strcmp(net_type, "tap"))
63+
return false;
64+
65+
netdev->name = (char *) net_type;
66+
netdev->type = NETDEV_IMPL_tap;
67+
netdev->op = calloc(1, sizeof(net_tap_options_t));
68+
if (!netdev->op)
69+
return false;
70+
71+
if (net_init_tap(netdev) < 0) {
72+
free(netdev->op);
73+
netdev->op = NULL;
74+
return false;
75+
}
76+
77+
return true;
78+
}
79+
80+
void netdev_delete(netdev_t *netdev)
81+
{
82+
if (!netdev || !netdev->op)
83+
return;
84+
85+
switch (netdev->type) {
86+
case NETDEV_IMPL_tap: {
87+
net_tap_options_t *tap = (net_tap_options_t *) netdev->op;
88+
if (tap->tap_fd >= 0)
89+
close(tap->tap_fd);
90+
break;
91+
}
92+
default:
93+
break;
94+
}
95+
96+
free(netdev->op);
97+
netdev->op = NULL;
98+
}
99+
100+
#else
101+
102+
bool netdev_init(netdev_t *netdev, const char *net_type)
103+
{
104+
(void) netdev;
105+
(void) net_type;
106+
107+
rv_log_error("virtio-net TAP backend is only supported on Linux hosts");
108+
return false;
109+
}
110+
111+
void netdev_delete(netdev_t *netdev)
112+
{
113+
(void) netdev;
114+
}
115+
116+
#endif

src/devices/netdev.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* rv32emu is freely redistributable under the MIT License. See the file
3+
* "LICENSE" for information on usage and redistribution of this file.
4+
*/
5+
6+
#pragma once
7+
8+
#include <stdbool.h>
9+
10+
typedef struct netdev netdev_t;
11+
12+
typedef enum {
13+
NETDEV_IMPL_tap,
14+
} netdev_impl_t;
15+
16+
typedef struct {
17+
int tap_fd;
18+
} net_tap_options_t;
19+
20+
struct netdev {
21+
char *name;
22+
netdev_impl_t type;
23+
void *op;
24+
};
25+
26+
bool netdev_init(netdev_t *netdev, const char *net_type);
27+
28+
void netdev_delete(netdev_t *netdev);

0 commit comments

Comments
 (0)