276 lines
6.7 KiB
C
276 lines
6.7 KiB
C
/******************************************************************************
|
|
* 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 "netdriver_int.h"
|
|
#include <libhvcall.h>
|
|
#include <virtio.h>
|
|
#include <string.h>
|
|
#include "virtio-net.h"
|
|
|
|
|
|
#define sync() asm volatile (" sync \n" ::: "memory")
|
|
|
|
|
|
struct virtio_device virtiodev;
|
|
struct vqs vq[2]; /* Information about virtqueues */
|
|
|
|
|
|
/* 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 uint16_t last_rx_idx; /* Last index in RX "used" ring */
|
|
|
|
|
|
/**
|
|
* Initialize the virtio-net device.
|
|
* See the Virtio Spec, chapter 2.2.1 and Appendix C "Device Initialization"
|
|
* for details.
|
|
*/
|
|
static int virtionet_init(void)
|
|
{
|
|
int i;
|
|
|
|
dprintk("virtionet_init(%02x:%02x:%02x:%02x:%02x:%02x)\n",
|
|
snk_module_interface->mac_addr[0], snk_module_interface->mac_addr[1],
|
|
snk_module_interface->mac_addr[2], snk_module_interface->mac_addr[3],
|
|
snk_module_interface->mac_addr[4], snk_module_interface->mac_addr[5]);
|
|
|
|
if (snk_module_interface->running != 0)
|
|
return 0;
|
|
|
|
/* Tell HV that we know how to drive the device. */
|
|
virtio_set_status(&virtiodev, VIRTIO_STAT_ACKNOWLEDGE|VIRTIO_STAT_DRIVER);
|
|
|
|
/* Device specific setup - we do not support special features right now */
|
|
virtio_set_guest_features(&virtiodev, 0);
|
|
|
|
/* Allocate memory for one transmit an multiple receive buffers */
|
|
vq[VQ_RX].buf_mem = malloc((BUFFER_ENTRY_SIZE+sizeof(struct virtio_net_hdr))
|
|
* RX_QUEUE_SIZE);
|
|
if (!vq[VQ_RX].buf_mem) {
|
|
printk("virtionet: Failed to allocate buffers!\n");
|
|
virtio_set_status(&virtiodev, VIRTIO_STAT_FAILED);
|
|
return -1;
|
|
}
|
|
|
|
/* Prepare receive buffer queue */
|
|
for (i = 0; i < RX_QUEUE_SIZE; i++) {
|
|
struct vring_desc *desc;
|
|
/* Descriptor for net_hdr: */
|
|
desc = &vq[VQ_RX].desc[i*2];
|
|
desc->addr = (uint64_t)vq[VQ_RX].buf_mem
|
|
+ i * (BUFFER_ENTRY_SIZE+sizeof(struct virtio_net_hdr));
|
|
// printk("RX buf %i addr = 0x%llx\n", i, desc->addr);
|
|
desc->len = sizeof(struct virtio_net_hdr);
|
|
desc->flags = VRING_DESC_F_NEXT | VRING_DESC_F_WRITE;
|
|
desc->next = i*2+1;
|
|
|
|
/* Descriptor for data: */
|
|
desc = &vq[VQ_RX].desc[i*2+1];
|
|
desc->addr = vq[VQ_RX].desc[i*2].addr + sizeof(struct virtio_net_hdr);
|
|
desc->len = BUFFER_ENTRY_SIZE;
|
|
desc->flags = VRING_DESC_F_WRITE;
|
|
desc->next = 0;
|
|
|
|
vq[VQ_RX].avail->ring[i] = i*2;
|
|
}
|
|
sync();
|
|
vq[VQ_RX].avail->flags = VRING_AVAIL_F_NO_INTERRUPT;
|
|
vq[VQ_RX].avail->idx = RX_QUEUE_SIZE;
|
|
|
|
last_rx_idx = vq[VQ_RX].used->idx;
|
|
|
|
vq[VQ_TX].avail->flags = VRING_AVAIL_F_NO_INTERRUPT;
|
|
vq[VQ_TX].avail->idx = 0;
|
|
|
|
/* Tell HV that setup succeeded */
|
|
virtio_set_status(&virtiodev, VIRTIO_STAT_ACKNOWLEDGE
|
|
|VIRTIO_STAT_DRIVER
|
|
|VIRTIO_STAT_DRIVER_OK);
|
|
|
|
/* Tell HV that RX queues are ready */
|
|
virtio_queue_notify(&virtiodev, VQ_RX);
|
|
|
|
snk_module_interface->running = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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(void)
|
|
{
|
|
dprintk("virtionet_term()\n");
|
|
|
|
if (snk_module_interface->running == 0)
|
|
return 0;
|
|
|
|
/* Quiesce device */
|
|
virtio_set_status(&virtiodev, VIRTIO_STAT_FAILED);
|
|
|
|
/* Reset device */
|
|
virtio_reset_device(&virtiodev);
|
|
|
|
snk_module_interface->running = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Transmit a packet
|
|
*/
|
|
static int virtionet_xmit(char *buf, int len)
|
|
{
|
|
struct vring_desc *desc;
|
|
int id;
|
|
static struct virtio_net_hdr nethdr = {
|
|
0, 0, sizeof(struct virtio_net_hdr),
|
|
0, 0, 0
|
|
};
|
|
|
|
if (len > BUFFER_ENTRY_SIZE) {
|
|
printk("virtionet: Packet too big!\n");
|
|
return 0;
|
|
}
|
|
|
|
dprintk("\nvirtionet_xmit(packet at %p, %d bytes)\n", buf, len);
|
|
|
|
/* Determine descriptor index */
|
|
id = (vq[VQ_TX].avail->idx * 2) % vq[VQ_TX].size;
|
|
|
|
/* Set up virtqueue descriptor for header */
|
|
desc = &vq[VQ_TX].desc[id];
|
|
desc->addr = (uint64_t)&nethdr;
|
|
desc->len = sizeof(struct virtio_net_hdr);
|
|
desc->flags = VRING_DESC_F_NEXT;
|
|
desc->next = id + 1;
|
|
|
|
/* Set up virtqueue descriptor for data */
|
|
desc = &vq[VQ_TX].desc[id+1];
|
|
desc->addr = (uint64_t)buf;
|
|
desc->len = len;
|
|
desc->flags = 0;
|
|
desc->next = 0;
|
|
|
|
vq[VQ_TX].avail->ring[vq[VQ_TX].avail->idx % vq[VQ_TX].size] = id;
|
|
sync();
|
|
vq[VQ_TX].avail->idx += 1;
|
|
|
|
/* Tell HV that TX queue is ready */
|
|
virtio_queue_notify(&virtiodev, VQ_TX);
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
/**
|
|
* Receive a packet
|
|
*/
|
|
static int virtionet_receive(char *buf, int maxlen)
|
|
{
|
|
int len = 0;
|
|
int id;
|
|
|
|
if (last_rx_idx == vq[VQ_RX].used->idx) {
|
|
/* Nothing received yet */
|
|
return 0;
|
|
}
|
|
|
|
id = (vq[VQ_RX].used->ring[last_rx_idx % vq[VQ_RX].size].id + 1)
|
|
% vq[VQ_RX].size;
|
|
len = vq[VQ_RX].used->ring[last_rx_idx % vq[VQ_RX].size].len
|
|
- sizeof(struct virtio_net_hdr);
|
|
|
|
dprintk("virtionet_receive() last_rx_idx=%i, vq[VQ_RX].used->idx=%i,"
|
|
" id=%i len=%i\n", last_rx_idx, vq[VQ_RX].used->idx, id, len);
|
|
|
|
if (len > maxlen) {
|
|
printk("virtio-net: Receive buffer not big enough!\n");
|
|
len = maxlen;
|
|
}
|
|
|
|
#if 0
|
|
/* Dump packet */
|
|
printk("\n");
|
|
int i;
|
|
for (i=0; i<64; i++) {
|
|
printk(" %02x", *(uint8_t*)(vq[VQ_RX].desc[id].addr+i));
|
|
if ((i%16)==15)
|
|
printk("\n");
|
|
}
|
|
printk("\n");
|
|
#endif
|
|
|
|
/* Copy data to destination buffer */
|
|
memcpy(buf, (void*)vq[VQ_RX].desc[id].addr, len);
|
|
|
|
/* Move indices to next entries */
|
|
last_rx_idx = last_rx_idx + 1;
|
|
|
|
vq[VQ_RX].avail->ring[vq[VQ_RX].avail->idx % vq[VQ_RX].size] = id - 1;
|
|
sync();
|
|
vq[VQ_RX].avail->idx += 1;
|
|
|
|
/* Tell HV that RX queue entry is ready */
|
|
virtio_queue_notify(&virtiodev, VQ_RX);
|
|
|
|
return len;
|
|
}
|
|
|
|
|
|
/**
|
|
* Network IO control function - not implemented.
|
|
*/
|
|
static int virtionet_ioctl(int request, void* data)
|
|
{
|
|
dprintk("virtionet_ioctl()\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/**
|
|
* Module definition
|
|
*/
|
|
snk_module_t virtionet_interface = {
|
|
.version = 1,
|
|
.type = MOD_TYPE_NETWORK,
|
|
.running = 0,
|
|
.init = virtionet_init,
|
|
.term = virtionet_term,
|
|
.write = virtionet_xmit,
|
|
.read = virtionet_receive,
|
|
.ioctl = virtionet_ioctl
|
|
};
|