Added virtio network device.
authorKyle Milka <kmilka1995@gmail.com>
Fri, 1 Jul 2016 16:19:32 +0000 (09:19 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 6 Jul 2016 15:41:41 +0000 (11:41 -0400)
We were able to get Linux to send a ping through the virtio device. Used
linux/lguest.c as a starting point and virtio_lguest_console.c for finding
the names of our version of the functions. This included adding the
network device struct and transmit and receive functions.

Fixes: b/29178446
Change-Id: I840c0acc617d238fb2b24b5662ee76f615bc743f
Signed-off-by: Kyle Milka <kmilka@google.com>
[checkpatch touchups, moved write() outside assert()]
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tests/vmm/vmrunkernel.c
user/vmm/include/vmm/virtio_net.h
user/vmm/include/vmm/vmm.h
user/vmm/virtio.c
user/vmm/virtio_net.c [new file with mode: 0644]

index d00c441..da704f2 100644 (file)
@@ -27,6 +27,7 @@
 #include <vmm/virtio_ids.h>
 #include <vmm/virtio_config.h>
 #include <vmm/virtio_console.h>
+#include <vmm/virtio_net.h>
 #include <vmm/virtio_lguest_console.h>
 
 #include <vmm/sched.h>
@@ -92,10 +93,11 @@ struct acpi_table_madt madt = {
        },
 
        .address = 0xfee00000ULL,
+       .flags = 0,
 };
 
 struct acpi_madt_local_apic Apic0 = {.header = {.type = ACPI_MADT_TYPE_LOCAL_APIC, .length = sizeof(struct acpi_madt_local_apic)},
-                                    .processor_id = 0, .id = 0};
+                                    .processor_id = 0, .id = 0, .lapic_flags = 1};
 struct acpi_madt_io_apic Apic1 = {.header = {.type = ACPI_MADT_TYPE_IO_APIC, .length = sizeof(struct acpi_madt_io_apic)},
                                  .id = 0, .address = 0xfec00000, .global_irq_base = 0};
 struct acpi_madt_local_x2apic X2Apic0 = {
@@ -164,6 +166,7 @@ void vapic_status_dump(FILE *f, void *vapic);
 #define LOCK_PREFIX "lock "
 #define ADDR                           BITOP_ADDR(addr)
 static inline int test_and_set_bit(int nr, volatile unsigned long *addr);
+static int default_nic = 1;
 
 pthread_t timerthread_struct;
 
@@ -204,9 +207,8 @@ static struct virtio_console_config cons_cfg_d;
 static struct virtio_vq_dev cons_vqdev = {
        .name = "console",
        .dev_id = VIRTIO_ID_CONSOLE,
-       .dev_feat = ((uint64_t)1 << VIRTIO_F_VERSION_1)
-                                         | (1 << VIRTIO_RING_F_INDIRECT_DESC)
-                         ,
+       .dev_feat =
+           (1ULL << VIRTIO_F_VERSION_1) | (1 << VIRTIO_RING_F_INDIRECT_DESC),
        .num_vqs = 2,
        .cfg = &cons_cfg,
        .cfg_d = &cons_cfg_d,
@@ -228,6 +230,44 @@ static struct virtio_vq_dev cons_vqdev = {
                }
 };
 
+static struct virtio_mmio_dev net_mmio_dev = {
+       .poke_guest = virtio_poke_guest,
+       .irq = 27,
+};
+
+static struct virtio_net_config net_cfg = {
+       .max_virtqueue_pairs = 1
+};
+static struct virtio_net_config net_cfg_d = {
+       .max_virtqueue_pairs = 1
+};
+
+static struct virtio_vq_dev net_vqdev = {
+       .name = "network",
+       .dev_id = VIRTIO_ID_NET,
+       .dev_feat = (1ULL << VIRTIO_F_VERSION_1 | 1 << VIRTIO_NET_F_MAC),
+
+       .num_vqs = 2,
+       .cfg = &net_cfg,
+       .cfg_d = &net_cfg_d,
+       .cfg_sz = sizeof(struct virtio_net_config),
+       .transport_dev = &net_mmio_dev,
+       .vqs = {
+               {
+                       .name = "net_receiveq",
+                       .qnum_max = 64,
+                       .srv_fn = net_receiveq_fn,
+                       .vqdev = &net_vqdev
+               },
+               {
+                       .name = "net_transmitq",
+                       .qnum_max = 64,
+                       .srv_fn = net_transmitq_fn,
+                       .vqdev = &net_vqdev
+               },
+       }
+};
+
 void lowmem() {
        __asm__ __volatile__ (".section .lowmem, \"aw\"\n\tlow: \n\t.=0x1000\n\t.align 0x100000\n\t.previous\n");
 }
@@ -566,10 +606,18 @@ int main(int argc, char **argv)
                                                  bp->e820_map[e820i - 1].size),
                                                 512 * GiB);
 
-       cons_mmio_dev.addr = virtio_mmio_base_addr;
+       cons_mmio_dev.addr =
+           virtio_mmio_base_addr + PGSIZE * VIRTIO_MMIO_CONSOLE_DEV;
        cons_mmio_dev.vqdev = &cons_vqdev;
        vm->virtio_mmio_devices[VIRTIO_MMIO_CONSOLE_DEV] = &cons_mmio_dev;
 
+       net_mmio_dev.addr =
+           virtio_mmio_base_addr + PGSIZE * VIRTIO_MMIO_NETWORK_DEV;
+       net_mmio_dev.vqdev = &net_vqdev;
+       vm->virtio_mmio_devices[VIRTIO_MMIO_NETWORK_DEV] = &net_mmio_dev;
+
+       net_init_fn(&net_vqdev, default_nic);
+
        /* Set the kernel command line parameters */
        a += 4096;
        cmdline = a;
index 31b8f54..c020e69 100644 (file)
@@ -27,7 +27,6 @@
 #include <stdint.h>
 #include <vmm/virtio_ids.h>
 #include <vmm/virtio_config.h>
-#include <linux/if_ether.h>
 
 /* The feature bitmap for virtio net */
 #define VIRTIO_NET_F_CSUM      0       /* Host handles pkts w/ partial csum */
 #define VIRTIO_NET_S_LINK_UP   1       /* Link is up */
 #define VIRTIO_NET_S_ANNOUNCE  2       /* Announcement is needed */
 
+#ifndef ETH_ALEN
+#define ETH_ALEN       6       /* Length of a MAC address (48 bits) */
+#endif
+
 struct virtio_net_config {
        /* The config defining mac address (if VIRTIO_NET_F_MAC) */
        uint8_t mac[ETH_ALEN];
@@ -239,3 +242,7 @@ struct virtio_net_ctrl_mq {
  */
 #define VIRTIO_NET_CTRL_GUEST_OFFLOADS   5
 #define VIRTIO_NET_CTRL_GUEST_OFFLOADS_SET        0
+
+void net_receiveq_fn(void *_vq);
+void net_transmitq_fn(void *_vq);
+void net_init_fn(struct virtio_vq_dev *vqdev, int nic);
index a61d9ad..ca1d6ec 100644 (file)
 #include <vmm/sched.h>
 
 /* The listing of VIRTIO MMIO devices. We currently only expect to have 2,
- * console and network. Only the console is implemented right now.*/
+ * console and network. Only the console is fully implemented right now.*/
 enum {
        VIRTIO_MMIO_CONSOLE_DEV,
+       VIRTIO_MMIO_NETWORK_DEV,
 
        /* This should always be the last entry. */
-       VIRTIO_MMIO_MAX_NUM_DEV = 2,
+       VIRTIO_MMIO_MAX_NUM_DEV,
 };
 
 /* Structure to encapsulate all of the bookkeeping for a VM. */
index 165cb47..f64d1e2 100644 (file)
@@ -32,6 +32,11 @@ const char *virtio_validate_feat(struct virtio_vq_dev *vqdev, uint64_t feat)
                case VIRTIO_ID_CONSOLE:
                        // No interdependent features for the console.
                        break;
+               case VIRTIO_ID_NET:
+                       // There is no "mandatory" feature bit that we always want to have,
+                       // either the device can set its own MAC Address (as it does now)
+                       // or the driver can set it using a controller thread.
+                       break;
                case 0:
                        return "Invalid device id (0x0)! On the MMIO transport, this value indicates that the device is a system memory map with placeholder devices at static, well known addresses. In any case, this is not something you validate features for.";
                default:
diff --git a/user/vmm/virtio_net.c b/user/vmm/virtio_net.c
new file mode 100644 (file)
index 0000000..1a4ecad
--- /dev/null
@@ -0,0 +1,212 @@
+/* Virtio helper functions from linux/tools/lguest/lguest.c
+ *
+ * Copyright (C) 1991-2016, the Linux Kernel authors
+ * Copyright (c) 2016 Google Inc.
+ *
+ * Author:
+ *  Rusty Russell <rusty@rustcorp.com.au>
+ *  Kyle Milka <kmilka@google.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * The code from lguest.c has been modified for Akaros.
+ *
+ * Original linux/tools/lguest/lguest.c:
+ *   https://github.com/torvalds/linux/blob/v4.5/tools/lguest/lguest.c
+ *   most recent hash on the file as of v4.5 tag:
+ *     e523caa601f4a7c2fa1ecd040db921baf7453798
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <vmm/virtio.h>
+#include <vmm/virtio_mmio.h>
+#include <vmm/virtio_net.h>
+
+#define VIRTIO_HEADER_SIZE     12
+
+static int ctlfd;
+static int etherfd;
+static char data_path[128];
+static char clone_path[64];
+
+void net_init_fn(struct virtio_vq_dev *vqdev, int nic)
+{
+       char type[] = "connect -1";
+       char buf[8];
+       char addr_path[32];
+       char addr_buf[3];
+       int addr_fd;
+       uint8_t addr_bytes;
+       int num_read;
+       int total_read = 0;
+
+       snprintf(addr_path, sizeof(addr_path), "/net/ether%d/addr", nic);
+       addr_fd = open(addr_path, O_RDONLY);
+       if (addr_fd < 0)
+               VIRTIO_DEV_ERRX(vqdev, "Bad addr_fd\n");
+
+       for (int i = 0; i < ETH_ALEN; ++i) {
+               assert(read(addr_fd, addr_buf, 2) == 2);
+               addr_buf[2] = 0;
+               addr_bytes = (uint8_t)(strtol(addr_buf, 0, 16));
+               ((struct virtio_net_config *)(vqdev->cfg))->mac[i] = addr_bytes;
+               ((struct virtio_net_config *)(vqdev->cfg_d))->mac[i] = addr_bytes;
+       }
+
+       snprintf(clone_path, sizeof(clone_path), "/net/ether%d/clone", nic);
+       ctlfd = open(clone_path, O_RDWR);
+       if (ctlfd < 0)
+               VIRTIO_DEV_ERRX(vqdev, "%s", clone_path);
+
+       do {
+               num_read = read(ctlfd, buf + total_read, sizeof(buf) - total_read);
+               total_read += num_read;
+       } while (num_read > 0);
+
+       etherfd = strtol(buf, 0, 10);
+       if (etherfd < 0)
+               VIRTIO_DEV_ERRX(vqdev, "bad etherfd %d (%s)", etherfd, buf);
+
+       snprintf(data_path, sizeof(data_path),
+                "/net/ether%d/%d/data", nic, etherfd);
+
+       if (write(ctlfd, type, sizeof(type)) != sizeof(type))
+               VIRTIO_DEV_ERRX(vqdev, "write to ctlfd failed");
+}
+
+/* net_receiveq_fn receives packets for the guest through the virtio networking
+ * device and the _vq virtio queue.
+ */
+void net_receiveq_fn(void *_vq)
+{
+       struct virtio_vq *vq = _vq;
+       uint32_t head;
+       uint32_t olen, ilen;
+       int num_read;
+       struct iovec *iov;
+       struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
+       int fd;
+       struct virtio_net_hdr_v1 *net_header;
+
+       fd = open(data_path, O_RDWR);
+       if (fd == -1)
+               VIRTIO_DEV_ERRX(vq->vqdev, "Could not open data file for ether1.");
+
+       if (!vq)
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                       "\n  %s:%d\n"
+                       "  Virtio device: (not sure which one): Error, device behavior.\n"
+                       "  The device must provide a valid virtio_vq as an argument to %s."
+                       , __FILE__, __LINE__, __func__);
+
+       if (vq->qready == 0x0)
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                       "The service function for queue '%s' was launched before the driver set QueueReady to 0x1.",
+                       vq->name);
+
+       iov = malloc(vq->qnum_max * sizeof(struct iovec));
+       assert(iov != NULL);
+
+       if (!dev->poke_guest) {
+               free(iov);
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                               "The 'poke_guest' function pointer was not set.");
+       }
+
+       for (;;) {
+               head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
+               if (olen) {
+                       free(iov);
+                       VIRTIO_DRI_ERRX(vq->vqdev,
+                               "The driver placed a device-readable buffer in the net device's receiveq.\n"
+                               "  See virtio-v1.0-cs04 s5.3.6.1 Device Operation");
+               }
+
+               /* For receive the virtio header is in iov[0], so we only want
+                * the packet to be read into iov[1] and above.
+                */
+               num_read = readv(fd, iov + 1, ilen - 1);
+               if (num_read < 0) {
+                       free(iov);
+                       VIRTIO_DEV_ERRX(vq->vqdev,
+                               "Encountered an error trying to read input from the ethernet device.");
+               }
+
+               /* See virtio spec virtio-v1.0-cs04 s5.1.6.3.2 Device Requirements:
+                * Setting Up Receive Buffers
+                *
+                * VIRTIO_NET_F_MRG_RXBUF is not currently negotiated.
+                * num_buffers will always be 1 if VIRTIO_NET_F_MRG_RXBUF is not
+                * negotiated.
+                */
+               net_header = iov[0].iov_base;
+               net_header->num_buffers = 1;
+               virtio_add_used_desc(vq, head, num_read + VIRTIO_HEADER_SIZE);
+
+               virtio_mmio_set_vring_irq(dev);
+               dev->poke_guest(dev->vec);
+       }
+}
+
+/* net_transmitq_fn transmits packets from the guest through the virtio
+ * networking device through the _vq virtio queue.
+ */
+void net_transmitq_fn(void *_vq)
+{
+       struct virtio_vq *vq = _vq;
+       uint32_t head;
+       uint32_t olen, ilen;
+       struct iovec *iov;
+       struct virtio_mmio_dev *dev = vq->vqdev->transport_dev;
+       void *stripped;
+       int ret;
+       int fd = open(data_path, O_RDWR);
+
+       if (fd == -1)
+               VIRTIO_DEV_ERRX(vq->vqdev, "Could not open data file for ether1.");
+
+       iov = malloc(vq->qnum_max * sizeof(struct iovec));
+       assert(iov != NULL);
+
+       if (!dev->poke_guest) {
+               free(iov);
+               VIRTIO_DEV_ERRX(vq->vqdev,
+                               "The 'poke_guest' function pointer was not set.");
+       }
+
+       for (;;) {
+               head = virtio_next_avail_vq_desc(vq, iov, &olen, &ilen);
+
+               if (ilen) {
+                       free(iov);
+                       VIRTIO_DRI_ERRX(vq->vqdev,
+                                       "The driver placed a device-writeable buffer in the network device's transmitq.\n"
+                                   "  See virtio-v1.0-cs04 s5.3.6.1 Device Operation");
+               }
+
+               /* Strip off the virtio header (the first 12 bytes), as it is
+                * not a part of the actual ethernet frame.
+                */
+               for (int i = 0; i < olen; i++) {
+                       stripped = iov[i].iov_base + VIRTIO_HEADER_SIZE;
+                       ret = write(fd, stripped, iov[i].iov_len - VIRTIO_HEADER_SIZE);
+                       assert(ret == iov[i].iov_len - VIRTIO_HEADER_SIZE);
+               }
+
+               virtio_add_used_desc(vq, head, 0);
+
+               virtio_mmio_set_vring_irq(dev);
+               dev->poke_guest(dev->vec);
+       }
+}