[virtio] Replace the virtio core and network device driver

The existing virtio network driver has been somewhat hacked together
over the past two decades by multiple contributors, and includes a
substantial amount of logic that is almost but not quite duplicated
between the "legacy" and "modern" code paths.

Rip out the existing driver and replace with a completely new driver
written based on the Virtual I/O Device specification document, not
derived from the Linux kernel driver.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-05-13 15:32:17 +01:00
parent 2b4a3efcc6
commit ca85200809
10 changed files with 1896 additions and 1742 deletions
-453
View File
@@ -1,453 +0,0 @@
/* virtio-pci.c - pci interface for virtio 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>
*
*/
#include "errno.h"
#include "byteswap.h"
#include "etherboot.h"
#include "ipxe/io.h"
#include "ipxe/iomap.h"
#include "ipxe/pci.h"
#include "ipxe/dma.h"
#include "ipxe/reboot.h"
#include "ipxe/virtio-pci.h"
#include "ipxe/virtio-ring.h"
static int vp_alloc_vq(struct vring_virtqueue *vq, u16 num, size_t header_size)
{
size_t ring_size = PAGE_MASK + vring_size(num);
size_t vdata_size = num * sizeof(void *);
size_t queue_size = ring_size + vdata_size + header_size;
vq->queue = dma_alloc(vq->dma, &vq->map, queue_size, queue_size);
if (!vq->queue) {
return -ENOMEM;
}
memset ( vq->queue, 0, queue_size );
vq->queue_size = queue_size;
/* vdata immediately follows the ring */
vq->vdata = (void **)(vq->queue + ring_size);
/* empty header immediately follows vdata */
vq->empty_header = (struct virtio_net_hdr_modern *)(vq->queue + ring_size + vdata_size);
return 0;
}
void vp_free_vq(struct vring_virtqueue *vq)
{
if (vq->queue && vq->queue_size) {
dma_free(&vq->map, vq->queue, vq->queue_size);
vq->queue = NULL;
vq->vdata = NULL;
vq->queue_size = 0;
}
}
int vp_find_vq(unsigned int ioaddr, int queue_index,
struct vring_virtqueue *vq, struct dma_device *dma_dev,
size_t header_size)
{
struct vring * vr = &vq->vring;
u16 num;
int rc;
/* select the queue */
outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
/* check if the queue is available */
num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM);
if (!num) {
DBG("VIRTIO-PCI ERROR: queue size is 0\n");
return -1;
}
/* check if the queue is already active */
if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
DBG("VIRTIO-PCI ERROR: queue already active\n");
return -1;
}
vq->queue_index = queue_index;
vq->dma = dma_dev;
/* initialize the queue */
rc = vp_alloc_vq(vq, num, header_size);
if (rc) {
DBG("VIRTIO-PCI ERROR: failed to allocate queue memory\n");
return rc;
}
vring_init(vr, num, vq->queue);
/* activate the queue
*
* NOTE: vr->desc is initialized by vring_init()
*/
outl(dma(&vq->map, vr->desc) >> PAGE_SHIFT, ioaddr + VIRTIO_PCI_QUEUE_PFN);
return num;
}
#define CFG_POS(vdev, field) \
(vdev->cfg_cap_pos + offsetof(struct virtio_pci_cfg_cap, field))
static void prep_pci_cfg_cap(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region,
size_t offset, u32 length)
{
pci_write_config_byte(vdev->pci, CFG_POS(vdev, cap.bar), region->bar);
pci_write_config_dword(vdev->pci, CFG_POS(vdev, cap.length), length);
pci_write_config_dword(vdev->pci, CFG_POS(vdev, cap.offset),
(intptr_t)(region->base + offset));
}
void vpm_iowrite8(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, u8 data, size_t offset)
{
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
writeb(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
outb(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 1);
pci_write_config_byte(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
break;
default:
assert(0);
break;
}
}
void vpm_iowrite16(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, u16 data, size_t offset)
{
data = cpu_to_le16(data);
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
writew(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
outw(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 2);
pci_write_config_word(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
break;
default:
assert(0);
break;
}
}
void vpm_iowrite32(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, u32 data, size_t offset)
{
data = cpu_to_le32(data);
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
writel(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
outl(data, region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 4);
pci_write_config_dword(vdev->pci, CFG_POS(vdev, pci_cfg_data), data);
break;
default:
assert(0);
break;
}
}
u8 vpm_ioread8(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, size_t offset)
{
uint8_t data;
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
data = readb(region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
data = inb(region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 1);
pci_read_config_byte(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
break;
default:
assert(0);
data = 0;
break;
}
return data;
}
u16 vpm_ioread16(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, size_t offset)
{
uint16_t data;
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
data = readw(region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
data = inw(region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 2);
pci_read_config_word(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
break;
default:
assert(0);
data = 0;
break;
}
return le16_to_cpu(data);
}
u32 vpm_ioread32(struct virtio_pci_modern_device *vdev,
struct virtio_pci_region *region, size_t offset)
{
uint32_t data;
switch (region->flags & VIRTIO_PCI_REGION_TYPE_MASK) {
case VIRTIO_PCI_REGION_MEMORY:
data = readl(region->base + offset);
break;
case VIRTIO_PCI_REGION_PORT:
data = inl(region->base + offset);
break;
case VIRTIO_PCI_REGION_PCI_CONFIG:
prep_pci_cfg_cap(vdev, region, offset, 4);
pci_read_config_dword(vdev->pci, CFG_POS(vdev, pci_cfg_data), &data);
break;
default:
assert(0);
data = 0;
break;
}
return le32_to_cpu(data);
}
int virtio_pci_find_capability(struct pci_device *pci, uint8_t cfg_type)
{
int pos;
uint8_t type, bar;
for (pos = pci_find_capability(pci, PCI_CAP_ID_VNDR);
pos > 0;
pos = pci_find_next_capability(pci, pos, PCI_CAP_ID_VNDR)) {
pci_read_config_byte(pci, pos + offsetof(struct virtio_pci_cap,
cfg_type), &type);
pci_read_config_byte(pci, pos + offsetof(struct virtio_pci_cap,
bar), &bar);
/* Ignore structures with reserved BAR values */
if (bar > 0x5) {
continue;
}
if (type == cfg_type) {
return pos;
}
}
return 0;
}
int virtio_pci_map_capability(struct pci_device *pci, int cap, size_t minlen,
u32 align, u32 start, u32 size,
struct virtio_pci_region *region)
{
u8 bar;
u32 offset, length, base_raw;
unsigned long base;
pci_read_config_byte(pci, cap + offsetof(struct virtio_pci_cap, bar), &bar);
pci_read_config_dword(pci, cap + offsetof(struct virtio_pci_cap, offset),
&offset);
pci_read_config_dword(pci, cap + offsetof(struct virtio_pci_cap, length),
&length);
if (length <= start) {
DBG("VIRTIO-PCI bad capability len %d (>%d expected)\n", length, start);
return -EINVAL;
}
if (length - start < minlen) {
DBG("VIRTIO-PCI bad capability len %d (>=%zd expected)\n", length, minlen);
return -EINVAL;
}
length -= start;
if (start + offset < offset) {
DBG("VIRTIO-PCI map wrap-around %d+%d\n", start, offset);
return -EINVAL;
}
offset += start;
if (offset & (align - 1)) {
DBG("VIRTIO-PCI offset %d not aligned to %d\n", offset, align);
return -EINVAL;
}
if (length > size) {
length = size;
}
if (minlen + offset < minlen ||
minlen + offset > pci_bar_size(pci, PCI_BASE_ADDRESS(bar))) {
DBG("VIRTIO-PCI map virtio %zd@%d out of range on bar %i length %ld\n",
minlen, offset,
bar, pci_bar_size(pci, PCI_BASE_ADDRESS(bar)));
return -EINVAL;
}
region->base = NULL;
region->length = length;
region->bar = bar;
base = pci_bar_start(pci, PCI_BASE_ADDRESS(bar));
if (base) {
pci_read_config_dword(pci, PCI_BASE_ADDRESS(bar), &base_raw);
if (base_raw & PCI_BASE_ADDRESS_SPACE_IO) {
/* Region accessed using port I/O */
region->base = (void *)(base + offset);
region->flags = VIRTIO_PCI_REGION_PORT;
} else {
/* Region mapped into memory space */
region->base = pci_ioremap(pci, base + offset, length);
region->flags = VIRTIO_PCI_REGION_MEMORY;
}
}
if (!region->base) {
/* Region accessed via PCI config space window */
region->base = (void *)(intptr_t)offset;
region->flags = VIRTIO_PCI_REGION_PCI_CONFIG;
}
return 0;
}
void virtio_pci_unmap_capability(struct virtio_pci_region *region)
{
unsigned region_type = region->flags & VIRTIO_PCI_REGION_TYPE_MASK;
if (region_type == VIRTIO_PCI_REGION_MEMORY) {
iounmap(region->base);
}
}
void vpm_notify(struct virtio_pci_modern_device *vdev,
struct vring_virtqueue *vq)
{
vpm_iowrite16(vdev, &vq->notification, (u16)vq->queue_index, 0);
}
int vpm_find_vqs(struct virtio_pci_modern_device *vdev,
unsigned nvqs, struct vring_virtqueue *vqs,
struct dma_device *dma_dev, size_t header_size)
{
unsigned i;
struct vring_virtqueue *vq;
u16 size, off;
u32 notify_offset_multiplier;
int err;
if (nvqs > vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(num_queues))) {
return -ENOENT;
}
/* Read notify_off_multiplier from config space. */
pci_read_config_dword(vdev->pci,
vdev->notify_cap_pos + offsetof(struct virtio_pci_notify_cap,
notify_off_multiplier),
&notify_offset_multiplier);
for (i = 0; i < nvqs; i++) {
/* Select the queue we're interested in */
vpm_iowrite16(vdev, &vdev->common, (u16)i, COMMON_OFFSET(queue_select));
/* Check if queue is either not available or already active. */
size = vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(queue_size));
/* QEMU has a bug where queues don't revert to inactive on device
* reset. Skip checking the queue_enable field until it is fixed.
*/
if (!size /*|| vpm_ioread16(vdev, &vdev->common.queue_enable)*/)
return -ENOENT;
if (size & (size - 1)) {
DBG("VIRTIO-PCI %p: bad queue size %d\n", vdev, size);
return -EINVAL;
}
if (size > MAX_QUEUE_NUM) {
/* iPXE networking tends to be not perf critical so there's no
* need to accept large queue sizes.
*/
size = MAX_QUEUE_NUM;
}
vq = &vqs[i];
vq->queue_index = i;
vq->dma = dma_dev;
/* get offset of notification word for this vq */
off = vpm_ioread16(vdev, &vdev->common, COMMON_OFFSET(queue_notify_off));
err = vp_alloc_vq(vq, size, header_size);
if (err) {
DBG("VIRTIO-PCI %p: failed to allocate queue memory\n", vdev);
return err;
}
vring_init(&vq->vring, size, vq->queue);
/* activate the queue */
vpm_iowrite16(vdev, &vdev->common, size, COMMON_OFFSET(queue_size));
vpm_iowrite64(vdev, &vdev->common,
dma(&vq->map, vq->vring.desc),
COMMON_OFFSET(queue_desc_lo),
COMMON_OFFSET(queue_desc_hi));
vpm_iowrite64(vdev, &vdev->common,
dma(&vq->map, vq->vring.avail),
COMMON_OFFSET(queue_avail_lo),
COMMON_OFFSET(queue_avail_hi));
vpm_iowrite64(vdev, &vdev->common,
dma(&vq->map, vq->vring.used),
COMMON_OFFSET(queue_used_lo),
COMMON_OFFSET(queue_used_hi));
err = virtio_pci_map_capability(vdev->pci,
vdev->notify_cap_pos, 2, 2,
off * notify_offset_multiplier, 2,
&vq->notification);
if (err) {
return err;
}
}
/* Select and activate all queues. Has to be done last: once we do
* this, there's no way to go back except reset.
*/
for (i = 0; i < nvqs; i++) {
vq = &vqs[i];
vpm_iowrite16(vdev, &vdev->common, (u16)vq->queue_index,
COMMON_OFFSET(queue_select));
vpm_iowrite16(vdev, &vdev->common, 1, COMMON_OFFSET(queue_enable));
}
return 0;
}
-143
View File
@@ -1,143 +0,0 @@
/* virtio-pci.c - virtio ring management
*
* (c) Copyright 2008 Bull S.A.S.
*
* Author: Laurent Vivier <Laurent.Vivier@bull.net>
*
* 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.
*
*
*/
FILE_LICENCE ( GPL2_OR_LATER );
#include "etherboot.h"
#include "ipxe/io.h"
#include "ipxe/virtio-pci.h"
#include "ipxe/virtio-ring.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)
/*
* vring_free
*
* put at the begin of the free list the current desc[head]
*/
void vring_detach(struct vring_virtqueue *vq, unsigned int head)
{
struct vring *vr = &vq->vring;
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 = vq->free_head;
wmb();
vq->free_head = head;
}
/*
* vring_get_buf
*
* get a buffer from the used list
*
*/
void *vring_get_buf(struct vring_virtqueue *vq, unsigned int *len)
{
struct vring *vr = &vq->vring;
struct vring_used_elem *elem;
u32 id;
void *opaque;
BUG_ON(!vring_more_used(vq));
elem = &vr->used->ring[vq->last_used_idx % vr->num];
wmb();
id = elem->id;
if (len != NULL)
*len = elem->len;
opaque = vq->vdata[id];
vring_detach(vq, id);
vq->last_used_idx++;
return opaque;
}
void vring_add_buf(struct vring_virtqueue *vq,
struct vring_list list[],
unsigned int out, unsigned int in,
void *opaque, int num_added)
{
struct vring *vr = &vq->vring;
int i, avail, head, prev;
BUG_ON(out + in == 0);
prev = 0;
head = vq->free_head;
for (i = head; out; i = vr->desc[i].next, out--) {
vr->desc[i].flags = VRING_DESC_F_NEXT;
vr->desc[i].addr = 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 = list->addr;
vr->desc[i].len = list->length;
prev = i;
list++;
}
vr->desc[prev].flags &= ~VRING_DESC_F_NEXT;
vq->free_head = i;
vq->vdata[head] = opaque;
avail = (vr->avail->idx + num_added) % vr->num;
vr->avail->ring[avail] = head;
wmb();
}
void vring_kick(struct virtio_pci_modern_device *vdev, unsigned int ioaddr,
struct vring_virtqueue *vq, int num_added)
{
struct vring *vr = &vq->vring;
wmb();
vr->avail->idx += num_added;
mb();
if (!(vr->used->flags & VRING_USED_F_NO_NOTIFY)) {
if (vdev) {
/* virtio 1.0 */
vpm_notify(vdev, vq);
} else {
/* legacy virtio */
vp_notify(ioaddr, vq->queue_index);
}
}
}
+783
View File
@@ -0,0 +1,783 @@
/*
* Copyright (C) 2026 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
/** @file
*
* Virtual I/O device
*
*/
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include <ipxe/pci.h>
#include <ipxe/virtio.h>
/******************************************************************************
*
* Original ("legacy") device operations
*
******************************************************************************
*/
/**
* Reset device
*
* @v virtio Virtio device
* @ret rc Return status code
*/
static int virtio_legacy_reset ( struct virtio_device *virtio ) {
uint8_t stat;
unsigned int i;
/* Reset device */
iowrite8 ( 0, virtio->common + VIRTIO_LEG_STAT );
/* Wait for reset to complete */
for ( i = 0 ; i < VIRTIO_RESET_MAX_WAIT_MS ; i++ ) {
stat = ioread8 ( virtio->common + VIRTIO_LEG_STAT );
if ( ! stat )
return 0;
mdelay ( 1 );
}
DBGC ( virtio, "VIRTIO %s could not reset device\n", virtio->name );
return -ETIMEDOUT;
}
/**
* Report driver status
*
* @v virtio Virtio device
* @ret stat Actual device status
*/
static unsigned int virtio_legacy_status ( struct virtio_device *virtio ) {
/* Report device status */
iowrite8 ( virtio->stat, virtio->common + VIRTIO_LEG_STAT );
/* Read back device status */
return ioread8 ( virtio->common + VIRTIO_LEG_STAT );
}
/**
* Get supported features
*
* @v virtio Virtio device
*/
static void virtio_legacy_supported ( struct virtio_device *virtio ) {
struct virtio_features *supported = &virtio->supported;
unsigned int i;
/* Get device supported features */
supported->word[0] = ioread32 ( virtio->common + VIRTIO_LEG_FEAT );
/* Legacy devices have only a single 32-bit feature register */
for ( i = 1 ; i < VIRTIO_FEATURE_WORDS ; i++ )
supported->word[i] = 0;
}
/**
* Negotiate device features
*
* @v virtio Virtio device
*/
static void virtio_legacy_negotiate ( struct virtio_device *virtio ) {
struct virtio_features *features = &virtio->features;
unsigned int i;
/* Set in-use features */
iowrite32 ( features->word[0], virtio->common + VIRTIO_LEG_USED );
/* Legacy devices have only a single 32-bit feature register */
for ( i = 1 ; i < VIRTIO_FEATURE_WORDS ; i++ )
assert ( features->word[i] == 0 );
}
/**
* Set queue size
*
* @v virtio Virtio device
* @v queue Virtio queue
* @v count Requested size
*/
static void virtio_legacy_size ( struct virtio_device *virtio,
struct virtio_queue *queue,
unsigned int count ) {
size_t len;
/* Select queue */
iowrite16 ( queue->index, virtio->common + VIRTIO_LEG_SEL );
/* Get (fixed) queue size */
count = ioread16 ( virtio->common + VIRTIO_LEG_SIZE );
/* Calculate queue length */
len = virtio_desc_size ( count );
len = virtio_align ( len + virtio_sq_size ( count ) );
len = virtio_align ( len + virtio_cq_size ( count ) );
/* Record queue size */
queue->count = count;
queue->len = len;
}
/**
* Enable queue
*
* @v virtio Virtio device
* @v queue Virtio queue
*/
static void virtio_legacy_enable ( struct virtio_device *virtio,
struct virtio_queue *queue ) {
unsigned int count = queue->count;
void *base = queue->desc;
size_t len;
/* Select queue */
iowrite16 ( queue->index, virtio->common + VIRTIO_LEG_SEL );
/* Lay out queue regions */
len = virtio_desc_size ( count );
queue->sq = ( base + len );
len = virtio_align ( len + virtio_sq_size ( count ) );
queue->cq = ( base + len );
len = virtio_align ( len + virtio_cq_size ( count ) );
assert ( len == queue->len );
/* Program queue base page address */
iowrite32 ( ( dma ( &queue->map, queue->desc ) / VIRTIO_PAGE ),
virtio->common + VIRTIO_LEG_BASE );
}
/** Original ("legacy") device operations */
static struct virtio_operations virtio_legacy_operations = {
.reset = virtio_legacy_reset,
.status = virtio_legacy_status,
.supported = virtio_legacy_supported,
.negotiate = virtio_legacy_negotiate,
.size = virtio_legacy_size,
.enable = virtio_legacy_enable,
};
/******************************************************************************
*
* PCI ("modern") device operations
*
******************************************************************************
*/
/**
* Reset device
*
* @v virtio Virtio device
* @ret rc Return status code
*/
static int virtio_pci_reset ( struct virtio_device *virtio ) {
uint8_t stat;
unsigned int i;
/* Reset device */
iowrite8 ( 0, virtio->common + VIRTIO_PCI_STAT );
/* Wait for reset to complete */
for ( i = 0 ; i < VIRTIO_RESET_MAX_WAIT_MS ; i++ ) {
stat = ioread8 ( virtio->common + VIRTIO_PCI_STAT );
if ( ! stat )
return 0;
mdelay ( 1 );
}
DBGC ( virtio, "VIRTIO %s could not reset device\n", virtio->name );
return -ETIMEDOUT;
}
/**
* Report driver status
*
* @v virtio Virtio device
* @ret stat Actual device status
*/
static unsigned int virtio_pci_status ( struct virtio_device *virtio ) {
/* Report device status */
iowrite8 ( virtio->stat, virtio->common + VIRTIO_PCI_STAT );
/* Read back device status */
return ioread8 ( virtio->common + VIRTIO_PCI_STAT );
}
/**
* Get supported features
*
* @v virtio Virtio device
*/
static void virtio_pci_supported ( struct virtio_device *virtio ) {
struct virtio_features *supported = &virtio->supported;
unsigned int i;
/* Get device supported features */
for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) {
iowrite32 ( i, virtio->common + VIRTIO_PCI_FEAT_SEL );
supported->word[i] =
ioread32 ( virtio->common + VIRTIO_PCI_FEAT );
}
}
/**
* Negotiate device features
*
* @v virtio Virtio device
*/
static void virtio_pci_negotiate ( struct virtio_device *virtio ) {
struct virtio_features *features = &virtio->features;
unsigned int i;
/* Set in-use features */
for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ ) {
iowrite32 ( i, virtio->common + VIRTIO_PCI_USED_SEL );
iowrite32 ( features->word[i],
virtio->common + VIRTIO_PCI_USED );
}
}
/**
* Set queue size
*
* @v virtio Virtio device
* @v queue Virtio queue
* @v count Requested size
*/
static void virtio_pci_size ( struct virtio_device *virtio,
struct virtio_queue *queue,
unsigned int count ) {
unsigned int max;
size_t len;
/* Select queue */
iowrite16 ( queue->index, virtio->common + VIRTIO_PCI_SEL );
/* Set queue size */
max = ioread16 ( virtio->common + VIRTIO_PCI_SIZE );
if ( count > max )
count = max;
iowrite16 ( count, virtio->common + VIRTIO_PCI_SIZE );
/* Calculate queue length */
len = virtio_align ( virtio_desc_size ( count ) );
len = virtio_align ( len + virtio_sq_size ( count ) );
len = virtio_align ( len + virtio_cq_size ( count ) );
/* Record queue size */
queue->count = count;
queue->len = len;
}
/**
* Program queue address
*
* @v virtio Virtio device
* @v queue Virtio queue
* @v addr Address
* @v offset Register offset
*/
static void virtio_pci_address ( struct virtio_device *virtio,
struct virtio_queue *queue,
void *addr, unsigned int offset ) {
physaddr_t phys;
/* Program address */
phys = dma ( &queue->map, addr );
iowrite32 ( ( phys & 0xffffffffUL ), ( virtio->common + offset + 0 ) );
if ( sizeof ( physaddr_t ) > sizeof ( uint32_t ) ) {
iowrite32 ( ( ( ( uint64_t ) phys ) >> 32 ),
( virtio->common + offset + 4 ) );
} else {
iowrite32 ( 0, ( virtio->common + offset + 4 ) );
}
}
/**
* Enable queue
*
* @v virtio Virtio device
* @v queue Virtio queue
*/
static void virtio_pci_enable ( struct virtio_device *virtio,
struct virtio_queue *queue ) {
unsigned int count = queue->count;
void *base = queue->desc;
size_t len;
/* Select queue */
iowrite16 ( queue->index, virtio->common + VIRTIO_PCI_SEL );
/* Lay out queue regions */
len = virtio_align ( virtio_desc_size ( count ) );
queue->sq = ( base + len );
len = virtio_align ( len + virtio_sq_size ( count ) );
queue->cq = ( base + len );
len = virtio_align ( len + virtio_cq_size ( count ) );
assert ( len == queue->len );
/* Program queue addresses */
virtio_pci_address ( virtio, queue, queue->desc, VIRTIO_PCI_DESC );
virtio_pci_address ( virtio, queue, queue->sq, VIRTIO_PCI_SQ );
virtio_pci_address ( virtio, queue, queue->cq, VIRTIO_PCI_CQ );
/* Enable queue */
iowrite16 ( 1, virtio->common + VIRTIO_PCI_ENABLE );
}
/** PCI ("modern") device operations */
static struct virtio_operations virtio_pci_operations = {
.reset = virtio_pci_reset,
.status = virtio_pci_status,
.supported = virtio_pci_supported,
.negotiate = virtio_pci_negotiate,
.size = virtio_pci_size,
.enable = virtio_pci_enable,
};
/**
* Find PCI capability
*
* @v virtio Virtio device
* @v pci PCI device
* @v type Capability type
* @v cap Virtio PCI capability to fill in
* @ret rc Return status code
*/
static int virtio_pci_cap ( struct virtio_device *virtio,
struct pci_device *pci, unsigned int type,
struct virtio_pci_capability *cap ) {
unsigned int reg;
int pos;
/* Scan through vendor capabilities */
for ( pos = pci_find_capability ( pci, PCI_CAP_ID_VNDR ) ; pos > 0 ;
pos = pci_find_next_capability ( pci, pos, PCI_CAP_ID_VNDR ) ) {
/* Check length */
pci_read_config_byte ( pci, ( pos + PCI_CAP_LEN ), &cap->len );
if ( cap->len < VIRTIO_PCI_CAP_END ) {
DBGC ( virtio, "VIRTIO %s capability +%#02x too short "
"(%d bytes)\n", virtio->name, pos, cap->len );
continue;
}
/* Read values */
pci_read_config_byte ( pci, ( pos + VIRTIO_PCI_CAP_TYPE ),
&cap->type );
pci_read_config_byte ( pci, ( pos + VIRTIO_PCI_CAP_BAR ),
&cap->bar );
pci_read_config_dword ( pci, ( pos + VIRTIO_PCI_CAP_OFFSET ),
&cap->offset );
/* Check type */
if ( cap->type != type )
continue;
DBGC2 ( virtio, "VIRTIO %s capability type %d BAR%d+%#04x\n",
virtio->name, type, cap->bar, cap->offset );
/* Check BAR */
reg = PCI_BASE_ADDRESS ( cap->bar );
if ( reg > PCI_BASE_ADDRESS_5 )
continue;
/* Success */
cap->pos = pos;
return 0;
}
DBGC ( virtio, "VIRTIO %s has no usable capability type %d\n",
virtio->name, type );
cap->pos = 0;
return -ENOENT;
}
/**
* Map PCI capability
*
* @v virtio Virtio device
* @v pci PCI device
* @v cap Virtio PCI capability
* @ret io_addr I/O address, or NULL on error
*/
static void * virtio_pci_map_cap ( struct virtio_device *virtio,
struct pci_device *pci,
struct virtio_pci_capability *cap ) {
unsigned long addr;
unsigned int reg;
int is_io_bar;
void *io_addr;
/* Get BAR start address and type */
reg = PCI_BASE_ADDRESS ( cap->bar );
addr = pci_bar_start ( pci, reg );
if ( ! addr ) {
DBGC ( virtio, "VIRTIO %s BAR%d is not usable\n",
virtio->name, cap->bar );
return NULL;
}
/* Map memory or I/O BAR */
addr += cap->offset;
is_io_bar = pci_bar_is_io ( pci, reg );
io_addr = ( is_io_bar ? ( ( void * ) addr ) :
pci_ioremap ( pci, addr, VIRTIO_PAGE ) );
if ( ! io_addr ) {
DBGC ( virtio, "VIRTIO %s could not map BAR%d+%#04x\n",
virtio->name, cap->bar, cap->offset );
return NULL;
}
DBGC2 ( virtio, "VIRTIO %s mapped BAR%d+%#04x (%s %#08lx)\n",
virtio->name, cap->bar, cap->offset,
( is_io_bar ? "IO" : "MEM" ), addr );
return io_addr;
}
/**
* Map PCI device
*
* @v virtio Virtio device
* @v pci PCI device
* @ret rc Return status code
*/
int virtio_pci_map ( struct virtio_device *virtio, struct pci_device *pci ) {
struct virtio_pci_capability common;
struct virtio_pci_capability notify;
struct virtio_pci_capability device;
unsigned int msix;
uint32_t mult;
uint16_t ctrl;
int rc;
/* Initialise device */
virtio->name = pci->dev.name;
virtio->dma = &pci->dma;
/* Fix up PCI device */
adjust_pci_device ( pci );
/* Check if MSI-X is enabled */
msix = pci_find_capability ( pci, PCI_CAP_ID_MSIX );
if ( msix ) {
pci_read_config_word ( pci, msix, &ctrl );
if ( ! ( ctrl & PCI_MSIX_CTRL_ENABLE ) )
msix = 0;
}
/* Locate virtio capabilities */
virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_COMMON, &common );
virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_NOTIFY, &notify );
virtio_pci_cap ( virtio, pci, VIRTIO_PCI_CAP_TYPE_DEVICE, &device );
/* Use modern interface if available */
if ( common.pos && notify.pos && device.pos &&
( notify.len >= VIRTIO_PCI_CAP_NOTIFY_END ) ) {
/* Use modern interface */
virtio->op = &virtio_pci_operations;
dma_set_mask_64bit ( virtio->dma );
/* Read notification doorbell multiplier */
pci_read_config_dword ( pci, ( notify.pos +
VIRTIO_PCI_CAP_NOTIFY_MULT ),
&mult );
virtio->multiplier = mult;
DBGC ( virtio, "VIRTIO %s using modern interface (mult x%d)\n",
virtio->name, virtio->multiplier );
} else {
/* Use legacy interface */
virtio->op = &virtio_legacy_operations;
common.bar = 0;
common.offset = 0;
notify.bar = 0;
notify.offset = VIRTIO_LEG_DB;
device.bar = 0;
device.offset = ( msix ? VIRTIO_LEG_DEV_MSIX :
VIRTIO_LEG_DEV );
DBGC ( virtio, "VIRTIO %s using legacy interface (MSI-X "
"%sabled)\n", virtio->name, ( msix ? "en" : "dis" ) );
}
/* Map registers */
virtio->common = virtio_pci_map_cap ( virtio, pci, &common );
if ( ! virtio->common ) {
rc = -ENODEV;
goto err_common;
}
virtio->notify = virtio_pci_map_cap ( virtio, pci, &notify );
if ( ! virtio->notify ) {
rc = -ENODEV;
goto err_notify;
}
virtio->device = virtio_pci_map_cap ( virtio, pci, &device );
if ( ! virtio->device ) {
rc = -ENODEV;
goto err_device;
}
return 0;
iounmap ( virtio->device );
err_device:
iounmap ( virtio->notify );
err_notify:
iounmap ( virtio->common );
err_common:
return rc;
}
/******************************************************************************
*
* Transport-independent operations
*
******************************************************************************
*/
/**
* Reset device
*
* @v virtio Virtio device
* @ret rc Return status code
*/
int virtio_reset ( struct virtio_device *virtio ) {
int rc;
/* Clear driver status */
virtio->stat = 0;
/* Reset device */
if ( ( rc = virtio->op->reset ( virtio ) ) != 0 ) {
DBGC ( virtio, "VIRTIO %s could not reset: %s\n",
virtio->name, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Report driver status
*
* @v virtio Virtio device
* @v stat Additional driver status bits
* @ret stat Actual device status
*/
unsigned int virtio_status ( struct virtio_device *virtio,
unsigned int stat ) {
/* Set new driver status bits */
virtio->stat |= stat;
/* Report driver status */
return virtio->op->status ( virtio );
}
/**
* Negotiate features
*
* @v virtio Virtio device
* @v driver Driver supported features
*/
static void virtio_negotiate ( struct virtio_device *virtio,
const struct virtio_features *driver ) {
struct virtio_features *device = &virtio->supported;
struct virtio_features *features = &virtio->features;
unsigned int i;
/* Get device supported features */
virtio->op->supported ( virtio );
/* Negotiate mutually supported features */
for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ )
features->word[i] = ( device->word[i] & driver->word[i] );
virtio->op->negotiate ( virtio );
/* Show features */
DBGC ( virtio, "VIRTIO %s features", virtio->name );
for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ )
DBGC ( virtio, "%s%08x", ( i ? ":" : " " ), device->word[i] );
DBGC ( virtio, " /" );
for ( i = 0 ; i < VIRTIO_FEATURE_WORDS ; i++ )
DBGC ( virtio, "%s%08x", ( i ? ":" : " " ), features->word[i] );
DBGC ( virtio, "\n" );
}
/**
* Initialise device
*
* @v virtio Virtio device
* @v driver Driver supported features
* @ret rc Return status code
*/
int virtio_init ( struct virtio_device *virtio,
const struct virtio_features *driver ) {
unsigned int stat;
int rc;
/* Reset device */
if ( ( rc = virtio_reset ( virtio ) ) != 0 )
goto err_reset;
/* Acknowledge device existence */
virtio_status ( virtio, VIRTIO_STAT_ACKNOWLEDGE );
/* Report driver existence */
virtio_status ( virtio, VIRTIO_STAT_DRIVER );
/* Negotiate features */
virtio_negotiate ( virtio, driver );
/* Report feature negotiation completion, if applicable */
if ( virtio->features.word[1] & VIRTIO_FEAT1_MODERN ) {
stat = virtio_status ( virtio, VIRTIO_STAT_FEATURES_OK );
if ( ! ( stat & VIRTIO_STAT_FEATURES_OK ) ) {
DBGC ( virtio, "VIRTIO %s did not accept features\n",
virtio->name );
rc = -ENOTSUP;
goto err_features;
}
}
return 0;
err_features:
virtio_reset ( virtio );
err_reset:
virtio_status ( virtio, VIRTIO_STAT_FAIL );
return rc;
}
/**
* Enable queue
*
* @v virtio Virtio device
* @v queue Virtio queue
* @v count Requested queue size
* @ret rc Return status code
*/
int virtio_enable ( struct virtio_device *virtio, struct virtio_queue *queue,
unsigned int count ) {
unsigned int offset;
int rc;
/* Reset counters */
queue->prod = 0;
queue->cons = 0;
/* Determine queue size */
virtio->op->size ( virtio, queue, count );
if ( ( queue->count == 0 ) ||
( queue->count & ( queue->count - 1 ) ) ) {
DBGC ( virtio, "VIRTIO %s Q%d invalid size %d\n",
virtio->name, queue->index, queue->count );
rc = -ENODEV;
goto err_count;
}
queue->mask = ( queue->count - 1 );
/* Allocate and initialise queue */
queue->desc = dma_alloc ( virtio->dma, &queue->map, queue->len,
VIRTIO_PAGE );
if ( ! queue->desc ) {
rc = -ENOMEM;
goto err_alloc;
}
memset ( queue->desc, 0, queue->len );
/* Enable queue */
virtio->op->enable ( virtio, queue );
DBGC ( virtio, "VIRTIO %s Q%d %dx descriptors at [%#08lx,%#08lx)\n",
virtio->name, queue->index, queue->count,
virt_to_phys ( queue->desc ),
( virt_to_phys ( queue->desc ) +
virtio_desc_size ( queue->count ) ) );
DBGC ( virtio, "VIRTIO %s Q%d %dx submissions at [%#08lx,%#08lx)\n",
virtio->name, queue->index, queue->count,
virt_to_phys ( queue->sq ),
( virt_to_phys ( queue->sq ) +
virtio_sq_size ( queue->count ) ) );
DBGC ( virtio, "VIRTIO %s Q%d %dx completions at [%#08lx,%#08lx)\n",
virtio->name, queue->index, queue->count,
virt_to_phys ( queue->cq ),
( virt_to_phys ( queue->cq ) +
virtio_cq_size ( queue->count ) ) );
/* Calculate doorbell register address */
offset = ( queue->index * virtio->multiplier );
queue->db = ( virtio->notify + offset );
DBGC ( virtio, "VIRTIO %s Q%d doorbell at +%#04x\n",
virtio->name, queue->index, offset );
return 0;
dma_free ( &queue->map, queue->desc, queue->len );
queue->desc = NULL;
err_alloc:
err_count:
return rc;
}
/**
* Free queue
*
* @v virtio Virtio device
* @v queue Virtio queue
*/
void virtio_free ( struct virtio_device *virtio, struct virtio_queue *queue ) {
/* Free queue */
if ( queue->desc ) {
dma_free ( &queue->map, queue->desc, queue->len );
queue->desc = NULL;
DBGC ( virtio, "VIRTIO %s Q%d freed\n",
virtio->name, queue->index );
}
}
/**
* Unmap device
*
* @v virtio Virtio device
*/
void virtio_unmap ( struct virtio_device *virtio ) {
/* Unmap device-specific registers */
iounmap ( virtio->device );
/* Unmap notification doorbells */
iounmap ( virtio->notify );
/* Unmap common registers */
iounmap ( virtio->common );
}
File diff suppressed because it is too large Load Diff
+128 -61
View File
@@ -1,70 +1,137 @@
#ifndef _VIRTIO_NET_H_
# define _VIRTIO_NET_H_
#ifndef _VIRTIO_NET_H
#define _VIRTIO_NET_H
/* The feature bitmap for virtio net */
#define VIRTIO_NET_F_CSUM 0 /* Host handles pkts w/ partial csum */
#define VIRTIO_NET_F_GUEST_CSUM 1 /* Guest handles pkts w/ partial csum */
#define VIRTIO_NET_F_MTU 3 /* Initial MTU advice */
#define VIRTIO_NET_F_MAC 5 /* Host has given MAC address. */
#define VIRTIO_NET_F_GSO 6 /* Host handles pkts w/ any GSO type */
#define VIRTIO_NET_F_GUEST_TSO4 7 /* Guest can handle TSOv4 in. */
#define VIRTIO_NET_F_GUEST_TSO6 8 /* Guest can handle TSOv6 in. */
#define VIRTIO_NET_F_GUEST_ECN 9 /* Guest can handle TSO[6] w/ ECN in. */
#define VIRTIO_NET_F_GUEST_UFO 10 /* Guest can handle UFO in. */
#define VIRTIO_NET_F_HOST_TSO4 11 /* Host can handle TSOv4 in. */
#define VIRTIO_NET_F_HOST_TSO6 12 /* Host can handle TSOv6 in. */
#define VIRTIO_NET_F_HOST_ECN 13 /* Host can handle TSO[6] w/ ECN in. */
#define VIRTIO_NET_F_HOST_UFO 14 /* Host can handle UFO in. */
#define VIRTIO_NET_F_MRG_RXBUF 15 /* Driver can merge receive buffers. */
#define VIRTIO_NET_F_STATUS 16 /* Configuration status field is available. */
#define VIRTIO_NET_F_CTRL_VQ 17 /* Control channel is available. */
#define VIRTIO_NET_F_CTRL_RX 18 /* Control channel RX mode support. */
#define VIRTIO_NET_F_CTRL_VLAN 19 /* Control channel VLAN filtering. */
#define VIRTIO_NET_F_GUEST_ANNOUNCE 21 /* Driver can send gratuitous packets. */
/** @file
*
* Virtual I/O network device
*
*/
struct virtio_net_config
{
/* The config defining mac address (if VIRTIO_NET_F_MAC) */
u8 mac[6];
/* See VIRTIO_NET_F_STATUS and VIRTIO_NET_S_* above */
u16 status;
/* Maximum number of each of transmit and receive queues;
* see VIRTIO_NET_F_MQ and VIRTIO_NET_CTRL_MQ.
* Legal values are between 1 and 0x8000
*/
u16 max_virtqueue_pairs;
/* Default maximum transmit unit advice */
u16 mtu;
} __attribute__((packed));
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
FILE_SECBOOT ( PERMITTED );
/* This is the first element of the scatter-gather list. If you don't
* specify GSO or CSUM features, you can simply ignore the header. */
#include <ipxe/virtio.h>
struct virtio_net_hdr
{
#define VIRTIO_NET_HDR_F_NEEDS_CSUM 1 // Use csum_start, csum_offset
uint8_t flags;
#define VIRTIO_NET_HDR_GSO_NONE 0 // Not a GSO frame
#define VIRTIO_NET_HDR_GSO_TCPV4 1 // GSO frame, IPv4 TCP (TSO)
/* FIXME: Do we need this? If they said they can handle ECN, do they care? */
#define VIRTIO_NET_HDR_GSO_TCPV4_ECN 2 // GSO frame, IPv4 TCP w/ ECN
#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
uint8_t gso_type;
uint16_t hdr_len;
uint16_t gso_size;
uint16_t csum_start;
uint16_t csum_offset;
/** Device has a reported MTU */
#define VIRTIO_FEAT0_NET_MTU 0x00000008
/** Device has a MAC address */
#define VIRTIO_FEAT0_NET_MAC 0x00000020
/** MAC address register offset */
#define VIRTIO_NET_MAC 0x00
/** MTU register offset */
#define VIRTIO_NET_MTU 0x0a
/** A virtio network packet header */
union virtio_net_header {
/** Legacy interface */
uint8_t legacy[10];
/** Modern (version 1.0) interface */
uint8_t modern[12];
} __attribute__ (( packed ));
/** Receive queue index */
#define VIRTIO_NET_RX_INDEX 0
/** Receive queue requested queue size */
#define VIRTIO_NET_RX_COUNT 128
/** Receive queue maximum fill level */
#define VIRTIO_NET_RX_MAX 16
/** Transmit queue index */
#define VIRTIO_NET_TX_INDEX 1
/** Transmit queue requested queue size */
#define VIRTIO_NET_TX_COUNT 128
/** Transmit queue maximum fill level */
#define VIRTIO_NET_TX_MAX 32
/** Number of descriptors per packet */
#define VIRTIO_NET_DESCS 2
/** A virtio network queue */
struct virtio_net_queue {
/** Underlying virtio queue */
struct virtio_queue queue;
/** I/O buffer list */
struct io_buffer **iobufs;
/** Descriptor slot ring */
uint8_t *slots;
/** Effective fill level */
unsigned int fill;
/** Descriptor index ring mask */
unsigned int mask;
/** Shared packet header */
union virtio_net_header hdr;
/** DMA mapping for packet header */
struct dma_mapping map;
/** DMA direction for packet header */
uint8_t dma;
/** Buffer writability flag for packet header */
uint8_t write;
/** Requested queue size */
uint8_t count;
/** Maximum fill level */
uint8_t max;
};
/* Virtio 1.0 version of the first element of the scatter-gather list. */
struct virtio_net_hdr_modern
{
struct virtio_net_hdr legacy;
/**
* Initialise virtio network queue
*
* @v queue Virtio network queue
* @v index Queue index
* @v iobufs I/O buffer list
* @v slots Descriptor slot ring
* @v dma DMA direction for packet header
* @v write Writability flag for packet header
* @v count Requested queue size
* @v max Maximum fill level
*/
static inline __attribute__ (( always_inline )) void
virtio_net_queue_init ( struct virtio_net_queue *queue,
struct io_buffer **iobufs, uint8_t *slots,
unsigned int index, unsigned int count,
unsigned int max, unsigned int dma,
unsigned int write ) {
/* Used only if VIRTIO_NET_F_MRG_RXBUF: */
uint16_t num_buffers;
queue->queue.index = index;
queue->iobufs = iobufs;
queue->slots = slots;
queue->dma = dma;
queue->write = write;
queue->count = count;
queue->max = max;
}
/** A virtio network device */
struct virtio_net {
/** Underlying virtio device */
struct virtio_device virtio;
/** Receive queue */
struct virtio_net_queue rx;
/** Transmit queue */
struct virtio_net_queue tx;
/** Virtio network header length */
size_t hlen;
/** Maximum frame size */
size_t mfs;
/** Receive descriptor slot ring */
uint8_t rx_slots[VIRTIO_NET_RX_MAX];
/** Receive I/O buffers */
struct io_buffer *rx_iobufs[VIRTIO_NET_RX_MAX];
/** Transmit descriptor slot ring */
uint8_t tx_slots[VIRTIO_NET_TX_MAX];
/** Transmit I/O buffers */
struct io_buffer *tx_iobufs[VIRTIO_NET_TX_MAX];
};
#endif /* _VIRTIO_NET_H_ */
#endif /* _VIRTIO_NET_H */