Added virtio network device.
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>
diff --git a/tests/vmm/vmrunkernel.c b/tests/vmm/vmrunkernel.c
index d00c441..da704f2 100644
--- a/tests/vmm/vmrunkernel.c
+++ b/tests/vmm/vmrunkernel.c
@@ -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 @@
},
.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 @@
#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_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_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 @@
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;
diff --git a/user/vmm/include/vmm/virtio_net.h b/user/vmm/include/vmm/virtio_net.h
index 31b8f54..c020e69 100644
--- a/user/vmm/include/vmm/virtio_net.h
+++ b/user/vmm/include/vmm/virtio_net.h
@@ -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 */
@@ -61,6 +60,10 @@
#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 @@
*/
#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);
diff --git a/user/vmm/include/vmm/vmm.h b/user/vmm/include/vmm/vmm.h
index a61d9ad..ca1d6ec 100644
--- a/user/vmm/include/vmm/vmm.h
+++ b/user/vmm/include/vmm/vmm.h
@@ -10,12 +10,13 @@
#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. */
diff --git a/user/vmm/virtio.c b/user/vmm/virtio.c
index 165cb47..f64d1e2 100644
--- a/user/vmm/virtio.c
+++ b/user/vmm/virtio.c
@@ -32,6 +32,11 @@
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
index 0000000..1a4ecad
--- /dev/null
+++ b/user/vmm/virtio_net.c
@@ -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);
+ }
+}