From c0263f340fd70e015df0411d3fd565f3fd8a0048 Mon Sep 17 00:00:00 2001 From: Aaron Date: Sat, 23 Nov 2024 04:00:11 +0800 Subject: [PATCH] Refactor network device handling for flexible device support Separate network device handling to support various network backends, such as Slirp, instead of hard-coding the TAP device. The '-netdev' flag can now be used to specify the network device type, defaulting to TAP if unspecified. Additionally, packet transfer functions are now tailored to each network device, enabling more modular and extensible network device development. --- Makefile | 4 +- device.h | 5 +- main.c | 20 ++++++-- netdev.c | 87 +++++++++++++++++++++++++++++++++++ netdev.h | 32 +++++++++++++ virtio-net.c | 127 ++++++++++++++++++++++++++++++++++++--------------- 6 files changed, 229 insertions(+), 46 deletions(-) create mode 100644 netdev.c create mode 100644 netdev.h diff --git a/Makefile b/Makefile index ceb397f9..a21e7816 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,7 @@ ifeq ($(call has, VIRTIOBLK), 1) endif endif +NETDEV ?= tap # virtio-net ENABLE_VIRTIONET ?= 1 ifneq ($(UNAME_S),Linux) @@ -39,6 +40,7 @@ endif $(call set-feature, VIRTIONET) ifeq ($(call has, VIRTIONET), 1) OBJS_EXTRA += virtio-net.o + OBJS_EXTRA += netdev.o endif BIN = semu @@ -96,7 +98,7 @@ ext4.img: check: $(BIN) minimal.dtb $(KERNEL_DATA) $(INITRD_DATA) $(DISKIMG_FILE) @$(call notice, Ready to launch Linux kernel. Please be patient.) - $(Q)./$(BIN) -k $(KERNEL_DATA) -c $(SMP) -b minimal.dtb -i $(INITRD_DATA) $(OPTS) + $(Q)./$(BIN) -k $(KERNEL_DATA) -c $(SMP) -b minimal.dtb -i $(INITRD_DATA) -n $(NETDEV) $(OPTS) build-image: scripts/build-image.sh diff --git a/device.h b/device.h index 1bb79001..489cae6c 100644 --- a/device.h +++ b/device.h @@ -1,5 +1,6 @@ #pragma once +#include "netdev.h" #include "riscv.h" #include "virtio.h" @@ -101,7 +102,7 @@ typedef struct { uint32_t Status; uint32_t InterruptStatus; /* supplied by environment */ - int tap_fd; + netdev_t peer; uint32_t *ram; /* implementation-specific */ void *priv; @@ -119,7 +120,7 @@ void virtio_net_write(hart_t *core, uint32_t value); void virtio_net_refresh_queue(virtio_net_state_t *vnet); -bool virtio_net_init(virtio_net_state_t *vnet); +bool virtio_net_init(virtio_net_state_t *vnet, const char *name); #endif /* SEMU_HAS(VIRTIONET) */ /* VirtIO-Block */ diff --git a/main.c b/main.c index 4a921130..39935f89 100644 --- a/main.c +++ b/main.c @@ -440,19 +440,22 @@ static void handle_options(int argc, char **dtb_file, char **initrd_file, char **disk_file, + char **net_dev, int *hart_count) { - *kernel_file = *dtb_file = *initrd_file = *disk_file = NULL; + *kernel_file = *dtb_file = *initrd_file = *disk_file = *net_dev = NULL; int optidx = 0; struct option opts[] = { {"kernel", 1, NULL, 'k'}, {"dtb", 1, NULL, 'b'}, {"initrd", 1, NULL, 'i'}, {"disk", 1, NULL, 'd'}, - {"smp", 1, NULL, 'c'}, {"help", 0, NULL, 'h'}, + {"netdev", 1, NULL, 'n'}, {"smp", 1, NULL, 'c'}, + {"help", 0, NULL, 'h'}, }; int c; - while ((c = getopt_long(argc, argv, "k:b:i:d:c:h", opts, &optidx)) != -1) { + while ((c = getopt_long(argc, argv, "k:b:i:d:n:c:h", opts, &optidx)) != + -1) { switch (c) { case 'k': *kernel_file = optarg; @@ -466,6 +469,9 @@ static void handle_options(int argc, case 'd': *disk_file = optarg; break; + case 'n': + *net_dev = optarg; + break; case 'c': *hart_count = atoi(optarg); break; @@ -487,6 +493,9 @@ static void handle_options(int argc, if (!*dtb_file) *dtb_file = "minimal.dtb"; + + if (!*net_dev) + *net_dev = "tap"; } @@ -509,9 +518,10 @@ static int semu_start(int argc, char **argv) char *dtb_file; char *initrd_file; char *disk_file; + char *netdev; int hart_count = 1; handle_options(argc, argv, &kernel_file, &dtb_file, &initrd_file, - &disk_file, &hart_count); + &disk_file, &netdev, &hart_count); /* Initialize the emulator */ emu_state_t emu; @@ -573,7 +583,7 @@ static int semu_start(int argc, char **argv) emu.uart.in_fd = 0, emu.uart.out_fd = 1; capture_keyboard_input(); /* set up uart */ #if SEMU_HAS(VIRTIONET) - if (!virtio_net_init(&(emu.vnet))) + if (!virtio_net_init(&(emu.vnet), netdev)) fprintf(stderr, "No virtio-net functioned\n"); emu.vnet.ram = emu.ram; #endif diff --git a/netdev.c b/netdev.c new file mode 100644 index 00000000..4f13b4be --- /dev/null +++ b/netdev.c @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netdev.h" + +static int net_init_tap(); +static int net_init_user(); + +static const char *netdev_driver_lookup[] = { +#define _(dev) [NET_DEV_DRIVER_##dev] = #dev, + SUPPORTED_DEVICES +#undef _ + NULL, +}; + +static int find_net_dev_idx(const char *net_type, const char **netlookup) +{ + if (!net_type) + return -1; + int i = 0; + while (netlookup[i]) { + if (!strcmp(net_type, netlookup[i])) { + return i; + } + i++; + } + return -1; +} + +static int net_init_tap(netdev_t *netdev) +{ + net_tap_options *tap; + tap = &netdev->op.tap; + tap->tap_fd = open("/dev/net/tun", O_RDWR); + if (tap->tap_fd < 0) { + fprintf(stderr, "failed to open TAP device: %s\n", strerror(errno)); + return false; + } + + /* Specify persistent tap device */ + struct ifreq ifreq = {.ifr_flags = IFF_TAP | IFF_NO_PI}; + strncpy(ifreq.ifr_name, "tap%d", sizeof(ifreq.ifr_name)); + if (ioctl(tap->tap_fd, TUNSETIFF, &ifreq) < 0) { + fprintf(stderr, "failed to allocate TAP device: %s\n", strerror(errno)); + return false; + } + + fprintf(stderr, "allocated TAP interface: %s\n", ifreq.ifr_name); + assert(fcntl(tap->tap_fd, F_SETFL, + fcntl(tap->tap_fd, F_GETFL, 0) | O_NONBLOCK) >= 0); + return 0; +} + +static int net_init_user(netdev_t *netdev) +{ + /* TODO: create slirp dev */ + return 0; +} + +int netdev_init(netdev_t *netdev, const char *net_type) +{ + int dev_idx = find_net_dev_idx(net_type, netdev_driver_lookup); + if (dev_idx == -1) + return false; + netdev->type = dev_idx; + + switch (dev_idx) { +#define _(dev) \ + case NET_DEV_DRIVER_##dev: \ + net_init_##dev(netdev); \ + break; + SUPPORTED_DEVICES +#undef _ + default: + fprintf(stderr, "unknown network device\n"); + break; + } + + return true; +} \ No newline at end of file diff --git a/netdev.h b/netdev.h new file mode 100644 index 00000000..364a0149 --- /dev/null +++ b/netdev.h @@ -0,0 +1,32 @@ +#pragma once + +/* clang-format off */ +#define SUPPORTED_DEVICES \ + _(tap) \ + _(user) +/* clang-format on */ + +typedef enum { +#define _(dev) NET_DEV_DRIVER_##dev, + SUPPORTED_DEVICES +#undef _ +} net_dev_driver_t; + +typedef struct { + int tap_fd; +} net_tap_options; + +typedef struct { + /* TODO: Implement user option */ +} net_user_options; + +typedef struct { + char *name; + net_dev_driver_t type; + union { + net_tap_options tap; + net_user_options user; + } op; +} netdev_t; + +int netdev_init(netdev_t *nedtev, const char *net_type); \ No newline at end of file diff --git a/virtio-net.c b/virtio-net.c index ddba34dc..1f7564bd 100644 --- a/virtio-net.c +++ b/virtio-net.c @@ -62,11 +62,11 @@ static void virtio_net_update_status(virtio_net_state_t *vnet, uint32_t status) return; /* Reset */ - int tap_fd = vnet->tap_fd; + netdev_t peer = vnet->peer; uint32_t *ram = vnet->ram; void *priv = vnet->priv; memset(vnet, 0, sizeof(*vnet)); - vnet->tap_fd = tap_fd, vnet->ram = ram; + vnet->peer = peer, vnet->ram = ram; vnet->priv = priv; } @@ -113,6 +113,66 @@ static bool vnet_iovec_read(struct iovec **vecs, return n && !*nvecs; } +static ssize_t handle_read(netdev_t *netdev, + virtio_net_queue_t *queue, + struct iovec *iovs_cursor, + size_t niovs) +{ + ssize_t plen = 0; +#define _(dev) NET_DEV_DRIVER_##dev + switch (netdev->type) { + case _(tap): + plen = readv(netdev->op.tap.tap_fd, iovs_cursor, niovs); + if (plen < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + queue->fd_ready = false; + return -1; + } + if (plen < 0) { + plen = 0; + fprintf(stderr, "[VNET] could not read packet: %s\n", + strerror(errno)); + } + break; + case _(user): + /* TODO: handle read */ + break; + default: + break; + } +#undef _ + return plen; +} + +static ssize_t handle_write(netdev_t *netdev, + virtio_net_queue_t *queue, + struct iovec *iovs_cursor, + size_t niovs) +{ + ssize_t plen = 0; +#define _(dev) NET_DEV_DRIVER_##dev + switch (netdev->type) { + case _(tap): + plen = writev(netdev->op.tap.tap_fd, iovs_cursor, niovs); + if (plen < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { + queue->fd_ready = false; + return -1; + } + if (plen < 0) { + plen = 0; + fprintf(stderr, "[VNET] could not write packet: %s\n", + strerror(errno)); + } + break; + case _(user): + /* TODO: handle slirp_input */ + break; + default: + break; + } +#undef _ + return plen; +} + /* Require existing 'desc_idx' to use as iteration variable, and input * 'buffer_idx'. */ @@ -186,18 +246,10 @@ static bool vnet_iovec_read(struct iovec **vecs, virtio_header, sizeof(virtio_header)); \ } \ \ - ssize_t plen = \ - VERB##v(vnet->tap_fd, buffer_iovs_cursor, buffer_niovs); \ - if (plen < 0 && (errno == EWOULDBLOCK || errno == EAGAIN)) { \ - queue->fd_ready = false; \ + ssize_t plen = handle_##VERB(&vnet->peer, queue, \ + buffer_iovs_cursor, buffer_niovs); \ + if (plen < 0) \ break; \ - } \ - if (plen < 0) { \ - plen = 0; \ - fprintf(stderr, "[VNET] could not " #VERB " packet: %s\n", \ - strerror(errno)); \ - } \ - \ /* consume from available queue, write to used queue */ \ queue->last_avail++; \ ram[queue->QueueUsed + 1 + (new_used % queue->QueueNum) * 2] = \ @@ -223,16 +275,28 @@ void virtio_net_refresh_queue(virtio_net_state_t *vnet) (vnet->Status & VIRTIO_STATUS__DEVICE_NEEDS_RESET)) return; - struct pollfd pfd = {vnet->tap_fd, POLLIN | POLLOUT, 0}; - poll(&pfd, 1, 0); - if (pfd.revents & POLLIN) { - vnet->queues[VNET_QUEUE_RX].fd_ready = true; - virtio_net_try_rx(vnet); - } - if (pfd.revents & POLLOUT) { - vnet->queues[VNET_QUEUE_TX].fd_ready = true; - virtio_net_try_tx(vnet); + net_dev_driver_t dev_type = vnet->peer.type; +#define _(dev) NET_DEV_DRIVER_##dev + switch (dev_type) { + case _(tap): + struct pollfd pfd = {vnet->peer.op.tap.tap_fd, POLLIN | POLLOUT, 0}; + poll(&pfd, 1, 0); + if (pfd.revents & POLLIN) { + vnet->queues[VNET_QUEUE_RX].fd_ready = true; + virtio_net_try_rx(vnet); + } + if (pfd.revents & POLLOUT) { + vnet->queues[VNET_QUEUE_TX].fd_ready = true; + virtio_net_try_tx(vnet); + } + break; + case _(user): + /* TODO: handle slirp input/output */ + break; + default: + break; } +#undef _ } static bool virtio_net_reg_read(virtio_net_state_t *vnet, @@ -433,7 +497,7 @@ void virtio_net_write(hart_t *vm, } } -bool virtio_net_init(virtio_net_state_t *vnet) +bool virtio_net_init(virtio_net_state_t *vnet, const char *name) { if (vnet_dev_cnt >= VNET_DEV_CNT_MAX) { fprintf(stderr, @@ -444,23 +508,10 @@ bool virtio_net_init(virtio_net_state_t *vnet) /* Allocate memory for the private member */ vnet->priv = &vnet_configs[vnet_dev_cnt++]; - vnet->tap_fd = open("/dev/net/tun", O_RDWR); - if (vnet->tap_fd < 0) { - fprintf(stderr, "failed to open TAP device: %s\n", strerror(errno)); + if (!netdev_init(&vnet->peer, name)) { + fprintf(stderr, "Fail to init net device %s\n", name); return false; } - /* Specify persistent tap device */ - struct ifreq ifreq = {.ifr_flags = IFF_TAP | IFF_NO_PI}; - strncpy(ifreq.ifr_name, TAP_INTERFACE, sizeof(ifreq.ifr_name)); - if (ioctl(vnet->tap_fd, TUNSETIFF, &ifreq) < 0) { - fprintf(stderr, "failed to allocate TAP device: %s\n", strerror(errno)); - return false; - } - - fprintf(stderr, "allocated TAP interface: %s\n", ifreq.ifr_name); - assert(fcntl(vnet->tap_fd, F_SETFL, - fcntl(vnet->tap_fd, F_GETFL, 0) | O_NONBLOCK) >= 0); - return true; }