diff options
Diffstat (limited to 'contrib/syslinux-4.02/gpxe/src/drivers/net/virtio-net.c')
-rw-r--r-- | contrib/syslinux-4.02/gpxe/src/drivers/net/virtio-net.c | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/contrib/syslinux-4.02/gpxe/src/drivers/net/virtio-net.c b/contrib/syslinux-4.02/gpxe/src/drivers/net/virtio-net.c new file mode 100644 index 0000000..49fcc1c --- /dev/null +++ b/contrib/syslinux-4.02/gpxe/src/drivers/net/virtio-net.c @@ -0,0 +1,307 @@ +/* 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]; +}; + +/* 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 struct vring_virtqueue virtqueue[QUEUE_NB]; + +/* + * 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(&virtqueue[i]); + vp_del_vq(nic->ioaddr, i); + } + vp_reset(nic->ioaddr); +} + +/* + * 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(&virtqueue[RX_INDEX])) + return 0; + + if (!retrieve) + return 1; + + token = vring_get_buf(&virtqueue[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(&virtqueue[RX_INDEX], list, 0, 2, token, 0); + vring_kick(nic->ioaddr, &virtqueue[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(&virtqueue[TX_INDEX], list, 2, 0, 0, 0); + + vring_kick(nic->ioaddr, &virtqueue[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(&virtqueue[TX_INDEX])) { + mb(); + udelay(10); + } + + /* free desc */ + + (void)vring_get_buf(&virtqueue[TX_INDEX], NULL); +} + +static void virtnet_irq(struct nic *nic __unused, irq_action_t action) +{ + switch ( action ) { + case DISABLE : + vring_disable_cb(&virtqueue[RX_INDEX]); + vring_disable_cb(&virtqueue[TX_INDEX]); + break; + case ENABLE : + vring_enable_cb(&virtqueue[RX_INDEX]); + vring_enable_cb(&virtqueue[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(&virtqueue[RX_INDEX], list, 0, 2, i, i); + } + + /* nofify */ + + vring_kick(nic->ioaddr, &virtqueue[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->ioaddr); + + features = vp_get_features(nic->ioaddr); + if (features & (1 << VIRTIO_NET_F_MAC)) { + vp_get(nic->ioaddr, 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++) { + virtqueue[i].free_head = 0; + virtqueue[i].last_used_idx = 0; + memset((char*)&virtqueue[i].queue, 0, sizeof(virtqueue[i].queue)); + if (vp_find_vq(nic->ioaddr, i, &virtqueue[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->ioaddr, features & (1 << VIRTIO_NET_F_MAC)); + vp_set_status(nic->ioaddr, 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", 0), +}; + +PCI_DRIVER ( virtnet_driver, virtnet_nics, PCI_NO_CLASS ); + +DRIVER ( "VIRTIO-NET", nic_driver, pci_driver, virtnet_driver, + virtnet_probe, virtnet_disable ); |