123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- /* 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 "gpxe/virtio-ring.h"
- #include "gpxe/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;
-
- BUG_ON(!vring_more_used(queue_index));
-
- 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,
- struct vring_list list[],
- unsigned int out, unsigned int in,
- int index, int num_added)
- {
- struct vring *vr = &vring[queue_index];
- int i, avail, head, prev;
-
- BUG_ON(queue_index >= QUEUE_NB);
- BUG_ON(out + in == 0);
-
- prev = 0;
- head = free_head[queue_index];
- for (i = head; out; i = vr->desc[i].next, out--) {
-
- vr->desc[i].flags = VRING_DESC_F_NEXT;
- vr->desc[i].addr = (u64)virt_to_phys(list->addr);
- vr->desc[i].len = list->length;
- prev = i;
- list++;
- }
- for ( ; in; i = vr->desc[i].next, in--) {
-
- vr->desc[i].flags = VRING_DESC_F_NEXT|VRING_DESC_F_WRITE;
- vr->desc[i].addr = (u64)virt_to_phys(list->addr);
- vr->desc[i].len = list->length;
- prev = i;
- list++;
- }
- vr->desc[prev].flags &= ~VRING_DESC_F_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;
- struct vring_list list[2];
-
- 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 */
-
- list[0].addr = (char*)&rx_hdr[token];
- list[0].length = sizeof(struct virtio_net_hdr);
- list[1].addr = (char*)&rx_buffer[token];
- list[1].length = ETH_FRAME_LEN;
-
- vring_add_buf(RX_INDEX, list, 0, 2, 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)
- {
- struct vring_list list[2];
-
- /*
- * 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);
-
- list[0].addr = (char*)&tx_virtio_hdr;
- list[0].length = sizeof(struct virtio_net_hdr);
- list[1].addr = (char*)&tx_eth_frame;
- list[1].length = ETH_FRAME_LEN;
-
- vring_add_buf(TX_INDEX, list, 2, 0, 0, 0);
-
- vring_kick(nic, TX_INDEX, 1);
-
- /*
- * 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);
- }
-
- /* 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;
- struct vring_list list[2];
-
- for (i = 0; i < RX_BUF_NB; i++) {
- list[0].addr = (char*)&rx_hdr[i];
- list[0].length = sizeof(struct virtio_net_hdr);
- list[1].addr = (char*)&rx_buffer[i];
- list[1].length = ETH_FRAME_LEN;
- vring_add_buf(RX_INDEX, list, 0, 2, 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 );
|