/****************************************************************************** * 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-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 responsible 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; }