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..1d074acf --- /dev/null +++ b/netdev.c @@ -0,0 +1,80 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "netdev.h" + +static int net_init_tap(); +static int net_init_slirp(); + +static int (*const net_init_fun[NET_DEV_DRIVER_MAX])(const netdev_t *) = { + [NET_DEV_DRIVER_TAP] = net_init_tap, + [NET_DEV_DRIVER_USER] = net_init_slirp, +}; + +static const char *netdev_driver_lookup[] = { + [NET_DEV_DRIVER_TAP] = "tap", + [NET_DEV_DRIVER_USER] = "user", +}; + +static int find_net_dev_idx(const char *net_type, const char **netlookup) +{ + int i; + if (!net_type) + return -1; + for (i = 0; i < NET_DEV_DRIVER_MAX; i++) { + if (!strcmp(net_type, netlookup[i])) { + return 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_slirp(netdev_t *netdev) +{ + // TBD: 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; + else + netdev->type = dev_idx; + + net_init_fun[netdev->type](netdev); + + return true; +} \ No newline at end of file diff --git a/netdev.h b/netdev.h new file mode 100644 index 00000000..76d71492 --- /dev/null +++ b/netdev.h @@ -0,0 +1,27 @@ +#pragma once + +typedef enum { + NET_DEV_DRIVER_TAP, + NET_DEV_DRIVER_USER, + NET_DEV_DRIVER_MAX, +} net_dev_driver_t; + +typedef struct { + int tap_fd; +} net_tap_options; + +typedef struct { + /* TODO: Implement user option */ + int temp; +} 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..faab1ebd 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,64 @@ 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; + switch (netdev->type) { + case NET_DEV_DRIVER_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 NET_DEV_DRIVER_USER: + /* TODO: handle read */ + break; + default: + break; + } + + 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; + switch (netdev->type) { + case NET_DEV_DRIVER_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 NET_DEV_DRIVER_USER: + /* TODO: handle slirp_input */ + break; + default: + break; + } + + return plen; +} + /* Require existing 'desc_idx' to use as iteration variable, and input * 'buffer_idx'. */ @@ -186,18 +244,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,15 +273,25 @@ 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; + switch (dev_type) { + case NET_DEV_DRIVER_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 NET_DEV_DRIVER_USER: + /* TODO: handle slirp input/output */ + break; + default: + break; } } @@ -433,7 +493,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 +504,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; }