From 46a32aa9c74be70b7f247f4aae00951755784973 Mon Sep 17 00:00:00 2001 From: Avik Sil Date: Mon, 23 Sep 2013 14:07:35 +0530 Subject: Add virtio-net driver in libvirtio Signed-off-by: Avik Sil Signed-off-by: Nikunj A Dadhania --- board-qemu/slof/virtio-net.fs | 50 +++++- clients/net-snk/kernel/modules.c | 1 - lib/libvirtio/Makefile | 2 +- lib/libvirtio/virtio-net.c | 369 +++++++++++++++++++++++++++++++++++++++ lib/libvirtio/virtio-net.h | 43 +++++ lib/libvirtio/virtio.code | 43 +++++ lib/libvirtio/virtio.in | 5 + 7 files changed, 503 insertions(+), 10 deletions(-) create mode 100644 lib/libvirtio/virtio-net.c create mode 100644 lib/libvirtio/virtio-net.h diff --git a/board-qemu/slof/virtio-net.fs b/board-qemu/slof/virtio-net.fs index 8170f99..80ed15c 100644 --- a/board-qemu/slof/virtio-net.fs +++ b/board-qemu/slof/virtio-net.fs @@ -18,21 +18,55 @@ INSTANCE VARIABLE obp-tftp-package /vd-len BUFFER: virtiodev virtiodev virtio-setup-vd +0 VALUE virtio-net-priv +0 VALUE open-count : open ( -- okay? ) - open IF - \ my-unit 1 rtas-set-tce-bypass - my-args s" obp-tftp" $open-package obp-tftp-package ! - true + open-count 0= IF + open IF + \ my-unit 1 rtas-set-tce-bypass + my-args s" obp-tftp" $open-package obp-tftp-package ! + s" local-mac-address" get-node get-property not IF + virtiodev virtio-net-open dup not IF ." virtio-net-open failed" EXIT THEN + drop TO virtio-net-priv + THEN + true + ELSE + false + THEN ELSE - false + true THEN + open-count 1 + to open-count ; + : close ( -- ) - s" close" obp-tftp-package @ $call-method - \ my-unit 0 rtas-set-tce-bypass - close + open-count 0> IF + open-count 1 - dup to open-count + 0= IF + s" close" obp-tftp-package @ $call-method + virtio-net-priv virtio-net-close + \ my-unit 0 rtas-set-tce-bypass + close + THEN + THEN +; + +: read ( buf len -- actual ) + dup IF + virtio-net-read + ELSE + nip + THEN +; + +: write ( buf len -- actual ) + dup IF + virtio-net-write + ELSE + nip + THEN ; : load ( addr -- len ) diff --git a/clients/net-snk/kernel/modules.c b/clients/net-snk/kernel/modules.c index 4484bc3..bf4bbfe 100644 --- a/clients/net-snk/kernel/modules.c +++ b/clients/net-snk/kernel/modules.c @@ -38,7 +38,6 @@ typedef struct { static const mod_descriptor_t modules[] = { { "net_e1000", MOD_TYPE_NETWORK }, { "net_bcm", MOD_TYPE_NETWORK }, - { "net_virtio", MOD_TYPE_NETWORK }, { NULL, 0 } }; diff --git a/lib/libvirtio/Makefile b/lib/libvirtio/Makefile index e61457f..430f9a7 100644 --- a/lib/libvirtio/Makefile +++ b/lib/libvirtio/Makefile @@ -22,7 +22,7 @@ TARGET = ../libvirtio.a all: $(TARGET) -SRCS = virtio.c virtio-blk.c p9.c virtio-9p.c virtio-scsi.c +SRCS = virtio.c virtio-blk.c p9.c virtio-9p.c virtio-scsi.c virtio-net.c OBJS = $(SRCS:%.c=%.o) diff --git a/lib/libvirtio/virtio-net.c b/lib/libvirtio/virtio-net.c new file mode 100644 index 0000000..275da3a --- /dev/null +++ b/lib/libvirtio/virtio-net.c @@ -0,0 +1,369 @@ +/****************************************************************************** + * Copyright (c) 2011 IBM Corporation + * All rights reserved. + * This program and the accompanying materials + * are made available under the terms of the BSD License + * which accompanies this distribution, and is available at + * http://www.opensource.org/licenses/bsd-license.php + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ + +/* + * This is the implementation for the Virtio network device driver. Details + * about the virtio-net interface can be found in Rusty Russel's "Virtio PCI + * Card Specification v0.8.10", appendix C, which can be found here: + * + * http://ozlabs.org/~rusty/virtio-spec/virtio-spec.pdf + */ + +#include +#include +#include +#include +#include +#include +#include "virtio.h" +#include "virtio-net.h" + +#undef DEBUG +//#define DEBUG +#ifdef DEBUG +# define dprintf(fmt...) printf(fmt) +#else +# define dprintf(fmt...) +#endif + +#define sync() asm volatile (" sync \n" ::: "memory") + +/* PCI virtio header offsets */ +#define VIRTIOHDR_DEVICE_FEATURES 0 +#define VIRTIOHDR_GUEST_FEATURES 4 +#define VIRTIOHDR_QUEUE_ADDRESS 8 +#define VIRTIOHDR_QUEUE_SIZE 12 +#define VIRTIOHDR_QUEUE_SELECT 14 +#define VIRTIOHDR_QUEUE_NOTIFY 16 +#define VIRTIOHDR_DEVICE_STATUS 18 +#define VIRTIOHDR_ISR_STATUS 19 +#define VIRTIOHDR_DEVICE_CONFIG 20 +#define VIRTIOHDR_MAC_ADDRESS 20 + +struct virtio_device virtiodev; +struct vqs vq[2]; /* Information about virtqueues */ + +/* See Virtio Spec, appendix C, "Device Operation" */ +struct virtio_net_hdr { + uint8_t flags; + uint8_t gso_type; + uint16_t hdr_len; + uint16_t gso_size; + uint16_t csum_start; + uint16_t csum_offset; + // uint16_t num_buffers; /* Only if VIRTIO_NET_F_MRG_RXBUF */ +}; + +static uint16_t last_rx_idx; /* Last index in RX "used" ring */ + +/** + * Module init for virtio via PCI. + * Checks whether we're reponsible for the given device and set up + * the virtqueue configuration. + */ +static int virtionet_init_pci(struct virtio_device *dev) +{ + int i; + + dprintf("virtionet: doing virtionet_init_pci!\n"); + + if (!dev) + return -1; + + virtiodev.base = dev->base; + virtiodev.type = dev->type; + + /* Reset device */ + virtio_reset_device(&virtiodev); + + /* The queue information can be retrieved via the virtio header that + * can be found in the I/O BAR. First queue is the receive queue, + * second the transmit queue, and the forth is the control queue for + * networking options. + * We are only interested in the receive and transmit queue here. */ + + for (i=VQ_RX; i<=VQ_TX; i++) { + /* Select ring (0=RX, 1=TX): */ + vq[i].id = i-VQ_RX; + ci_write_16(virtiodev.base+VIRTIOHDR_QUEUE_SELECT, + cpu_to_le16(vq[i].id)); + + vq[i].size = le16_to_cpu(ci_read_16(virtiodev.base+VIRTIOHDR_QUEUE_SIZE)); + vq[i].desc = SLOF_alloc_mem_aligned(virtio_vring_size(vq[i].size), 4096); + if (!vq[i].desc) { + printf("memory allocation failed!\n"); + return -1; + } + memset(vq[i].desc, 0, virtio_vring_size(vq[i].size)); + ci_write_32(virtiodev.base+VIRTIOHDR_QUEUE_ADDRESS, + cpu_to_le32((long)vq[i].desc / 4096)); + vq[i].avail = (void*)vq[i].desc + + vq[i].size * sizeof(struct vring_desc); + vq[i].used = (void*)VQ_ALIGN((long)vq[i].avail + + vq[i].size * sizeof(struct vring_avail)); + + dprintf("%i: vq.id = %llx\nvq.size =%x\n vq.avail =%p\nvq.used=%p\n", + i, vq[i].id, vq[i].size, vq[i].avail, vq[i].used); + } + + /* Acknowledge device. */ + virtio_set_status(&virtiodev, VIRTIO_STAT_ACKNOWLEDGE); + + return 0; +} + +/** + * Initialize the virtio-net device. + * See the Virtio Spec, chapter 2.2.1 and Appendix C "Device Initialization" + * for details. + */ +static int virtionet_init(net_driver_t *driver) +{ + int i; + + dprintf("virtionet_init(%02x:%02x:%02x:%02x:%02x:%02x)\n", + driver->mac_addr[0], driver->mac_addr[1], + driver->mac_addr[2], driver->mac_addr[3], + driver->mac_addr[4], driver->mac_addr[5]); + + if (driver->running != 0) + return 0; + + /* Tell HV that we know how to drive the device. */ + virtio_set_status(&virtiodev, VIRTIO_STAT_ACKNOWLEDGE|VIRTIO_STAT_DRIVER); + + /* Device specific setup - we do not support special features right now */ + virtio_set_guest_features(&virtiodev, 0); + + /* Allocate memory for one transmit an multiple receive buffers */ + vq[VQ_RX].buf_mem = SLOF_alloc_mem((BUFFER_ENTRY_SIZE+sizeof(struct virtio_net_hdr)) + * RX_QUEUE_SIZE); + if (!vq[VQ_RX].buf_mem) { + printf("virtionet: Failed to allocate buffers!\n"); + virtio_set_status(&virtiodev, VIRTIO_STAT_FAILED); + return -1; + } + + /* Prepare receive buffer queue */ + for (i = 0; i < RX_QUEUE_SIZE; i++) { + struct vring_desc *desc; + /* Descriptor for net_hdr: */ + desc = &vq[VQ_RX].desc[i*2]; + desc->addr = (uint64_t)vq[VQ_RX].buf_mem + + i * (BUFFER_ENTRY_SIZE+sizeof(struct virtio_net_hdr)); + desc->len = sizeof(struct virtio_net_hdr); + desc->flags = VRING_DESC_F_NEXT | VRING_DESC_F_WRITE; + desc->next = i*2+1; + + /* Descriptor for data: */ + desc = &vq[VQ_RX].desc[i*2+1]; + desc->addr = vq[VQ_RX].desc[i*2].addr + sizeof(struct virtio_net_hdr); + desc->len = BUFFER_ENTRY_SIZE; + desc->flags = VRING_DESC_F_WRITE; + desc->next = 0; + + vq[VQ_RX].avail->ring[i] = i*2; + } + sync(); + vq[VQ_RX].avail->flags = VRING_AVAIL_F_NO_INTERRUPT; + vq[VQ_RX].avail->idx = RX_QUEUE_SIZE; + + last_rx_idx = vq[VQ_RX].used->idx; + + vq[VQ_TX].avail->flags = VRING_AVAIL_F_NO_INTERRUPT; + vq[VQ_TX].avail->idx = 0; + + /* Tell HV that setup succeeded */ + virtio_set_status(&virtiodev, VIRTIO_STAT_ACKNOWLEDGE + |VIRTIO_STAT_DRIVER + |VIRTIO_STAT_DRIVER_OK); + + /* Tell HV that RX queues are ready */ + virtio_queue_notify(&virtiodev, VQ_RX); + + driver->running = 1; + + return 0; +} + + +/** + * Shutdown driver. + * We've got to make sure that the hosts stops all transfers since the buffers + * in our main memory will become invalid after this module has been terminated. + */ +static int virtionet_term(net_driver_t *driver) +{ + dprintf("virtionet_term()\n"); + + if (driver->running == 0) + return 0; + + /* Quiesce device */ + virtio_set_status(&virtiodev, VIRTIO_STAT_FAILED); + + /* Reset device */ + virtio_reset_device(&virtiodev); + + driver->running = 0; + + return 0; +} + + +/** + * Transmit a packet + */ +static int virtionet_xmit(char *buf, int len) +{ + struct vring_desc *desc; + int id; + static struct virtio_net_hdr nethdr; + + if (len > BUFFER_ENTRY_SIZE) { + printf("virtionet: Packet too big!\n"); + return 0; + } + + dprintf("\nvirtionet_xmit(packet at %p, %d bytes)\n", buf, len); + + memset(&nethdr, 0, sizeof(nethdr)); + + /* Determine descriptor index */ + id = (vq[VQ_TX].avail->idx * 2) % vq[VQ_TX].size; + + /* Set up virtqueue descriptor for header */ + desc = &vq[VQ_TX].desc[id]; + desc->addr = (uint64_t)&nethdr; + desc->len = sizeof(struct virtio_net_hdr); + desc->flags = VRING_DESC_F_NEXT; + desc->next = id + 1; + + /* Set up virtqueue descriptor for data */ + desc = &vq[VQ_TX].desc[id+1]; + desc->addr = (uint64_t)buf; + desc->len = len; + desc->flags = 0; + desc->next = 0; + + vq[VQ_TX].avail->ring[vq[VQ_TX].avail->idx % vq[VQ_TX].size] = id; + sync(); + vq[VQ_TX].avail->idx += 1; + sync(); + + /* Tell HV that TX queue is ready */ + virtio_queue_notify(&virtiodev, VQ_TX); + + return len; +} + + +/** + * Receive a packet + */ +static int virtionet_receive(char *buf, int maxlen) +{ + int len = 0; + int id; + + if (last_rx_idx == vq[VQ_RX].used->idx) { + /* Nothing received yet */ + return 0; + } + + id = (vq[VQ_RX].used->ring[last_rx_idx % vq[VQ_RX].size].id + 1) + % vq[VQ_RX].size; + len = vq[VQ_RX].used->ring[last_rx_idx % vq[VQ_RX].size].len + - sizeof(struct virtio_net_hdr); + + dprintf("virtionet_receive() last_rx_idx=%i, vq[VQ_RX].used->idx=%i," + " id=%i len=%i\n", last_rx_idx, vq[VQ_RX].used->idx, id, len); + + if (len > maxlen) { + printf("virtio-net: Receive buffer not big enough!\n"); + len = maxlen; + } + +#if 0 + /* Dump packet */ + printf("\n"); + int i; + for (i=0; i<64; i++) { + printf(" %02x", *(uint8_t*)(vq[VQ_RX].desc[id].addr+i)); + if ((i%16)==15) + printf("\n"); + } + prinfk("\n"); +#endif + + /* Copy data to destination buffer */ + memcpy(buf, (void*)vq[VQ_RX].desc[id].addr, len); + + /* Move indices to next entries */ + last_rx_idx = last_rx_idx + 1; + + vq[VQ_RX].avail->ring[vq[VQ_RX].avail->idx % vq[VQ_RX].size] = id - 1; + sync(); + vq[VQ_RX].avail->idx += 1; + + /* Tell HV that RX queue entry is ready */ + virtio_queue_notify(&virtiodev, VQ_RX); + + return len; +} + +net_driver_t *virtionet_open(char *mac_addr, int len, struct virtio_device *dev) +{ + net_driver_t *driver; + + driver = SLOF_alloc_mem(sizeof(*driver)); + if (!driver) { + printf("Unable to allocate virtio-net driver\n"); + return NULL; + } + + memcpy(driver->mac_addr, mac_addr, 6); + driver->running = 0; + + if (virtionet_init_pci(dev)) + goto FAIL; + + if (virtionet_init(driver)) + goto FAIL; + + return driver; + +FAIL: SLOF_free_mem(driver, sizeof(*driver)); + return NULL; +} + +void virtionet_close(net_driver_t *driver) +{ + if (driver) { + virtionet_term(driver); + SLOF_free_mem(driver, sizeof(*driver)); + } +} + +int virtionet_read(char *buf, int len) +{ + if (buf) + return virtionet_receive(buf, len); + return -1; +} + +int virtionet_write(char *buf, int len) +{ + if (buf) + return virtionet_xmit(buf, len); + return -1; +} diff --git a/lib/libvirtio/virtio-net.h b/lib/libvirtio/virtio-net.h new file mode 100644 index 0000000..e2a2925 --- /dev/null +++ b/lib/libvirtio/virtio-net.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * Copyright (c) 2011 IBM Corporation + * All rights reserved. + * This program and the accompanying materials + * are made available under the terms of the BSD License + * which accompanies this distribution, and is available at + * http://www.opensource.org/licenses/bsd-license.php + * + * Contributors: + * IBM Corporation - initial implementation + *****************************************************************************/ + +#ifndef VIRTIO_NET_H +#define VIRTIO_NET_H + +#include + +#define RX_QUEUE_SIZE 16 +#define BUFFER_ENTRY_SIZE 1514 + +enum { + VQ_RX = 0, /* Receive Queue */ + VQ_TX = 1, /* Transmit Queue */ +}; + +struct vqs { + uint64_t id; /* Queue ID */ + uint32_t size; + void *buf_mem; + struct vring_desc *desc; + struct vring_avail *avail; + struct vring_used *used; +}; + +/* Device is identified by RX queue ID: */ +#define DEVICE_ID vq[0].id + +extern net_driver_t *virtionet_open(char *mac_addr, int len, struct virtio_device *dev); +extern void virtionet_close(net_driver_t *driver); +extern int virtionet_read(char *buf, int len); +extern int virtionet_write(char *buf, int len); + +#endif diff --git a/lib/libvirtio/virtio.code b/lib/libvirtio/virtio.code index 9c50c92..304849e 100644 --- a/lib/libvirtio/virtio.code +++ b/lib/libvirtio/virtio.code @@ -14,6 +14,7 @@ #include #include #include +#include /******** core virtio ********/ @@ -119,3 +120,45 @@ PRIM(virtio_X2d_scsi_X2d_send) TOS.n = virtioscsi_send(dev, req, resp, is_read, buf, blen); MIRP +/******** virtio-net ********/ + +// : virtio-net-open ( mac-addr-str len dev -- false | [ driver true ] ) +PRIM(virtio_X2d_net_X2d_open) +{ + void *dev = TOS.a; POP; + int len = TOS.u; POP; + char *mac_addr = TOS.a; + + net_driver_t *net_driver = virtionet_open(mac_addr, len, dev); + + if (net_driver) { + TOS.u = (unsigned long)net_driver; PUSH; + TOS.n = -1; + } else + TOS.n = 0; +} +MIRP + +// : virtio-net-close ( driver -- ) +PRIM(virtio_X2d_net_X2d_close) +{ + net_driver_t *driver = TOS.a; POP; + virtionet_close(driver); +} +MIRP + +// : virtio-net-read ( addr len -- actual ) +PRIM(virtio_X2d_net_X2d_read) +{ + int len = TOS.u; POP; + TOS.n = virtionet_read(TOS.a, len); +} +MIRP + +// : virtio-net-write ( addr len -- actual ) +PRIM(virtio_X2d_net_X2d_write) +{ + int len = TOS.u; POP; + TOS.n = virtionet_write(TOS.a, len); +} +MIRP diff --git a/lib/libvirtio/virtio.in b/lib/libvirtio/virtio.in index e62341d..c36d127 100644 --- a/lib/libvirtio/virtio.in +++ b/lib/libvirtio/virtio.in @@ -26,3 +26,8 @@ cod(virtio-scsi-send) cod(virtio-fs-init) cod(virtio-fs-shutdown) cod(virtio-fs-load) + +cod(virtio-net-open) +cod(virtio-net-close) +cod(virtio-net-read) +cod(virtio-net-write) -- cgit v1.1