462 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			462 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/* virtio net driver for MINIX 3
 | 
						|
 *
 | 
						|
 * Copyright (c) 2013, A. Welzel, <arne.welzel@gmail.com>
 | 
						|
 *
 | 
						|
 * This software is released under the BSD license. See the LICENSE file
 | 
						|
 * included in the main directory of this source distribution for the
 | 
						|
 * license terms and conditions.
 | 
						|
 */
 | 
						|
 | 
						|
#include <assert.h>
 | 
						|
#include <sys/types.h>
 | 
						|
 | 
						|
#include <net/gen/ether.h>
 | 
						|
#include <net/gen/eth_io.h>
 | 
						|
 | 
						|
#include <minix/drivers.h>
 | 
						|
#include <minix/netdriver.h>
 | 
						|
#include <minix/sysutil.h>
 | 
						|
#include <minix/virtio.h>
 | 
						|
 | 
						|
#include <sys/queue.h>
 | 
						|
 | 
						|
#include "virtio_net.h"
 | 
						|
 | 
						|
#define VERBOSE 0
 | 
						|
 | 
						|
#if VERBOSE
 | 
						|
#define dput(s)		do { dprintf(s); printf("\n"); } while (0)
 | 
						|
#define dprintf(s) do {						\
 | 
						|
	printf("%s: ", name);					\
 | 
						|
	printf s;						\
 | 
						|
} while (0)
 | 
						|
#else
 | 
						|
#define dput(s)
 | 
						|
#define dprintf(s)
 | 
						|
#endif
 | 
						|
 | 
						|
static struct virtio_device *net_dev;
 | 
						|
 | 
						|
static const char *const name = "virtio-net";
 | 
						|
 | 
						|
enum queue {RX_Q, TX_Q, CTRL_Q};
 | 
						|
 | 
						|
/* Number of packets to work with */
 | 
						|
/* TODO: This should be an argument to the driver and possibly also
 | 
						|
 *       depend on the queue sizes offered by this device.
 | 
						|
 */
 | 
						|
#define BUF_PACKETS		64
 | 
						|
/* Maximum size of a packet */
 | 
						|
#define MAX_PACK_SIZE		ETH_MAX_PACK_SIZE
 | 
						|
/* Buffer size needed for the payload of BUF_PACKETS */
 | 
						|
#define PACKET_BUF_SZ		(BUF_PACKETS * MAX_PACK_SIZE)
 | 
						|
 | 
						|
struct packet {
 | 
						|
	int idx;
 | 
						|
	struct virtio_net_hdr *vhdr;
 | 
						|
	phys_bytes phdr;
 | 
						|
	char *vdata;
 | 
						|
	phys_bytes pdata;
 | 
						|
	size_t len;
 | 
						|
	STAILQ_ENTRY(packet) next;
 | 
						|
};
 | 
						|
 | 
						|
/* Allocated data chunks */
 | 
						|
static char *data_vir;
 | 
						|
static phys_bytes data_phys;
 | 
						|
static struct virtio_net_hdr *hdrs_vir;
 | 
						|
static phys_bytes hdrs_phys;
 | 
						|
static struct packet *packets;
 | 
						|
static int in_rx;
 | 
						|
 | 
						|
/* Packets on this list can be given to the host */
 | 
						|
static STAILQ_HEAD(free_list, packet) free_list;
 | 
						|
 | 
						|
/* Packets on this list are to be given to inet */
 | 
						|
static STAILQ_HEAD(recv_list, packet) recv_list;
 | 
						|
 | 
						|
/* Various state data */
 | 
						|
static eth_stat_t virtio_net_stats;
 | 
						|
static int spurious_interrupt;
 | 
						|
 | 
						|
/* Prototypes */
 | 
						|
static int virtio_net_probe(unsigned int skip);
 | 
						|
static void virtio_net_config(ether_addr_t *addr);
 | 
						|
static int virtio_net_alloc_bufs(void);
 | 
						|
static void virtio_net_init_queues(void);
 | 
						|
 | 
						|
static void virtio_net_refill_rx_queue(void);
 | 
						|
static void virtio_net_check_queues(void);
 | 
						|
static void virtio_net_check_pending(void);
 | 
						|
 | 
						|
static int virtio_net_init(unsigned int instance, ether_addr_t *addr);
 | 
						|
static void virtio_net_stop(void);
 | 
						|
static int virtio_net_send(struct netdriver_data *data, size_t len);
 | 
						|
static ssize_t virtio_net_recv(struct netdriver_data *data, size_t max);
 | 
						|
static void virtio_net_stat(eth_stat_t *stat);
 | 
						|
static void virtio_net_intr(unsigned int mask);
 | 
						|
 | 
						|
static const struct netdriver virtio_net_table = {
 | 
						|
	.ndr_init	= virtio_net_init,
 | 
						|
	.ndr_stop	= virtio_net_stop,
 | 
						|
	.ndr_recv	= virtio_net_recv,
 | 
						|
	.ndr_send	= virtio_net_send,
 | 
						|
	.ndr_stat	= virtio_net_stat,
 | 
						|
	.ndr_intr	= virtio_net_intr,
 | 
						|
};
 | 
						|
 | 
						|
/* TODO: Features are pretty much ignored */
 | 
						|
static struct virtio_feature netf[] = {
 | 
						|
	{ "partial csum",	VIRTIO_NET_F_CSUM,	0,	0	},
 | 
						|
	{ "given mac",		VIRTIO_NET_F_MAC,	0,	1	},
 | 
						|
	{ "status ",		VIRTIO_NET_F_STATUS,	0,	0	},
 | 
						|
	{ "control channel",	VIRTIO_NET_F_CTRL_VQ,	0,	1	},
 | 
						|
	{ "control channel rx",	VIRTIO_NET_F_CTRL_RX,	0,	0	}
 | 
						|
};
 | 
						|
 | 
						|
static int
 | 
						|
virtio_net_probe(unsigned int skip)
 | 
						|
{
 | 
						|
	/* virtio-net has at least 2 queues */
 | 
						|
	int queues = 2;
 | 
						|
	net_dev= virtio_setup_device(0x00001, name, netf,
 | 
						|
				     sizeof(netf) / sizeof(netf[0]),
 | 
						|
				     1 /* threads */, skip);
 | 
						|
	if (net_dev == NULL)
 | 
						|
		return ENXIO;
 | 
						|
 | 
						|
	/* If the host supports the control queue, allocate it as well */
 | 
						|
	if (virtio_host_supports(net_dev, VIRTIO_NET_F_CTRL_VQ))
 | 
						|
		queues += 1;
 | 
						|
 | 
						|
	if (virtio_alloc_queues(net_dev, queues) != OK) {
 | 
						|
		virtio_free_device(net_dev);
 | 
						|
		return ENOMEM;
 | 
						|
	}
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_config(ether_addr_t * addr)
 | 
						|
{
 | 
						|
	u32_t mac14;
 | 
						|
	u32_t mac56;
 | 
						|
	int i;
 | 
						|
 | 
						|
	if (virtio_host_supports(net_dev, VIRTIO_NET_F_MAC)) {
 | 
						|
		dprintf(("Mac set by host: "));
 | 
						|
		mac14 = virtio_sread32(net_dev, 0);
 | 
						|
		mac56 = virtio_sread32(net_dev, 4);
 | 
						|
		memcpy(&addr->ea_addr[0], &mac14, 4);
 | 
						|
		memcpy(&addr->ea_addr[4], &mac56, 2);
 | 
						|
 | 
						|
		for (i = 0; i < 6; i++)
 | 
						|
			dprintf(("%02x%s", addr->ea_addr[i],
 | 
						|
					 i == 5 ? "\n" : ":"));
 | 
						|
	} else {
 | 
						|
		dput(("No mac"));
 | 
						|
	}
 | 
						|
 | 
						|
	if (virtio_host_supports(net_dev, VIRTIO_NET_F_STATUS)) {
 | 
						|
		dput(("Current Status %x", (u32_t)virtio_sread16(net_dev, 6)));
 | 
						|
	} else {
 | 
						|
		dput(("No status"));
 | 
						|
	}
 | 
						|
 | 
						|
	if (virtio_host_supports(net_dev, VIRTIO_NET_F_CTRL_VQ))
 | 
						|
		dput(("Host supports control channel"));
 | 
						|
 | 
						|
	if (virtio_host_supports(net_dev, VIRTIO_NET_F_CTRL_RX))
 | 
						|
		dput(("Host supports control channel for RX"));
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
virtio_net_alloc_bufs(void)
 | 
						|
{
 | 
						|
	data_vir = alloc_contig(PACKET_BUF_SZ, 0, &data_phys);
 | 
						|
 | 
						|
	if (!data_vir)
 | 
						|
		return ENOMEM;
 | 
						|
 | 
						|
	hdrs_vir = alloc_contig(BUF_PACKETS * sizeof(hdrs_vir[0]),
 | 
						|
				 0, &hdrs_phys);
 | 
						|
 | 
						|
	if (!hdrs_vir) {
 | 
						|
		free_contig(data_vir, PACKET_BUF_SZ);
 | 
						|
		return ENOMEM;
 | 
						|
	}
 | 
						|
 | 
						|
	packets = malloc(BUF_PACKETS * sizeof(packets[0]));
 | 
						|
 | 
						|
	if (!packets) {
 | 
						|
		free_contig(data_vir, PACKET_BUF_SZ);
 | 
						|
		free_contig(hdrs_vir, BUF_PACKETS * sizeof(hdrs_vir[0]));
 | 
						|
		return ENOMEM;
 | 
						|
	}
 | 
						|
 | 
						|
	memset(data_vir, 0, PACKET_BUF_SZ);
 | 
						|
	memset(hdrs_vir, 0, BUF_PACKETS * sizeof(hdrs_vir[0]));
 | 
						|
	memset(packets, 0, BUF_PACKETS * sizeof(packets[0]));
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_init_queues(void)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
	STAILQ_INIT(&free_list);
 | 
						|
	STAILQ_INIT(&recv_list);
 | 
						|
 | 
						|
	for (i = 0; i < BUF_PACKETS; i++) {
 | 
						|
		packets[i].idx = i;
 | 
						|
		packets[i].vhdr = &hdrs_vir[i];
 | 
						|
		packets[i].phdr = hdrs_phys + i * sizeof(hdrs_vir[i]);
 | 
						|
		packets[i].vdata = data_vir + i * MAX_PACK_SIZE;
 | 
						|
		packets[i].pdata = data_phys + i * MAX_PACK_SIZE;
 | 
						|
		STAILQ_INSERT_HEAD(&free_list, &packets[i], next);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_refill_rx_queue(void)
 | 
						|
{
 | 
						|
	struct vumap_phys phys[2];
 | 
						|
	struct packet *p;
 | 
						|
 | 
						|
	while ((in_rx < BUF_PACKETS / 2) && !STAILQ_EMPTY(&free_list)) {
 | 
						|
		/* peek */
 | 
						|
		p = STAILQ_FIRST(&free_list);
 | 
						|
		/* remove */
 | 
						|
		STAILQ_REMOVE_HEAD(&free_list, next);
 | 
						|
 | 
						|
		phys[0].vp_addr = p->phdr;
 | 
						|
		assert(!(phys[0].vp_addr & 1));
 | 
						|
		phys[0].vp_size = sizeof(struct virtio_net_hdr);
 | 
						|
 | 
						|
		phys[1].vp_addr = p->pdata;
 | 
						|
		assert(!(phys[1].vp_addr & 1));
 | 
						|
		phys[1].vp_size = MAX_PACK_SIZE;
 | 
						|
 | 
						|
		/* RX queue needs write */
 | 
						|
		phys[0].vp_addr |= 1;
 | 
						|
		phys[1].vp_addr |= 1;
 | 
						|
 | 
						|
		virtio_to_queue(net_dev, RX_Q, phys, 2, p);
 | 
						|
		in_rx++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (in_rx == 0 && STAILQ_EMPTY(&free_list)) {
 | 
						|
		dput(("warning: rx queue underflow!"));
 | 
						|
		virtio_net_stats.ets_fifoUnder++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_check_queues(void)
 | 
						|
{
 | 
						|
	struct packet *p;
 | 
						|
	size_t len;
 | 
						|
 | 
						|
	/* Put the received packets into the recv list */
 | 
						|
	while (virtio_from_queue(net_dev, RX_Q, (void **)&p, &len) == 0) {
 | 
						|
		p->len = len;
 | 
						|
		STAILQ_INSERT_TAIL(&recv_list, p, next);
 | 
						|
		in_rx--;
 | 
						|
		virtio_net_stats.ets_packetR++;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Packets from the TX queue just indicated they are free to
 | 
						|
	 * be reused now. inet already knows about them as being sent.
 | 
						|
	 */
 | 
						|
	while (virtio_from_queue(net_dev, TX_Q, (void **)&p, NULL) == 0) {
 | 
						|
		memset(p->vhdr, 0, sizeof(*p->vhdr));
 | 
						|
		memset(p->vdata, 0, MAX_PACK_SIZE);
 | 
						|
		STAILQ_INSERT_HEAD(&free_list, p, next);
 | 
						|
		virtio_net_stats.ets_packetT++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_check_pending(void)
 | 
						|
{
 | 
						|
 | 
						|
	/* Pending read and something in recv_list? */
 | 
						|
	if (!STAILQ_EMPTY(&recv_list))
 | 
						|
		netdriver_recv();
 | 
						|
 | 
						|
	if (!STAILQ_EMPTY(&free_list))
 | 
						|
		netdriver_send();
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
virtio_net_intr(unsigned int __unused mask)
 | 
						|
{
 | 
						|
 | 
						|
	/* Check and clear interrupt flag */
 | 
						|
	if (virtio_had_irq(net_dev)) {
 | 
						|
		virtio_net_check_queues();
 | 
						|
	} else {
 | 
						|
		if (!spurious_interrupt)
 | 
						|
			dput(("Spurious interrupt"));
 | 
						|
 | 
						|
		spurious_interrupt = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	virtio_net_check_pending();
 | 
						|
 | 
						|
	virtio_irq_enable(net_dev);
 | 
						|
 | 
						|
	/* Readd packets to the receive queue as necessary. */
 | 
						|
	virtio_net_refill_rx_queue();
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Put user bytes into a free packet buffer, forward this packet to the TX
 | 
						|
 * queue, and return OK.  If there are no free packet buffers, return SUSPEND.
 | 
						|
 */
 | 
						|
static int
 | 
						|
virtio_net_send(struct netdriver_data * data, size_t len)
 | 
						|
{
 | 
						|
	struct vumap_phys phys[2];
 | 
						|
	struct packet *p;
 | 
						|
 | 
						|
	if (STAILQ_EMPTY(&free_list))
 | 
						|
		return SUSPEND;
 | 
						|
 | 
						|
	p = STAILQ_FIRST(&free_list);
 | 
						|
	STAILQ_REMOVE_HEAD(&free_list, next);
 | 
						|
 | 
						|
	if (len > MAX_PACK_SIZE)
 | 
						|
		panic("%s: packet too large to send: %zu", name, len);
 | 
						|
 | 
						|
	netdriver_copyin(data, 0, p->vdata, len);
 | 
						|
 | 
						|
	phys[0].vp_addr = p->phdr;
 | 
						|
	assert(!(phys[0].vp_addr & 1));
 | 
						|
	phys[0].vp_size = sizeof(struct virtio_net_hdr);
 | 
						|
	phys[1].vp_addr = p->pdata;
 | 
						|
	assert(!(phys[1].vp_addr & 1));
 | 
						|
	phys[1].vp_size = len;
 | 
						|
	virtio_to_queue(net_dev, TX_Q, phys, 2, p);
 | 
						|
 | 
						|
	return OK;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Put a packet receive from the RX queue into a user buffer, and return the
 | 
						|
 * packet length.  If there are no received packets, return SUSPEND.
 | 
						|
 */
 | 
						|
static ssize_t
 | 
						|
virtio_net_recv(struct netdriver_data * data, size_t max)
 | 
						|
{
 | 
						|
	struct packet *p;
 | 
						|
	ssize_t len;
 | 
						|
 | 
						|
	/* Get the first received packet, if any. */
 | 
						|
	if (STAILQ_EMPTY(&recv_list))
 | 
						|
		return SUSPEND;
 | 
						|
 | 
						|
	p = STAILQ_FIRST(&recv_list);
 | 
						|
	STAILQ_REMOVE_HEAD(&recv_list, next);
 | 
						|
 | 
						|
	/* Copy out the packet contents. */
 | 
						|
	len = p->len - sizeof(struct virtio_net_hdr);
 | 
						|
	if (len > max)
 | 
						|
		len = max;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * HACK: due to lack of padding, received packets may in fact be
 | 
						|
	 * smaller than the minimum ethernet packet size.  Inet will accept the
 | 
						|
	 * packets just fine if we increase the length to its minimum.  We
 | 
						|
	 * already zeroed out the rest of the packet data, so this is safe.
 | 
						|
	 */
 | 
						|
	if (len < ETH_MIN_PACK_SIZE)
 | 
						|
		len = ETH_MIN_PACK_SIZE;
 | 
						|
 | 
						|
	netdriver_copyout(data, 0, p->vdata, len);
 | 
						|
 | 
						|
	/* Clean the packet. */
 | 
						|
	memset(p->vhdr, 0, sizeof(*p->vhdr));
 | 
						|
	memset(p->vdata, 0, MAX_PACK_SIZE);
 | 
						|
	STAILQ_INSERT_HEAD(&free_list, p, next);
 | 
						|
 | 
						|
	/* Readd packets to the receive queue as necessary. */
 | 
						|
	virtio_net_refill_rx_queue();
 | 
						|
 | 
						|
	return len;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Return statistics.
 | 
						|
 */
 | 
						|
static void
 | 
						|
virtio_net_stat(eth_stat_t *stat)
 | 
						|
{
 | 
						|
 | 
						|
	memcpy(stat, &virtio_net_stats, sizeof(*stat));
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Initialize the driver and the virtual hardware.
 | 
						|
 */
 | 
						|
static int
 | 
						|
virtio_net_init(unsigned int instance, ether_addr_t *addr)
 | 
						|
{
 | 
						|
	int r;
 | 
						|
 | 
						|
	if ((r = virtio_net_probe(instance)) != OK)
 | 
						|
		return r;
 | 
						|
 | 
						|
	virtio_net_config(addr);
 | 
						|
 | 
						|
	if (virtio_net_alloc_bufs() != OK)
 | 
						|
		panic("%s: Buffer allocation failed", name);
 | 
						|
 | 
						|
	virtio_net_init_queues();
 | 
						|
 | 
						|
	/* Add packets to the receive queue. */
 | 
						|
	virtio_net_refill_rx_queue();
 | 
						|
 | 
						|
	virtio_device_ready(net_dev);
 | 
						|
 | 
						|
	virtio_irq_enable(net_dev);
 | 
						|
 | 
						|
	return(OK);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The driver is terminating.  Clean up.
 | 
						|
 */
 | 
						|
static void
 | 
						|
virtio_net_stop(void)
 | 
						|
{
 | 
						|
 | 
						|
	dput(("Terminating"));
 | 
						|
 | 
						|
	free_contig(data_vir, PACKET_BUF_SZ);
 | 
						|
	free_contig(hdrs_vir, BUF_PACKETS * sizeof(hdrs_vir[0]));
 | 
						|
	free(packets);
 | 
						|
 | 
						|
	virtio_reset_device(net_dev);
 | 
						|
	virtio_free_queues(net_dev);
 | 
						|
	virtio_free_device(net_dev);
 | 
						|
	net_dev = NULL;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * The virtio-net device driver.
 | 
						|
 */
 | 
						|
int
 | 
						|
main(int argc, char *argv[])
 | 
						|
{
 | 
						|
 | 
						|
	env_setargs(argc, argv);
 | 
						|
 | 
						|
	netdriver_task(&virtio_net_table);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 |