aboutsummaryrefslogtreecommitdiffstats
path: root/roms/SLOF/lib/libvirtio/virtio-net.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/SLOF/lib/libvirtio/virtio-net.c')
-rw-r--r--roms/SLOF/lib/libvirtio/virtio-net.c384
1 files changed, 384 insertions, 0 deletions
diff --git a/roms/SLOF/lib/libvirtio/virtio-net.c b/roms/SLOF/lib/libvirtio/virtio-net.c
new file mode 100644
index 000000000..5a0d19088
--- /dev/null
+++ b/roms/SLOF/lib/libvirtio/virtio-net.c
@@ -0,0 +1,384 @@
+/******************************************************************************
+ * 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 <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <helpers.h>
+#include <cache.h>
+#include <byteorder.h>
+#include "virtio-net.h"
+#include "virtio-internal.h"
+
+#undef DEBUG
+//#define DEBUG
+#ifdef DEBUG
+# define dprintf(fmt...) do { printf(fmt); } while(0)
+#else
+# define dprintf(fmt...)
+#endif
+
+#define sync() asm volatile (" sync \n" ::: "memory")
+
+#define DRIVER_FEATURE_SUPPORT (VIRTIO_NET_F_MAC | VIRTIO_F_VERSION_1)
+
+/* 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 unsigned int net_hdr_size;
+
+struct virtio_net_hdr_v1 {
+ uint8_t flags;
+ uint8_t gso_type;
+ le16 hdr_len;
+ le16 gso_size;
+ le16 csum_start;
+ le16 csum_offset;
+ le16 num_buffers;
+};
+
+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_net *vnet, struct virtio_device *dev)
+{
+ struct virtio_device *vdev = &vnet->vdev;
+
+ dprintf("virtionet: doing virtionet_init_pci!\n");
+
+ if (!dev)
+ return -1;
+
+ /* make a copy of the device structure */
+ memcpy(vdev, dev, sizeof(struct virtio_device));
+
+ /* Reset device */
+ virtio_reset_device(vdev);
+
+ /* Acknowledge device. */
+ virtio_set_status(vdev, 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(struct virtio_net *vnet)
+{
+ int i;
+ int status = VIRTIO_STAT_ACKNOWLEDGE | VIRTIO_STAT_DRIVER;
+ struct virtio_device *vdev = &vnet->vdev;
+ net_driver_t *driver = &vnet->driver;
+ struct vqs *vq_tx, *vq_rx;
+
+ 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(vdev, status);
+
+ /* Device specific setup */
+ if (vdev->features & VIRTIO_F_VERSION_1) {
+ if (virtio_negotiate_guest_features(vdev, DRIVER_FEATURE_SUPPORT))
+ goto dev_error;
+ net_hdr_size = sizeof(struct virtio_net_hdr_v1);
+ virtio_get_status(vdev, &status);
+ } else {
+ net_hdr_size = sizeof(struct virtio_net_hdr);
+ virtio_set_guest_features(vdev, 0);
+ }
+
+ /* 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. */
+ vq_rx = virtio_queue_init_vq(vdev, VQ_RX);
+ vq_tx = virtio_queue_init_vq(vdev, VQ_TX);
+ if (!vq_rx || !vq_tx) {
+ virtio_set_status(vdev, VIRTIO_STAT_ACKNOWLEDGE|VIRTIO_STAT_DRIVER
+ |VIRTIO_STAT_FAILED);
+ return -1;
+ }
+
+ /* Allocate memory for one transmit an multiple receive buffers */
+ vq_rx->buf_mem = SLOF_alloc_mem((BUFFER_ENTRY_SIZE+net_hdr_size)
+ * RX_QUEUE_SIZE);
+ if (!vq_rx->buf_mem) {
+ printf("virtionet: Failed to allocate buffers!\n");
+ goto dev_error;
+ }
+
+ /* Prepare receive buffer queue */
+ for (i = 0; i < RX_QUEUE_SIZE; i++) {
+ uint64_t addr = (uint64_t)vq_rx->buf_mem
+ + i * (BUFFER_ENTRY_SIZE+net_hdr_size);
+ uint32_t id = i*2;
+ /* Descriptor for net_hdr: */
+ virtio_fill_desc(vq_rx, id, vdev->features, addr, net_hdr_size,
+ VRING_DESC_F_NEXT | VRING_DESC_F_WRITE, id + 1);
+
+ /* Descriptor for data: */
+ virtio_fill_desc(vq_rx, id + 1, vdev->features, addr + net_hdr_size,
+ BUFFER_ENTRY_SIZE, VRING_DESC_F_WRITE, 0);
+
+ vq_rx->avail->ring[i] = virtio_cpu_to_modern16(vdev, id);
+ }
+ sync();
+
+ vq_rx->avail->flags = virtio_cpu_to_modern16(vdev, VRING_AVAIL_F_NO_INTERRUPT);
+ vq_rx->avail->idx = virtio_cpu_to_modern16(vdev, RX_QUEUE_SIZE);
+
+ last_rx_idx = virtio_modern16_to_cpu(vdev, vq_rx->used->idx);
+
+ vq_tx->avail->flags = virtio_cpu_to_modern16(vdev, VRING_AVAIL_F_NO_INTERRUPT);
+ vq_tx->avail->idx = 0;
+
+ /* Tell HV that setup succeeded */
+ status |= VIRTIO_STAT_DRIVER_OK;
+ virtio_set_status(vdev, status);
+
+ /* Tell HV that RX queues are ready */
+ virtio_queue_notify(vdev, VQ_RX);
+
+ driver->running = 1;
+ for(i = 0; i < (int)sizeof(driver->mac_addr); i++) {
+ driver->mac_addr[i] = virtio_get_config(vdev, i, 1);
+ }
+ return 0;
+
+dev_error:
+ status |= VIRTIO_STAT_FAILED;
+ virtio_set_status(vdev, status);
+ return -1;
+}
+
+
+/**
+ * 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(struct virtio_net *vnet)
+{
+ struct virtio_device *vdev = &vnet->vdev;
+ net_driver_t *driver = &vnet->driver;
+ struct vqs *vq_tx = &vnet->vdev.vq[VQ_TX];
+ struct vqs *vq_rx = &vnet->vdev.vq[VQ_RX];
+
+ dprintf("virtionet_term()\n");
+
+ if (driver->running == 0)
+ return 0;
+
+ /* Quiesce device */
+ virtio_set_status(vdev, VIRTIO_STAT_FAILED);
+
+ /* Reset device */
+ virtio_reset_device(vdev);
+
+ driver->running = 0;
+
+ SLOF_free_mem(vq_rx->buf_mem,
+ (BUFFER_ENTRY_SIZE+net_hdr_size) * RX_QUEUE_SIZE);
+ vq_rx->buf_mem = NULL;
+
+ virtio_queue_term_vq(vdev, vq_rx, VQ_RX);
+ virtio_queue_term_vq(vdev, vq_tx, VQ_TX);
+
+ return 0;
+}
+
+
+/**
+ * Transmit a packet
+ */
+static int virtionet_xmit(struct virtio_net *vnet, char *buf, int len)
+{
+ int id, idx;
+ static struct virtio_net_hdr_v1 nethdr_v1;
+ static struct virtio_net_hdr nethdr_legacy;
+ void *nethdr = &nethdr_legacy;
+ struct virtio_device *vdev = &vnet->vdev;
+ struct vqs *vq_tx = &vdev->vq[VQ_TX];
+
+ if (len > BUFFER_ENTRY_SIZE) {
+ printf("virtionet: Packet too big!\n");
+ return 0;
+ }
+
+ dprintf("\nvirtionet_xmit(packet at %p, %d bytes)\n", buf, len);
+
+ if (vdev->features & VIRTIO_F_VERSION_1)
+ nethdr = &nethdr_v1;
+
+ memset(nethdr, 0, net_hdr_size);
+
+ /* Determine descriptor index */
+ idx = virtio_modern16_to_cpu(vdev, vq_tx->avail->idx);
+ id = (idx * 2) % vq_tx->size;
+
+ virtio_free_desc(vq_tx, id, vdev->features);
+ virtio_free_desc(vq_tx, id + 1, vdev->features);
+
+ /* Set up virtqueue descriptor for header */
+ virtio_fill_desc(vq_tx, id, vdev->features, (uint64_t)nethdr,
+ net_hdr_size, VRING_DESC_F_NEXT, id + 1);
+
+ /* Set up virtqueue descriptor for data */
+ virtio_fill_desc(vq_tx, id + 1, vdev->features, (uint64_t)buf, len, 0, 0);
+
+ vq_tx->avail->ring[idx % vq_tx->size] = virtio_cpu_to_modern16(vdev, id);
+ sync();
+ vq_tx->avail->idx = virtio_cpu_to_modern16(vdev, idx + 1);
+ sync();
+
+ /* Tell HV that TX queue is ready */
+ virtio_queue_notify(vdev, VQ_TX);
+
+ return len;
+}
+
+
+/**
+ * Receive a packet
+ */
+static int virtionet_receive(struct virtio_net *vnet, char *buf, int maxlen)
+{
+ uint32_t len = 0;
+ uint32_t id, idx;
+ uint16_t avail_idx;
+ struct virtio_device *vdev = &vnet->vdev;
+ struct vqs *vq_rx = &vnet->vdev.vq[VQ_RX];
+
+ idx = virtio_modern16_to_cpu(vdev, vq_rx->used->idx);
+
+ if (last_rx_idx == idx) {
+ /* Nothing received yet */
+ return 0;
+ }
+
+ id = (virtio_modern32_to_cpu(vdev, vq_rx->used->ring[last_rx_idx % vq_rx->size].id) + 1)
+ % vq_rx->size;
+ len = virtio_modern32_to_cpu(vdev, vq_rx->used->ring[last_rx_idx % vq_rx->size].len)
+ - net_hdr_size;
+ dprintf("virtionet_receive() last_rx_idx=%i, vq_rx->used->idx=%i,"
+ " id=%i len=%i\n", last_rx_idx, vq_rx->used->idx, id, len);
+
+ if (len > (uint32_t)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_rx->desc[id].addr+i));
+ if ((i%16)==15)
+ printf("\n");
+ }
+ prinfk("\n");
+#endif
+
+ /* Copy data to destination buffer */
+ memcpy(buf, virtio_desc_addr(vdev, VQ_RX, id), len);
+
+ /* Move indices to next entries */
+ last_rx_idx = last_rx_idx + 1;
+
+ avail_idx = virtio_modern16_to_cpu(vdev, vq_rx->avail->idx);
+ vq_rx->avail->ring[avail_idx % vq_rx->size] = virtio_cpu_to_modern16(vdev, id - 1);
+ sync();
+ vq_rx->avail->idx = virtio_cpu_to_modern16(vdev, avail_idx + 1);
+
+ /* Tell HV that RX queue entry is ready */
+ virtio_queue_notify(vdev, VQ_RX);
+
+ return len;
+}
+
+struct virtio_net *virtionet_open(struct virtio_device *dev)
+{
+ struct virtio_net *vnet;
+
+ vnet = SLOF_alloc_mem(sizeof(*vnet));
+ if (!vnet) {
+ printf("Unable to allocate virtio-net driver\n");
+ return NULL;
+ }
+
+ vnet->driver.running = 0;
+
+ if (virtionet_init_pci(vnet, dev))
+ goto FAIL;
+
+ if (virtionet_init(vnet))
+ goto FAIL;
+
+ return vnet;
+
+FAIL:
+ SLOF_free_mem(vnet, sizeof(*vnet));
+ return NULL;
+}
+
+void virtionet_close(struct virtio_net *vnet)
+{
+ if (vnet) {
+ virtionet_term(vnet);
+ SLOF_free_mem(vnet, sizeof(*vnet));
+ }
+}
+
+int virtionet_read(struct virtio_net *vnet, char *buf, int len)
+{
+ if (vnet && buf)
+ return virtionet_receive(vnet, buf, len);
+ return -1;
+}
+
+int virtionet_write(struct virtio_net *vnet, char *buf, int len)
+{
+ if (vnet && buf)
+ return virtionet_xmit(vnet, buf, len);
+ return -1;
+}