aboutsummaryrefslogtreecommitdiffstats
path: root/gpxe/src/drivers/net/virtio-net.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpxe/src/drivers/net/virtio-net.c')
-rw-r--r--gpxe/src/drivers/net/virtio-net.c492
1 files changed, 492 insertions, 0 deletions
diff --git a/gpxe/src/drivers/net/virtio-net.c b/gpxe/src/drivers/net/virtio-net.c
new file mode 100644
index 00000000..4ec154df
--- /dev/null
+++ b/gpxe/src/drivers/net/virtio-net.c
@@ -0,0 +1,492 @@
+/* virtio-net.c - etherboot driver for virtio network interface
+ *
+ * (c) Copyright 2008 Bull S.A.S.
+ *
+ * Author: Laurent Vivier <Laurent.Vivier@bull.net>
+ *
+ * some parts from Linux Virtio PCI driver
+ *
+ * Copyright IBM Corp. 2007
+ * Authors: Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * some parts from Linux Virtio Ring
+ *
+ * Copyright Rusty Russell IBM Corporation 2007
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ *
+ */
+
+#include "etherboot.h"
+#include "nic.h"
+#include "virtio-ring.h"
+#include "virtio-pci.h"
+#include "virtio-net.h"
+
+#define BUG() do { \
+ printf("BUG: failure at %s:%d/%s()!\n", \
+ __FILE__, __LINE__, __FUNCTION__); \
+ while(1); \
+} while (0)
+#define BUG_ON(condition) do { if (condition) BUG(); } while (0)
+
+/* Ethernet header */
+
+struct eth_hdr {
+ unsigned char dst_addr[ETH_ALEN];
+ unsigned char src_addr[ETH_ALEN];
+ unsigned short type;
+};
+
+struct eth_frame {
+ struct eth_hdr hdr;
+ unsigned char data[ETH_FRAME_LEN];
+};
+
+typedef unsigned char virtio_queue_t[PAGE_MASK + vring_size(MAX_QUEUE_NUM)];
+
+/* TX: virtio header and eth buffer */
+
+static struct virtio_net_hdr tx_virtio_hdr;
+static struct eth_frame tx_eth_frame;
+
+/* RX: virtio headers and buffers */
+
+#define RX_BUF_NB 6
+static struct virtio_net_hdr rx_hdr[RX_BUF_NB];
+static unsigned char rx_buffer[RX_BUF_NB][ETH_FRAME_LEN];
+
+/* virtio queues and vrings */
+
+enum {
+ RX_INDEX = 0,
+ TX_INDEX,
+ QUEUE_NB
+};
+
+static virtio_queue_t queue[QUEUE_NB];
+static struct vring vring[QUEUE_NB];
+static u16 free_head[QUEUE_NB];
+static u16 last_used_idx[QUEUE_NB];
+static u16 vdata[QUEUE_NB][MAX_QUEUE_NUM];
+
+/*
+ * Virtio PCI interface
+ *
+ */
+
+static int vp_find_vq(struct nic *nic, int queue_index)
+{
+ struct vring * vr = &vring[queue_index];
+ u16 num;
+
+ /* select the queue */
+
+ outw(queue_index, nic->ioaddr + VIRTIO_PCI_QUEUE_SEL);
+
+ /* check if the queue is available */
+
+ num = inw(nic->ioaddr + VIRTIO_PCI_QUEUE_NUM);
+ if (!num) {
+ printf("ERROR: queue size is 0\n");
+ return -1;
+ }
+
+ if (num > MAX_QUEUE_NUM) {
+ printf("ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM);
+ return -1;
+ }
+
+ /* check if the queue is already active */
+
+ if (inl(nic->ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
+ printf("ERROR: queue already active\n");
+ return -1;
+ }
+
+ /* initialize the queue */
+
+ vring_init(vr, num, (unsigned char*)&queue[queue_index]);
+
+ /* activate the queue
+ *
+ * NOTE: vr->desc is initialized by vring_init()
+ */
+
+ outl((unsigned long)virt_to_phys(vr->desc) >> PAGE_SHIFT,
+ nic->ioaddr + VIRTIO_PCI_QUEUE_PFN);
+
+ return num;
+}
+
+/*
+ * Virtual ring management
+ *
+ */
+
+static void vring_enable_cb(int queue_index)
+{
+ vring[queue_index].avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
+}
+
+static void vring_disable_cb(int queue_index)
+{
+ vring[queue_index].avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
+}
+
+/*
+ * vring_free
+ *
+ * put at the begin of the free list the current desc[head]
+ */
+
+static void vring_detach(int queue_index, unsigned int head)
+{
+ struct vring *vr = &vring[queue_index];
+ unsigned int i;
+
+ /* find end of given descriptor */
+
+ i = head;
+ while (vr->desc[i].flags & VRING_DESC_F_NEXT)
+ i = vr->desc[i].next;
+
+ /* link it with free list and point to it */
+
+ vr->desc[i].next = free_head[queue_index];
+ wmb();
+ free_head[queue_index] = head;
+}
+
+/*
+ * vring_more_used
+ *
+ * is there some used buffers ?
+ *
+ */
+
+static inline int vring_more_used(int queue_index)
+{
+ wmb();
+ return last_used_idx[queue_index] != vring[queue_index].used->idx;
+}
+
+/*
+ * vring_get_buf
+ *
+ * get a buffer from the used list
+ *
+ */
+
+static int vring_get_buf(int queue_index, unsigned int *len)
+{
+ struct vring *vr = &vring[queue_index];
+ struct vring_used_elem *elem;
+ u32 id;
+ int ret;
+
+ elem = &vr->used->ring[last_used_idx[queue_index] % vr->num];
+ wmb();
+ id = elem->id;
+ if (len != NULL)
+ *len = elem->len;
+
+ ret = vdata[queue_index][id];
+
+ vring_detach(queue_index, id);
+
+ last_used_idx[queue_index]++;
+
+ return ret;
+}
+
+static void vring_add_buf(int queue_index, int index, int num_added)
+{
+ struct vring *vr = &vring[queue_index];
+ int i, avail, head;
+
+ BUG_ON(queue_index >= QUEUE_NB);
+
+ head = free_head[queue_index];
+ i = head;
+
+ if (queue_index == TX_INDEX) {
+
+ BUG_ON(index != 0);
+
+ /* add header into vring */
+
+ vr->desc[i].flags = VRING_DESC_F_NEXT;
+ vr->desc[i].addr = (u64)virt_to_phys(&tx_virtio_hdr);
+ vr->desc[i].len = sizeof(struct virtio_net_hdr);
+ i = vr->desc[i].next;
+
+ /* add frame buffer into vring */
+
+ vr->desc[i].flags = 0;
+ vr->desc[i].addr = (u64)virt_to_phys(&tx_eth_frame);
+ vr->desc[i].len = ETH_FRAME_LEN;
+ i = vr->desc[i].next;
+
+ } else if (queue_index == RX_INDEX) {
+
+ BUG_ON(index >= RX_BUF_NB);
+
+ /* add header into vring */
+
+ vr->desc[i].flags = VRING_DESC_F_NEXT|VRING_DESC_F_WRITE;
+ vr->desc[i].addr = (u64)virt_to_phys(&rx_hdr[index]);
+ vr->desc[i].len = sizeof(struct virtio_net_hdr);
+ i = vr->desc[i].next;
+
+ /* add frame buffer into vring */
+
+ vr->desc[i].flags = VRING_DESC_F_WRITE;
+ vr->desc[i].addr = (u64)virt_to_phys(&rx_buffer[index]);
+ vr->desc[i].len = ETH_FRAME_LEN;
+ i = vr->desc[i].next;
+ }
+
+ free_head[queue_index] = i;
+
+ vdata[queue_index][head] = index;
+
+ avail = (vr->avail->idx + num_added) % vr->num;
+ vr->avail->ring[avail] = head;
+ wmb();
+}
+
+static void vring_kick(struct nic *nic, int queue_index, int num_added)
+{
+ struct vring *vr = &vring[queue_index];
+
+ wmb();
+ vr->avail->idx += num_added;
+
+ mb();
+ if (!(vr->used->flags & VRING_USED_F_NO_NOTIFY))
+ vp_notify(nic, queue_index);
+}
+
+/*
+ * virtnet_disable
+ *
+ * Turn off ethernet interface
+ *
+ */
+
+static void virtnet_disable(struct nic *nic)
+{
+ int i;
+
+ for (i = 0; i < QUEUE_NB; i++) {
+ vring_disable_cb(i);
+ vp_del_vq(nic, i);
+ }
+ vp_reset(nic);
+}
+
+/*
+ * virtnet_poll
+ *
+ * Wait for a frame
+ *
+ * return true if there is a packet ready to read
+ *
+ * nic->packet should contain data on return
+ * nic->packetlen should contain length of data
+ *
+ */
+static int virtnet_poll(struct nic *nic, int retrieve)
+{
+ unsigned int len;
+ u16 token;
+ struct virtio_net_hdr *hdr;
+
+ if (!vring_more_used(RX_INDEX))
+ return 0;
+
+ if (!retrieve)
+ return 1;
+
+ token = vring_get_buf(RX_INDEX, &len);
+
+ BUG_ON(len > sizeof(struct virtio_net_hdr) + ETH_FRAME_LEN);
+
+ hdr = &rx_hdr[token]; /* FIXME: check flags */
+ len -= sizeof(struct virtio_net_hdr);
+
+ nic->packetlen = len;
+ memcpy(nic->packet, (char *)rx_buffer[token], nic->packetlen);
+
+ /* add buffer to desc */
+
+ vring_add_buf(RX_INDEX, token, 0);
+ vring_kick(nic, RX_INDEX, 1);
+
+ return 1;
+}
+
+/*
+ *
+ * virtnet_transmit
+ *
+ * Transmit a frame
+ *
+ */
+
+static void virtnet_transmit(struct nic *nic, const char *destaddr,
+ unsigned int type, unsigned int len, const char *data)
+{
+ /*
+ * from http://www.etherboot.org/wiki/dev/devmanual :
+ * "You do not need more than one transmit buffer."
+ */
+
+ /* FIXME: initialize header according to vp_get_features() */
+
+ tx_virtio_hdr.flags = 0;
+ tx_virtio_hdr.csum_offset = 0;
+ tx_virtio_hdr.csum_start = 0;
+ tx_virtio_hdr.gso_type = VIRTIO_NET_HDR_GSO_NONE;
+ tx_virtio_hdr.gso_size = 0;
+ tx_virtio_hdr.hdr_len = 0;
+
+ /* add ethernet frame into vring */
+
+ BUG_ON(len > sizeof(tx_eth_frame.data));
+
+ memcpy(tx_eth_frame.hdr.dst_addr, destaddr, ETH_ALEN);
+ memcpy(tx_eth_frame.hdr.src_addr, nic->node_addr, ETH_ALEN);
+ tx_eth_frame.hdr.type = htons(type);
+ memcpy(tx_eth_frame.data, data, len);
+
+ vring_add_buf(TX_INDEX, 0, 0);
+
+ /*
+ * http://www.etherboot.org/wiki/dev/devmanual
+ *
+ * "You should ensure the packet is fully transmitted
+ * before returning from this routine"
+ */
+
+ while (vring_more_used(TX_INDEX)) {
+ mb();
+ udelay(10);
+ }
+
+ vring_kick(nic, TX_INDEX, 1);
+
+ /* free desc */
+
+ (void)vring_get_buf(TX_INDEX, NULL);
+}
+
+static void virtnet_irq(struct nic *nic __unused, irq_action_t action)
+{
+ switch ( action ) {
+ case DISABLE :
+ vring_disable_cb(RX_INDEX);
+ vring_disable_cb(TX_INDEX);
+ break;
+ case ENABLE :
+ vring_enable_cb(RX_INDEX);
+ vring_enable_cb(TX_INDEX);
+ break;
+ case FORCE :
+ break;
+ }
+}
+
+static void provide_buffers(struct nic *nic)
+{
+ int i;
+
+ for (i = 0; i < RX_BUF_NB; i++)
+ vring_add_buf(RX_INDEX, i, i);
+
+ /* nofify */
+
+ vring_kick(nic, RX_INDEX, i);
+}
+
+static struct nic_operations virtnet_operations = {
+ .connect = dummy_connect,
+ .poll = virtnet_poll,
+ .transmit = virtnet_transmit,
+ .irq = virtnet_irq,
+};
+
+/*
+ * virtnet_probe
+ *
+ * Look for a virtio network adapter
+ *
+ */
+
+static int virtnet_probe(struct nic *nic, struct pci_device *pci)
+{
+ u32 features;
+ int i;
+
+ /* Mask the bit that says "this is an io addr" */
+
+ nic->ioaddr = pci->ioaddr & ~3;
+
+ /* Copy IRQ from PCI information */
+
+ nic->irqno = pci->irq;
+
+ printf("I/O address 0x%08x, IRQ #%d\n", nic->ioaddr, nic->irqno);
+
+ adjust_pci_device(pci);
+
+ vp_reset(nic);
+
+ features = vp_get_features(nic);
+ if (features & (1 << VIRTIO_NET_F_MAC)) {
+ vp_get(nic, offsetof(struct virtio_net_config, mac),
+ nic->node_addr, ETH_ALEN);
+ printf("MAC address ");
+ for (i = 0; i < ETH_ALEN; i++) {
+ printf("%02x%c", nic->node_addr[i],
+ (i == ETH_ALEN - 1) ? '\n' : ':');
+ }
+ }
+
+ /* initialize emit/receive queue */
+
+ for (i = 0; i < QUEUE_NB; i++) {
+ free_head[i] = 0;
+ last_used_idx[i] = 0;
+ memset((char*)&queue[i], 0, sizeof(queue[i]));
+ if (vp_find_vq(nic, i) == -1)
+ printf("Cannot register queue #%d\n", i);
+ }
+
+ /* provide some receive buffers */
+
+ provide_buffers(nic);
+
+ /* define NIC interface */
+
+ nic->nic_op = &virtnet_operations;
+
+ /* driver is ready */
+
+ vp_set_features(nic, features & (1 << VIRTIO_NET_F_MAC));
+ vp_set_status(nic, VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK);
+
+ return 1;
+}
+
+static struct pci_device_id virtnet_nics[] = {
+PCI_ROM(0x1af4, 0x1000, "virtio-net", "Virtio Network Interface"),
+};
+
+PCI_DRIVER ( virtnet_driver, virtnet_nics, PCI_NO_CLASS );
+
+DRIVER ( "VIRTIO-NET", nic_driver, pci_driver, virtnet_driver,
+ virtnet_probe, virtnet_disable );