[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:
@@ -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),
|
||||
¬ify_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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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, ¬ify );
|
||||
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, ¬ify );
|
||||
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 );
|
||||
}
|
||||
+505
-615
File diff suppressed because it is too large
Load Diff
+128
-61
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user