Skip to content

Migrate virtio-net from semu - #748

Open
Charlie-Tsai1123 wants to merge 7 commits into
sysprog21:masterfrom
Charlie-Tsai1123:research/virtio-net
Open

Migrate virtio-net from semu#748
Charlie-Tsai1123 wants to merge 7 commits into
sysprog21:masterfrom
Charlie-Tsai1123:research/virtio-net

Conversation

@Charlie-Tsai1123

@Charlie-Tsai1123 Charlie-Tsai1123 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR migrates virtio-net support from semu and follows the existing virtio-blk and virtio-rng integration in rv32emu.

The implementation adds a TAP-backed virtio-net device model for system emulation mode, including MMIO register handling, queue setup, feature negotiation, RX/TX virtqueue handling, virtio-net header processing, interrupt delivery through the PLIC, dynamic DTB node creation, and a runtime option for enabling the device.

Implementation notes

  • Add a TAP backend helper for host network I/O.
  • Add a virtio-net device model with RX and TX virtqueue handling.
  • Handle the virtio-net header on both guest TX and RX paths.
  • Route virtio-net MMIO accesses through the system MMIO path.
  • Update virtio-net interrupts through the PLIC.
  • Dynamically create a virtio,mmio DTB node for virtio-net.
  • Ensure virtio-net can coexist with existing virtio-blk and virtio-rng devices without reusing MMIO bases or IRQs.
  • Add a runtime option for enabling virtio-net with a TAP backend.
  • Note that opening a TAP device usually requires elevated privileges, so rv32emu should be executed with sudo or equivalent permissions when using the TAP backend.

Test

Build:

make system_defconfig
make ENABLE_SYSTEM=1

sudo build/rv32emu \
  -k build/linux-image/Image \
  -i build/linux-image/rootfs.cpio \
  -x vblk:build/disks/disk.img \
  -x vrng \
  -x vnet:tap

After run rv32emu with -x vnet:tap rv32emu would build TAP, so host linux doesn't need to build TAP again.
Host TAP setup:

ip link | grep tap
sudo ip addr add 192.168.100.1/24 dev tap0
sudo ip link set  tap0 up
sudo tcpdump -i tap0 -n -vv

Guest device verification:

readlink /sys/bus/virtio/devices/virtio0/driver
ip link set eth0 up
ip addr add 192.168.100.2/24 dev eth0
ip addr show eth0
ping -c 3 192.168.100.1

Expected Result:

# readlink /sys/bus/virtio/devices/virtio0/driver
../../../../../bus/virtio/drivers/virtio_net
# ip link set eth0 up
# ip addr add 192.168.100.2/24 dev eth0
# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether aa:e0:56:5b:58:bc brd ff:ff:ff:ff:ff:ff
    inet 192.168.100.2/24 scope global eth0
       valid_lft forever preferred_lft forever
# ping -c 3 192.168.100.1
PING 192.168.100.1 (192.168.100.1): 56 data bytes
64 bytes from 192.168.100.1: seq=0 ttl=64 time=1.200 ms

64 bytes from 192.168.100.1: seq=1 ttl=64 time=1001.085 ms
64 bytes from 192.168.100.1: seq=2 ttl=64 time=1.334 ms

--- 192.168.100.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 1.200/334.539/1001.085 ms
# 

Summary by cubic

Adds a virtio-net device with TAP and user-mode SLIRP backends over VirtIO‑MMIO and the PLIC. Backends are build-time configurable and selected at runtime with -x vnet:tap or -x vnet:user; the device is added to the DTB and covered by Linux and macOS boot tests.

  • New Features

    • Virtio-net: feature negotiation, RX/TX queues, virtio-net header (TX skip, RX prepend), QueueNotify/used ring, reset; queues are polled each loop and drive SLIRP via non-blocking socketpairs and a simple timer.
    • Backends: Linux tap and user (SLIRP) with non-blocking I/O; macOS supports user; Emscripten disables networking; one vnet device only; CLI validates backend and lists only compiled backends.
    • Build/Config: Kconfig switches CONFIG_VIRTIO_NET, CONFIG_VIRTIO_NET_TAP, CONFIG_VIRTIO_NET_USER; exclude unused net objects; build src/minislirp only when user is enabled (macOS links -lresolv); MMIO/DTB/interrupt logic is compiled only when enabled.
    • CLI/CI/Docs: .ci/netdev.sh runs via .ci/boot-linux.sh; adds Linux jobs for user and tap, plus macOS user; ARM64 setup retries apt-get with IPv4 and ensures expect; docs at docs/networking.md.
  • Bug Fixes

    • Update PLIC after queue refresh and raise used-ring interrupts only when the used index advances, fixing delayed interrupts and reducing TAP ping latency.

Written for commit 1a52828. Summary will update on new commits.

Review in cubic

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmarks

Details
Benchmark suite Current: 1a52828 Previous: f845a2e Ratio
Dhrystone 1596.333 DMIPS 1859.333 DMIPS 1.16
CoreMark 1116.946 iterations/sec 1199.227 iterations/sec 1.07

This comment was automatically generated by workflow using github-action-benchmark.

cubic-dev-ai[bot]

This comment was marked as resolved.

@Charlie-Tsai1123
Charlie-Tsai1123 force-pushed the research/virtio-net branch 2 times, most recently from 7658d0a to b775142 Compare July 1, 2026 15:18
@shengwen-tw

Copy link
Copy Markdown
Contributor

Hi @Charlie-Tsai1123,

I’m not the original author of the virtio-net device. Please check the commit history via git log --follow -- virtio-net.c; I believe the original author is @jserv.

@Charlie-Tsai1123

Copy link
Copy Markdown
Contributor Author

Thanks for the clarification, and sorry for the incorrect attribution.
I checked the semu history with git log --follow -- virtio-net.c. The original virtio-net implementation appears to have been introduced by jserv. I will update the commit message accordingly.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refine CI pipeline to validate virtio-net.

@jserv

jserv commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Thanks for the clarification, and sorry for the incorrect attribution. I checked the semu history with git log --follow -- virtio-net.c. The original virtio-net implementation appears to have been introduced by jserv. I will update the commit message accordingly.

Remove "This implementation is based on semu's virtio-net device, originally introduced by Jserv" as you already append "Co-authored-by: Jim Huang".

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>
The host-arm64 CI job may fail before running any build or test when
apt cannot update package indexes due to transient network issues on
the Ubuntu ports mirror. Add retry logic and force IPv4 when updating
the apt cache in install-llvm.sh to make LLVM repository setup more
robust on GitHub-hosted ARM runners.

The host-arm64 dependency installation also has a fallback path for
partially failed apt installs. However, the fallback only attempted to
install make, curl, and wget. As a result, the later Linux boot test
could fail with "expect: command not found" even though expect is a
required dependency for .ci/boot-linux.sh.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/system.h">

<violation number="1" location="src/system.h:89">
P1: Host packets received after the guest posts RX buffers can remain invisible: queue refresh sets virtio-net interrupt status but never asserts its PLIC line, while the guest is waiting for that interrupt. Update vnet interrupts immediately after `virtio_net_refresh_queue()` (and after any other asynchronous RX completion).</violation>
</file>

<file name="src/devices/virtio-net.c">

<violation number="1" location="src/devices/virtio-net.c:480">
P2: Enabling virtio-net adds a `poll(2)` syscall every 100 guest cycles even when no TAP or queue work exists, substantially slowing idle guest execution. Consider rate-limited polling or integrating TAP readiness with the emulator event loop.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread .ci/test-netdev.sh Outdated
Comment thread src/system.h
case MMIO_VIRTIONET: \
IIF(rw)( /* read */ \
mmio_read_val = virtio_net_read(PRIV(rv)->vnet, addr & 0xFFFFF); \
emu_update_vnet_interrupts(rv); \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Host packets received after the guest posts RX buffers can remain invisible: queue refresh sets virtio-net interrupt status but never asserts its PLIC line, while the guest is waiting for that interrupt. Update vnet interrupts immediately after virtio_net_refresh_queue() (and after any other asynchronous RX completion).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/system.h, line 89:

<comment>Host packets received after the guest posts RX buffers can remain invisible: queue refresh sets virtio-net interrupt status but never asserts its PLIC line, while the guest is waiting for that interrupt. Update vnet interrupts immediately after `virtio_net_refresh_queue()` (and after any other asynchronous RX completion).</comment>

<file context>
@@ -82,6 +83,17 @@ enum SUPPORTED_MMIO {
+        case MMIO_VIRTIONET:                                                          \
+            IIF(rw)( /* read */                                                       \
+                mmio_read_val = virtio_net_read(PRIV(rv)->vnet, addr & 0xFFFFF);      \
+                emu_update_vnet_interrupts(rv);                                       \
+                return mmio_read_val;                                                 \
+                ,    /* write */                                                      \
</file context>

Comment thread src/devices/virtio-net.c
Comment thread src/riscv.c
Comment thread src/devices/virtio-net.c
.events = POLLIN | POLLOUT,
};

poll(&pfd, 1, 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Enabling virtio-net adds a poll(2) syscall every 100 guest cycles even when no TAP or queue work exists, substantially slowing idle guest execution. Consider rate-limited polling or integrating TAP readiness with the emulator event loop.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/devices/virtio-net.c, line 480:

<comment>Enabling virtio-net adds a `poll(2)` syscall every 100 guest cycles even when no TAP or queue work exists, substantially slowing idle guest execution. Consider rate-limited polling or integrating TAP readiness with the emulator event loop.</comment>

<file context>
@@ -0,0 +1,693 @@
+            .events = POLLIN | POLLOUT,
+        };
+
+        poll(&pfd, 1, 0);
+
+        if (pfd.revents & POLLIN) {
</file context>

Comment thread src/devices/netdev.c Outdated
@jserv
jserv requested a review from ChinYikMing July 16, 2026 09:19
@jserv jserv added this to the release-2026.2 milestone Jul 16, 2026

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rework semu's network infrastructure, as described in networking.md:

  • Linux: TAP (kernel-level) and user-mode (SLIRP) networking
  • macOS: vmnet.framework (kernel-level NAT; bridge mode planned) and user-mode (SLIRP) networking

For this pull request, both TAP and SLIRP should be landed.

Comment thread .ci/test-netdev.sh Outdated
Comment thread src/main.c Outdated
Comment thread src/main.c
}
opt_virtio_blk_img[opt_virtio_blk_idx++] =
optarg + 5; /* strlen("vblk:") */
} else if (!strncmp("vnet:", optarg, 5)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to limit the vnet cli option passed by the user like the vblk?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added validation for the vnet option.
At this stage, only one virtio-net device is supported, so repeated vnet options are rejected. The backend is also validated and currently restricted to tap. The user backend will be enabled in the follow-up SLIRP commit.

Comment thread src/riscv.c Outdated
for (; !rv_has_halted(rv);) { /* run until the flag is done */
rv_step(rv); /* step instructions */

#if RV32_HAS(SYSTEM_MMIO)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do the check in rv_step?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the virtio-net refresh and interrupt propagation into the per-step execution path because the current backend uses non-blocking polling to receive packets from the host side. I plan to improve this in a follow-up with an event-driven wakeup path, eg. using eventfd.

Comment thread src/riscv.c Outdated
Remove the trailing space from the vnet help text, reject unsupported
virtio-net backends, and avoid repeated virtio-net options when only
one virtio-net device is supported.

At this stage only the TAP backend is accepted. The user-mode backend
will be enabled later when SLIRP support is added.

Also remove the unnecessary device index increment after adding the
virtio-net DTB node, since no later device uses the incremented value
Move the virtio-net validation logic into .ci/netdev.sh and invoke it
from .ci/boot-linux.sh when VNET_BACKEND is set.

This keeps the network-specific checks modular while reusing existing
Linux boot test flow, including boot-linux-prepare.sh setup and cleanup,
matching the structure used by the RTC tests.

The TAP test still validates that the guest virtio_net driver binds,
brings eth0 up, assigns the guest address, and can ping the host TAP
gateway.
virtio-net queue refresh can complete RX/TX descriptors and set the
device interrupt status, but PLIC line was not updated immediately
after refresh. This could leave the guest waiting until later interrupt
update point before observing completed network work, causing TAP ping
latency spikes of around one second.

Update the virtio-net interrupt after queue refresh.
This delivers completed network packets to the guest promptly.

Also make virtio_net_try_rx() and virtio_net_try_tx() raise used-ring
interrupts only when used ring index actually advances. TAP is often
reported writable by poll(), so trying TX without completing any
descriptor must not set VIRTIO_INT_USED_RING. Otherwise guest may see
repeated interrupts without corresponding used-ring updates.

Before this change, TAP ping could show delayed replies such as:

PING 192.168.100.1 (192.168.100.1): 56 data bytes
64 bytes from 192.168.100.1: seq=0 ttl=64 time=3.831 ms
64 bytes from 192.168.100.1: seq=1 ttl=64 time=1001.088 ms
64 bytes from 192.168.100.1: seq=2 ttl=64 time=1.389 ms

After the change, replies are delivered promptly:

PING 192.168.100.1 (192.168.100.1): 56 data bytes
64 bytes from 192.168.100.1: seq=0 ttl=64 time=1.181 ms
64 bytes from 192.168.100.1: seq=1 ttl=64 time=0.546 ms
64 bytes from 192.168.100.1: seq=2 ttl=64 time=0.565 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 11 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/devices/slirp.c">

<violation number="1" location="src/devices/slirp.c:376">
P2: Idle user-mode networking issues two host `poll()` syscalls per 100 guest instructions, substantially throttling system emulation. Throttle polling or integrate SLIRP scheduling with the main loop instead of polling on every refresh.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread Makefile Outdated
Comment thread src/devices/slirp.c
if (usr->pfd_len == 0)
return 0;

int ret = poll(usr->pfd, (nfds_t) usr->pfd_len, 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Idle user-mode networking issues two host poll() syscalls per 100 guest instructions, substantially throttling system emulation. Throttle polling or integrate SLIRP scheduling with the main loop instead of polling on every refresh.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/devices/slirp.c, line 376:

<comment>Idle user-mode networking issues two host `poll()` syscalls per 100 guest instructions, substantially throttling system emulation. Throttle polling or integrate SLIRP scheduling with the main loop instead of polling on every refresh.</comment>

<file context>
@@ -0,0 +1,399 @@
+    if (usr->pfd_len == 0)
+        return 0;
+
+    int ret = poll(usr->pfd, (nfds_t) usr->pfd_len, 0);
+    if (ret < 0) {
+        if (errno == EINTR)
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current implementation uses non-blocking I/O and polls SLIRP from the device refresh path, which is simple and keeps the migration consistent with the existing execution loop. I plan to address this in a follow-up PR with a proper event-driven wakeup path.

Comment thread src/devices/slirp.c
Comment thread src/devices/slirp.c Outdated
Comment thread src/main.c Outdated
Comment thread src/devices/netdev.c
Migrate semu's user-mode virtio-net networking support to rv32emu.

This adds SLIRP backend based on minislirp, allowing virtio-net to work
without TAP, root privileges, or host network setup. The backend
connects the virtio-net RX/TX paths with libslirp through non-blocking
socketpairs and uses the standard SLIRP guest network configuration:

  guest IP: 10.0.2.15/24
  gateway:  10.0.2.2
  DNS:      10.0.2.3

Several semu-specific parts are adapted for rv32emu:

- Replace semu's timer/event integration with a small
  CLOCK_MONOTONIC-based SLIRP timer wrapper.
- Drive SLIRP progress from virtio_net_refresh_queue(), matching
  rv32emu's existing execution loop and virtio-net polling model.
- Keep packet forwarding non-blocking so backend can be refreshed from
  the emulator loop without stalling guest execution.
- Also extend virtio-net CI coverage through the existing Linux boot
  test flow. The CI now validates Linux TAP networking and user-mode
  SLIRP networking on Linux, and also runs the user-mode SLIRP boot
  test on macOS.
@Charlie-Tsai1123

Copy link
Copy Markdown
Contributor Author

This update mainly covers two parts:

  1. virtio-net interrupt delivery
    Related commit: cf42444

I moved virtio-net refresh and interrupt propagation into the per-step execution path. After queue refresh, the virtio-net interrupt state is pushed to the PLIC. This avoids delaying completed RX/TX work until a later interrupt update point.

I also updated virtio_net_try_rx() and virtio_net_try_tx() so they only raise used-ring interrupts when the used ring index actually advances. This avoids repeated interrupts when no virtqueue progress was made, and fixes the observed TAP ping latency spike.

Before this change, TAP ping could show delayed replies such as:

PING 192.168.100.1 (192.168.100.1): 56 data bytes
64 bytes from 192.168.100.1: seq=0 ttl=64 time=3.831 ms
64 bytes from 192.168.100.1: seq=1 ttl=64 time=1001.088 ms
64 bytes from 192.168.100.1: seq=2 ttl=64 time=1.389 ms

After the change, replies are delivered promptly:

PING 192.168.100.1 (192.168.100.1): 56 data bytes
64 bytes from 192.168.100.1: seq=0 ttl=64 time=1.181 ms
64 bytes from 192.168.100.1: seq=1 ttl=64 time=0.546 ms
64 bytes from 192.168.100.1: seq=2 ttl=64 time=0.565 ms
  1. user-mode SLIRP backend
    Related commit: 103be25

I added a minislirp-based user-mode backend for virtio-net, adapted from semu. The backend connects the virtio-net RX/TX paths with libslirp through non-blocking socketpairs, so guest networking can work without TAP, root rivileges, or host network setup.

The current backend support is:

  • Linux : vnet:tap and vnet:user
  • macOS : vnet:user
  • Emscripten: no tap, no user, no vmnet

Emscripten is handled separately because rv32emu supports emcc builds, unlike semu's original networking setup. For emcc, the networking backends are disabled and unsupported vnet backends are rejected during argument parsing.

CI coverage is arranged as follows:

  • Linux x64 interpreter matrix runs both VNET_BACKEND=user and VNET_BACKEND=tap.
  • macOS runs the user-mode SLIRP boot test.
  • TAP is only tested on Linux because it depends on the Linux TAP/TUN interface.
  • virtio-net tests are not run for every JIT/T2C/MOP-fusion matrix entry, only run in the interpreter matrix.

Future PR may work:

  • vmnet.framework backend:
    I looked at semu's vmnet implementation. It uses Apple's vmnet.framework and Blocks syntax for callbacks. That path is suitable for Clang with Blocks support, but it is not suitable for the current macOS gcc-15 CI job. vmnet also has macOS-specific privilege / entitlement requirements. A follow-up could add vmnet through a clang-only macOS path or a more portable wrapper.

  • Event-driven wakeup:
    The current backend follows rv32emu's existing polling-based device refresh model and uses non-blocking I/O. This keeps the initial migration simple and consistent with the current virtio-net execution path. In a follow-up, this can be improved with an event-driven wakeup mechanism, such as eventfd on Linux or a pipe/condvar-style notification path, so host-side activity can wake the emulator loop instead of relying on frequent polling.

  • Emscripten support for virtio-net

@ChinYikMing ChinYikMing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please help adding backend selection into Kconfig. This can reduce the binary size of rv32emu exe if user might not need the functionality of vnet.

Comment thread src/devices/netdev.h Outdated
typedef struct netdev netdev_t;

typedef enum {
NETDEV_IMPL_none = 0,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer all in upper case: NETDEV_IMPL_NONE.

Comment thread src/devices/netdev.h Outdated
typedef enum {
NETDEV_IMPL_none = 0,
#if RV32EMU_NET_HAS_TAP
NETDEV_IMPL_tap,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

Comment thread src/devices/netdev.h Outdated
NETDEV_IMPL_tap,
#endif
#if RV32EMU_NET_HAS_SLIRP
NETDEV_IMPL_user,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

@ChinYikMing

Copy link
Copy Markdown
Collaborator

Hi @Charlie-Tsai1123 , could you document how to test the user mode vnet backend as I only see the tap backend is listed at here? It will be easier for reviewer to quick test . Thanks.

Comment thread src/main.c Outdated
"(default read and write). This option may be specified "
"multiple times for multiple block devices\n"
" -x vrng : enable virtio-rng device\n"
" -x vnet:<backend>: use <backend> as virtio-net backend interface\n"

@ChinYikMing ChinYikMing Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add possible vnet backend here.

E.g., -x vnet:<backend>: use <backend> as virtio-net backend interface(supported backend: user/tap)\n

But if vnet is togglable in Kconfig, this should be adjusted again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I've updated the help message to include tap and user. I also made it dynamic based on Kconfig: the -x vnet help text will now only display the specific backends that are actually enabled in the current build configuration.

Comment thread src/devices/netdev.c
}
#endif

bool netdev_init(netdev_t *netdev, const char *net_type)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialization for NETDEV_IMPL_user has duplicated logic. Please rewrite.

Something like this might be cleaner:

/* Linux specify */
if defined(__linux__) && RV32EMU_NET_HAS_TAP
init_tap()
else /* user mode for macOS and Linux */
init_user()
endif

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I write netdev_setup for duplicated logic.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 14 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="mk/system.mk">

<violation number="1" location="mk/system.mk:61">
P3: When VIRTIO_NET=y but both backends are disabled (VIRTIO_NET_TAP=n and VIRTIO_NET_USER=n), this block still compiles virtio-net.o and netdev.o even though no backend is available, so vnet degrades to a runtime 'no backend compiled' failure instead of being excluded. Consider also filtering those objects when neither backend is enabled, mirroring the VIRTIO_NET!=y branch.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread mk/system.mk Outdated
ifneq ($(CONFIG_VIRTIO_NET),y)
DEV_OBJS := $(filter-out $(DEV_OUT)/virtio-net.o $(DEV_OUT)/netdev.o $(DEV_OUT)/slirp.o, $(DEV_OBJS))
else
ifneq ($(CONFIG_VIRTIO_NET_USER),y)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: When VIRTIO_NET=y but both backends are disabled (VIRTIO_NET_TAP=n and VIRTIO_NET_USER=n), this block still compiles virtio-net.o and netdev.o even though no backend is available, so vnet degrades to a runtime 'no backend compiled' failure instead of being excluded. Consider also filtering those objects when neither backend is enabled, mirroring the VIRTIO_NET!=y branch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At mk/system.mk, line 61:

<comment>When VIRTIO_NET=y but both backends are disabled (VIRTIO_NET_TAP=n and VIRTIO_NET_USER=n), this block still compiles virtio-net.o and netdev.o even though no backend is available, so vnet degrades to a runtime 'no backend compiled' failure instead of being excluded. Consider also filtering those objects when neither backend is enabled, mirroring the VIRTIO_NET!=y branch.</comment>

<file context>
@@ -52,6 +52,17 @@ $(DEV_OUT)/%.o: $(DEV_SRC)/%.c $(EFFECTIVE_CONFIG_STAMP) | $(DEV_OUT)
+ifneq ($(CONFIG_VIRTIO_NET),y)
+DEV_OBJS := $(filter-out $(DEV_OUT)/virtio-net.o $(DEV_OUT)/netdev.o $(DEV_OUT)/slirp.o, $(DEV_OBJS))
+else
+ifneq ($(CONFIG_VIRTIO_NET_USER),y)
+DEV_OBJS := $(filter-out $(DEV_OUT)/slirp.o, $(DEV_OBJS))
+endif
</file context>

Add Kconfig options for the virtio-net device, Linux TAP backend,
and user-mode SLIRP backend.

Exclude unused network objects at build time, build minislirp only
when the user backend is enabled, and guard the related CLI, runtime,
MMIO, DTB, and interrupt integration.

Also list only compiled backends in the CLI help and consolidate
backend initialization through a shared helper (netdev_setup in
src/devices/netdev.c).
@Charlie-Tsai1123

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I have added build-time backend selection through Kconfig and documented how to test the user-mode backend.

test user-mode

Build (Host):

make system_defconfig
make ENABLE_SYSTEM=1

build/rv32emu \
  -k build/linux-image/Image \
  -i build/linux-image/rootfs.cpio \
  -x vnet:user

Guest device verification (Guest):

readlink /sys/bus/virtio/devices/virtio0/driver
ip link set eth0 up
ip addr flush dev eth0
ip addr add 10.0.2.15/24 dev eth0
ip route add default via 10.0.2.2
ip addr show eth0
ip route
ping -c 3 -W 5 10.0.2.2

Expected result (Guest):

# readlink /sys/bus/virtio/devices/virtio0/driver
../../../../../bus/virtio/drivers/virtio_net
# ip link set eth0 up
# ip addr flush dev eth0
# ip addr add 10.0.2.15/24 dev eth0
# ip route add default via 10.0.2.2
# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
    link/ether c2:84:a2:01:23:bb brd ff:ff:ff:ff:ff:ff
    inet 10.0.2.15/24 scope global eth0
       valid_lft forever preferred_lft forever
# ip route
default via 10.0.2.2 dev eth0 
10.0.2.0/24 dev eth0 scope link  src 10.0.2.15 
# ping -c 3 -W 5 10.0.2.2
PING 10.0.2.2 (10.0.2.2): 56 data bytes
64 bytes from 10.0.2.2: seq=0 ttl=255 time=1.125 ms
64 bytes from 10.0.2.2: seq=1 ttl=255 time=0.526 ms
64 bytes from 10.0.2.2: seq=2 ttl=255 time=0.544 ms

--- 10.0.2.2 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.526/0.731/1.125 ms

or test with the existing automated user-mode network test (Host):

VNET_BACKEND=user bash .ci/boot-linux.sh

Kconfig changes

The following options were added:

CONFIG_VIRTIO_NET
CONFIG_VIRTIO_NET_TAP
CONFIG_VIRTIO_NET_USER

Default system_defconfig still contain virtio-net setting, user could disable it by using make config and cancel it:

make config

select Execution Modes:
image

Under the system-emulation configuration, users can independently enable or disable:
image
Disabling CONFIG_VIRTIO_NET excludes virtio-net.o netdev.o slirp.o from the build:

When CONFIG_VIRTIO_NET is enabled but neither TAP nor USER is selected, virtio-net is also treated as unavailable and the network objects are excluded.

When user cancel virtio-net, the binary size of rv32emu exe decrease. I copy the build file and test

copy file after make for different config setting:

cp build/rv32emu /tmp/rv32emu-no-vnet
cp build/rv32emu /tmp/rv32emu-vnet-full

Then their size:

charlie-tsai:/media/charlie-tsai/DATA/road_to_master/rv32emu$stat -c '%n: %s bytes'     /tmp/rv32emu-vnet-full     /tmp/rv32emu-no-vnet
/tmp/rv32emu-vnet-full: 344416 bytes
/tmp/rv32emu-no-vnet: 229560 bytes

@ChinYikMing

ChinYikMing commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

The boot linux CI is refactored in bda4649 of #638, I would like to see the newly added virtio-net CI test rebase on that.

@Charlie-Tsai1123

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion. I rebased the virtio-net changes onto the latest version of PR #638 and updated the CI integration accordingly.

Previously, the virtio-net user-mode and TAP tests were defined as separate GitHub Actions steps. They are now integrated into .ci/boot-linux.sh, following the same structure as the RTC, reboot, and virtio-blk tests:

  • virtio-net user-mode SLIRP is tested on Linux and macOS.
  • virtio-net TAP is tested on Linux and skipped on macOS hosts.
  • The separate virtio-net workflow steps were removed from main.yml to avoid duplicate execution.
  • The virtio-net boot timeout on macOS was extended because the default timeout was insufficient under the macOS GitHub runner load.

Previous:
image
Now:
image

All relevant build and boot tests passed. The only failing job is docker-hub-build-and-publish, which requires Docker Hub credentials that are not available for this temporary branch/fork, so it appears unrelated to these changes.

Temporary integration branch:
https://github.com/Charlie-Tsai1123/rv32emu/tree/test/virtio-net-on-pr638
CI test result:
https://github.com/Charlie-Tsai1123/rv32emu/actions/runs/30758331124/job/91524331778
Small modification after rebase:
Charlie-Tsai1123@68e5e4b

I have not updated the branch of PR #748 yet because PR #638 is still open.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants