Goto sanos source index
//
// virtionet.c
//
// Network device driver for virtio
//
// Copyright (C) 2011 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// 3. Neither the name of the project nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
#include <os/krnl.h>
#define MTUSIZE 1514
#define MAXSEGS 4
//
// Feature bits
//
#define VIRTIO_NET_F_CSUM (1 << 0) // Host handles pkts w/ partial checksum
#define VIRTIO_NET_F_GUEST_CSUM (1 << 1) // Guest handles pkts w/ partial checksum
#define VIRTIO_NET_F_MAC (1 << 5) // Host has MAC address
#define VIRTIO_NET_F_GSO (1 << 6) // Host handles pkts with any GSO type
#define VIRTIO_NET_F_GUEST_TSO4 (1 << 7) // Guest can handle TSOv4 in
#define VIRTIO_NET_F_GUEST_TSO6 (1 << 8) // Guest can handle TSOv6 in
#define VIRTIO_NET_F_GUEST_ECN (1 << 9) // Guest can handle TSO[6] with ECN in
#define VIRTIO_NET_F_GUEST_UFO (1 << 10) // Guest can handle UFO in
#define VIRTIO_NET_F_HOST_TSO4 (1 << 11) // Host can handle TSOv4 in
#define VIRTIO_NET_F_HOST_TSO6 (1 << 12) // Host can handle TSOv6 in
#define VIRTIO_NET_F_HOST_ECN (1 << 13) // Host can handle TSO[6] w/ ECN in
#define VIRTIO_NET_F_HOST_UFO (1 << 14) // Host can handle UFO in
#define VIRTIO_NET_F_MRG_RXBUF (1 << 15) // Host can merge receive buffers
#define VIRTIO_NET_F_STATUS (1 << 16) // virtio_net_config status available
#define VIRTIO_NET_F_CTRL_VQ (1 << 17) // Control channel available
#define VIRTIO_NET_F_CTRL_RX (1 << 18) // Control channel RX mode support
#define VIRTIO_NET_F_CTRL_VLAN (1 << 19) // Control channel VLAN filtering
//
// Virtual NIC configuration block
//
#define VIRTIO_NET_S_LINK_UP 1 // Link is up
struct virtio_net_config {
struct eth_addr mac;
unsigned short status;
};
//
// Virtual NIC device packet header
//
#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset
#define VIRTIO_NET_HDR_F_DATA_VALID 2 // Csum is valid
#define VIRTIO_NET_HDR_GSO_NONE 0 // Not a GSO frame
#define VIRTIO_NET_HDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO)
#define VIRTIO_NET_HDR_GSO_UDP 3 // GSO frame, IPv4 UDP (UFO)
#define VIRTIO_NET_HDR_GSO_TCPV6 4 // GSO frame, IPv6 TCP
#define VIRTIO_NET_HDR_GSO_ECN 0x80 // TCP has ECN set
struct virtio_net_hdr {
unsigned char flags;
unsigned char gso_type;
unsigned short hdr_len; // Ethernet + IP + TCP/UDP headers
unsigned short gso_size; // Bytes to append to hdr_len per frame
unsigned short csum_start; // Position to start checksumming from
unsigned short csum_offset; // Offset after that to place checksum
};
//
// Virtual network device data
//
struct virtionet {
struct virtio_device vd;
struct virtio_net_config config;
struct virtio_queue rxqueue;
struct virtio_queue txqueue;
dev_t devno;
};
static int add_receive_buffer(struct virtionet *vnet) {
struct virtio_net_hdr *hdr;
struct scatterlist sg[2];
struct pbuf *p = pbuf_alloc(PBUF_RAW, MTUSIZE + sizeof(struct virtio_net_hdr), PBUF_RW);
if (!p) return -ENOMEM;
hdr = p->payload;
pbuf_header(p, -(int) sizeof(struct virtio_net_hdr));
sg[0].data = hdr;
sg[0].size = sizeof(struct virtio_net_hdr);
sg[1].data = p->payload;
sg[1].size = p->len;
virtio_enqueue(&vnet->rxqueue, sg, 0, 2, p);
return 0;
}
static int virtionet_ioctl(struct dev *dev, int cmd, void *args, size_t size) {
return -ENOSYS;
}
static int virtionet_rx_callback(struct virtio_queue *vq) {
struct virtionet *vnet = (struct virtionet *) vq->vd;
struct pbuf *p;
unsigned int len;
int rc, received;
// Drain receive queue
received = 0;
while ((p = virtio_dequeue(vq, &len)) != NULL) {
pbuf_realloc(p, len);
rc = dev_receive(vnet->devno, p);
if (rc < 0) pbuf_free(p);
received++;
}
// Fill up receive queue with new empty buffers
if (received > 0) {
while (received > 0) {
add_receive_buffer(vnet);
received--;
}
virtio_kick(&vnet->rxqueue);
}
return 0;
}
static int virtionet_tx_callback(struct virtio_queue *vq) {
struct pbuf *hdr;
struct pbuf *data;
unsigned int len;
// Deallocate packets buffers after they have been transmitted.
while ((hdr = virtio_dequeue(vq, &len)) != NULL) {
data = pbuf_dechain(hdr);
pbuf_free(hdr);
pbuf_free(data);
}
return 0;
}
int virtionet_attach(struct dev *dev, struct eth_addr *hwaddr) {
struct virtionet *vnet = dev->privdata;
*hwaddr = vnet->config.mac;
return 0;
}
int virtionet_detach(struct dev *dev) {
return 0;
}
int virtionet_transmit(struct dev *dev, struct pbuf *p) {
struct virtionet *vnet = dev->privdata;
struct pbuf *hdr;
struct pbuf *q;
int i;
struct scatterlist sg[MAXSEGS];
// Allocate packet header
hdr = pbuf_alloc(PBUF_RAW, sizeof(struct virtio_net_hdr), PBUF_RW);
if (hdr == NULL) return -ENOMEM;
memset(hdr->payload, 0, sizeof(struct virtio_net_hdr));
pbuf_chain(hdr, p);
// Add packet to transmit queue
for (i = 0, q = hdr; q; q = q->next, i++) {
if (i == MAXSEGS) return -ERANGE;
sg[i].data = q->payload;
sg[i].size = q->len;
}
virtio_enqueue(&vnet->txqueue, sg, 2, 0, p);
virtio_kick(&vnet->txqueue);
return 0;
}
struct driver virtionet_driver = {
"virtionet",
DEV_TYPE_PACKET,
virtionet_ioctl,
NULL,
NULL,
virtionet_attach,
virtionet_detach,
virtionet_transmit
};
int __declspec(dllexport) install(struct unit *unit, char *opts) {
struct virtionet *vnet;
int rc, size, i;
// Setup unit information
if (!unit) return -ENOSYS;
unit->vendorname = "VIRTIO";
unit->productname = "VIRTIO Virtual Network Device";
// Allocate memory for device
vnet = kmalloc(sizeof(struct virtionet));
if (vnet == NULL) return -ENOMEM;
memset(vnet, 0, sizeof(struct virtionet));
// Initialize virtual device
rc = virtio_device_init(&vnet->vd, unit, 0);
if (rc < 0) return rc;
// Get block device configuration
virtio_get_config(&vnet->vd, &vnet->config, sizeof(vnet->config));
// Initialize transmit and receive queues
rc = virtio_queue_init(&vnet->rxqueue, &vnet->vd, 0, virtionet_rx_callback);
if (rc < 0) return rc;
rc = virtio_queue_init(&vnet->txqueue, &vnet->vd, 1, virtionet_tx_callback);
if (rc < 0) return rc;
// Fill receive queue
size = virtio_queue_size(&vnet->rxqueue) / 2;
for (i = 0; i < size; ++i) add_receive_buffer(vnet);
virtio_kick(&vnet->rxqueue);
// Create device
vnet->devno = dev_make("eth#", &virtionet_driver, unit, vnet);
virtio_setup_complete(&vnet->vd, 1);
kprintf(KERN_INFO "%s: virtio net, mac %la\n", device(vnet->devno)->name, &vnet->config.mac);
return 0;
}
int __stdcall start(hmodule_t hmod, int reason, void *reserved2) {
return 1;
}