mirror of
https://github.com/ipxe/ipxe
synced 2026-05-18 10:00:30 +03:00
[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 */
|
||||
|
||||
@@ -212,7 +212,7 @@ FILE_SECBOOT ( PERMITTED );
|
||||
#define ERRFILE_eoib ( ERRFILE_DRIVER | 0x007c0000 )
|
||||
#define ERRFILE_golan ( ERRFILE_DRIVER | 0x007d0000 )
|
||||
#define ERRFILE_flexboot_nodnic ( ERRFILE_DRIVER | 0x007e0000 )
|
||||
#define ERRFILE_virtio_pci ( ERRFILE_DRIVER | 0x007f0000 )
|
||||
#define ERRFILE_virtio ( ERRFILE_DRIVER | 0x007f0000 )
|
||||
#define ERRFILE_pciea ( ERRFILE_DRIVER | 0x00c00000 )
|
||||
#define ERRFILE_axge ( ERRFILE_DRIVER | 0x00c10000 )
|
||||
#define ERRFILE_thunderx ( ERRFILE_DRIVER | 0x00c20000 )
|
||||
|
||||
@@ -102,6 +102,9 @@ FILE_SECBOOT ( PERMITTED );
|
||||
/** Next capability */
|
||||
#define PCI_CAP_NEXT 0x01
|
||||
|
||||
/** Capability length */
|
||||
#define PCI_CAP_LEN 0x02
|
||||
|
||||
/** Power management control and status */
|
||||
#define PCI_PM_CTRL 0x04
|
||||
#define PCI_PM_CTRL_STATE_MASK 0x0003 /**< Current power state */
|
||||
|
||||
@@ -1,314 +0,0 @@
|
||||
#ifndef _VIRTIO_PCI_H_
|
||||
# define _VIRTIO_PCI_H_
|
||||
|
||||
#include <ipxe/dma.h>
|
||||
|
||||
/* A 32-bit r/o bitmask of the features supported by the host */
|
||||
#define VIRTIO_PCI_HOST_FEATURES 0
|
||||
|
||||
/* A 32-bit r/w bitmask of features activated by the guest */
|
||||
#define VIRTIO_PCI_GUEST_FEATURES 4
|
||||
|
||||
/* A 32-bit r/w PFN for the currently selected queue */
|
||||
#define VIRTIO_PCI_QUEUE_PFN 8
|
||||
|
||||
/* A 16-bit r/o queue size for the currently selected queue */
|
||||
#define VIRTIO_PCI_QUEUE_NUM 12
|
||||
|
||||
/* A 16-bit r/w queue selector */
|
||||
#define VIRTIO_PCI_QUEUE_SEL 14
|
||||
|
||||
/* A 16-bit r/w queue notifier */
|
||||
#define VIRTIO_PCI_QUEUE_NOTIFY 16
|
||||
|
||||
/* An 8-bit device status register. */
|
||||
#define VIRTIO_PCI_STATUS 18
|
||||
|
||||
/* An 8-bit r/o interrupt status register. Reading the value will return the
|
||||
* current contents of the ISR and will also clear it. This is effectively
|
||||
* a read-and-acknowledge. */
|
||||
#define VIRTIO_PCI_ISR 19
|
||||
|
||||
/* The bit of the ISR which indicates a device configuration change. */
|
||||
#define VIRTIO_PCI_ISR_CONFIG 0x2
|
||||
|
||||
/* The remaining space is defined by each driver as the per-driver
|
||||
* configuration space */
|
||||
#define VIRTIO_PCI_CONFIG 20
|
||||
|
||||
/* Virtio ABI version, this must match exactly */
|
||||
#define VIRTIO_PCI_ABI_VERSION 0
|
||||
|
||||
/* PCI capability types: */
|
||||
#define VIRTIO_PCI_CAP_COMMON_CFG 1 /* Common configuration */
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_CFG 2 /* Notifications */
|
||||
#define VIRTIO_PCI_CAP_ISR_CFG 3 /* ISR access */
|
||||
#define VIRTIO_PCI_CAP_DEVICE_CFG 4 /* Device specific configuration */
|
||||
#define VIRTIO_PCI_CAP_PCI_CFG 5 /* PCI configuration access */
|
||||
|
||||
#define __u8 uint8_t
|
||||
#define __le16 uint16_t
|
||||
#define __le32 uint32_t
|
||||
#define __le64 uint64_t
|
||||
|
||||
/* This is the PCI capability header: */
|
||||
struct virtio_pci_cap {
|
||||
__u8 cap_vndr; /* Generic PCI field: PCI_CAP_ID_VNDR */
|
||||
__u8 cap_next; /* Generic PCI field: next ptr. */
|
||||
__u8 cap_len; /* Generic PCI field: capability length */
|
||||
__u8 cfg_type; /* Identifies the structure. */
|
||||
__u8 bar; /* Where to find it. */
|
||||
__u8 padding[3]; /* Pad to full dword. */
|
||||
__le32 offset; /* Offset within bar. */
|
||||
__le32 length; /* Length of the structure, in bytes. */
|
||||
};
|
||||
|
||||
struct virtio_pci_notify_cap {
|
||||
struct virtio_pci_cap cap;
|
||||
__le32 notify_off_multiplier; /* Multiplier for queue_notify_off. */
|
||||
};
|
||||
|
||||
struct virtio_pci_cfg_cap {
|
||||
struct virtio_pci_cap cap;
|
||||
__u8 pci_cfg_data[4]; /* Data for BAR access. */
|
||||
};
|
||||
|
||||
/* Fields in VIRTIO_PCI_CAP_COMMON_CFG: */
|
||||
struct virtio_pci_common_cfg {
|
||||
/* About the whole device. */
|
||||
__le32 device_feature_select; /* read-write */
|
||||
__le32 device_feature; /* read-only */
|
||||
__le32 guest_feature_select; /* read-write */
|
||||
__le32 guest_feature; /* read-write */
|
||||
__le16 msix_config; /* read-write */
|
||||
__le16 num_queues; /* read-only */
|
||||
__u8 device_status; /* read-write */
|
||||
__u8 config_generation; /* read-only */
|
||||
|
||||
/* About a specific virtqueue. */
|
||||
__le16 queue_select; /* read-write */
|
||||
__le16 queue_size; /* read-write, power of 2. */
|
||||
__le16 queue_msix_vector; /* read-write */
|
||||
__le16 queue_enable; /* read-write */
|
||||
__le16 queue_notify_off; /* read-only */
|
||||
__le32 queue_desc_lo; /* read-write */
|
||||
__le32 queue_desc_hi; /* read-write */
|
||||
__le32 queue_avail_lo; /* read-write */
|
||||
__le32 queue_avail_hi; /* read-write */
|
||||
__le32 queue_used_lo; /* read-write */
|
||||
__le32 queue_used_hi; /* read-write */
|
||||
};
|
||||
|
||||
/* Virtio 1.0 PCI region descriptor. We support memory mapped I/O, port I/O,
|
||||
* and PCI config space access via the cfg PCI capability as a fallback. */
|
||||
struct virtio_pci_region {
|
||||
void *base;
|
||||
size_t length;
|
||||
u8 bar;
|
||||
|
||||
/* How to interpret the base field */
|
||||
#define VIRTIO_PCI_REGION_TYPE_MASK 0x00000003
|
||||
/* The base field is a memory address */
|
||||
#define VIRTIO_PCI_REGION_MEMORY 0x00000001
|
||||
/* The base field is a port address */
|
||||
#define VIRTIO_PCI_REGION_PORT 0x00000002
|
||||
/* The base field is an offset within the PCI bar */
|
||||
#define VIRTIO_PCI_REGION_PCI_CONFIG 0x00000003
|
||||
unsigned flags;
|
||||
};
|
||||
|
||||
/* Virtio 1.0 device state */
|
||||
struct virtio_pci_modern_device {
|
||||
struct pci_device *pci;
|
||||
|
||||
/* VIRTIO_PCI_CAP_PCI_CFG position */
|
||||
int cfg_cap_pos;
|
||||
|
||||
/* VIRTIO_PCI_CAP_COMMON_CFG data */
|
||||
struct virtio_pci_region common;
|
||||
|
||||
/* VIRTIO_PCI_CAP_DEVICE_CFG data */
|
||||
struct virtio_pci_region device;
|
||||
|
||||
/* VIRTIO_PCI_CAP_ISR_CFG data */
|
||||
struct virtio_pci_region isr;
|
||||
|
||||
/* VIRTIO_PCI_CAP_NOTIFY_CFG data */
|
||||
int notify_cap_pos;
|
||||
};
|
||||
|
||||
static inline u32 vp_get_features(unsigned int ioaddr)
|
||||
{
|
||||
return inl(ioaddr + VIRTIO_PCI_HOST_FEATURES);
|
||||
}
|
||||
|
||||
static inline void vp_set_features(unsigned int ioaddr, u32 features)
|
||||
{
|
||||
outl(features, ioaddr + VIRTIO_PCI_GUEST_FEATURES);
|
||||
}
|
||||
|
||||
static inline void vp_get(unsigned int ioaddr, unsigned offset,
|
||||
void *buf, unsigned len)
|
||||
{
|
||||
u8 *ptr = buf;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
ptr[i] = inb(ioaddr + VIRTIO_PCI_CONFIG + offset + i);
|
||||
}
|
||||
|
||||
static inline u8 vp_get_status(unsigned int ioaddr)
|
||||
{
|
||||
return inb(ioaddr + VIRTIO_PCI_STATUS);
|
||||
}
|
||||
|
||||
static inline void vp_set_status(unsigned int ioaddr, u8 status)
|
||||
{
|
||||
if (status == 0) /* reset */
|
||||
return;
|
||||
outb(status, ioaddr + VIRTIO_PCI_STATUS);
|
||||
}
|
||||
|
||||
static inline u8 vp_get_isr(unsigned int ioaddr)
|
||||
{
|
||||
return inb(ioaddr + VIRTIO_PCI_ISR);
|
||||
}
|
||||
|
||||
static inline void vp_reset(unsigned int ioaddr)
|
||||
{
|
||||
outb(0, ioaddr + VIRTIO_PCI_STATUS);
|
||||
(void)inb(ioaddr + VIRTIO_PCI_ISR);
|
||||
}
|
||||
|
||||
static inline void vp_notify(unsigned int ioaddr, int queue_index)
|
||||
{
|
||||
outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_NOTIFY);
|
||||
}
|
||||
|
||||
static inline void vp_del_vq(unsigned int ioaddr, int queue_index)
|
||||
{
|
||||
/* select the queue */
|
||||
|
||||
outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
|
||||
|
||||
/* deactivate the queue */
|
||||
|
||||
outl(0, ioaddr + VIRTIO_PCI_QUEUE_PFN);
|
||||
}
|
||||
|
||||
struct vring_virtqueue;
|
||||
|
||||
void vp_free_vq(struct vring_virtqueue *vq);
|
||||
int vp_find_vq(unsigned int ioaddr, int queue_index,
|
||||
struct vring_virtqueue *vq, struct dma_device *dma_dev,
|
||||
size_t header_size);
|
||||
|
||||
|
||||
/* Virtio 1.0 I/O routines abstract away the three possible HW access
|
||||
* mechanisms - memory, port I/O, and PCI cfg space access. Also built-in
|
||||
* are endianness conversions - to LE on write and from LE on read. */
|
||||
|
||||
void vpm_iowrite8(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, u8 data, size_t offset);
|
||||
|
||||
void vpm_iowrite16(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, u16 data, size_t offset);
|
||||
|
||||
void vpm_iowrite32(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, u32 data, size_t offset);
|
||||
|
||||
static inline void vpm_iowrite64(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region,
|
||||
u64 data, size_t offset_lo, size_t offset_hi)
|
||||
{
|
||||
vpm_iowrite32(vdev, region, (u32)data, offset_lo);
|
||||
vpm_iowrite32(vdev, region, data >> 32, offset_hi);
|
||||
}
|
||||
|
||||
u8 vpm_ioread8(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, size_t offset);
|
||||
|
||||
u16 vpm_ioread16(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, size_t offset);
|
||||
|
||||
u32 vpm_ioread32(struct virtio_pci_modern_device *vdev,
|
||||
struct virtio_pci_region *region, size_t offset);
|
||||
|
||||
/* Virtio 1.0 device manipulation routines */
|
||||
|
||||
#define COMMON_OFFSET(field) offsetof(struct virtio_pci_common_cfg, field)
|
||||
|
||||
static inline void vpm_reset(struct virtio_pci_modern_device *vdev)
|
||||
{
|
||||
vpm_iowrite8(vdev, &vdev->common, 0, COMMON_OFFSET(device_status));
|
||||
while (vpm_ioread8(vdev, &vdev->common, COMMON_OFFSET(device_status)))
|
||||
mdelay(1);
|
||||
}
|
||||
|
||||
static inline u8 vpm_get_status(struct virtio_pci_modern_device *vdev)
|
||||
{
|
||||
return vpm_ioread8(vdev, &vdev->common, COMMON_OFFSET(device_status));
|
||||
}
|
||||
|
||||
static inline void vpm_add_status(struct virtio_pci_modern_device *vdev,
|
||||
u8 status)
|
||||
{
|
||||
u8 curr_status = vpm_ioread8(vdev, &vdev->common, COMMON_OFFSET(device_status));
|
||||
vpm_iowrite8(vdev, &vdev->common,
|
||||
curr_status | status, COMMON_OFFSET(device_status));
|
||||
}
|
||||
|
||||
static inline u64 vpm_get_features(struct virtio_pci_modern_device *vdev)
|
||||
{
|
||||
u32 features_lo, features_hi;
|
||||
|
||||
vpm_iowrite32(vdev, &vdev->common, 0, COMMON_OFFSET(device_feature_select));
|
||||
features_lo = vpm_ioread32(vdev, &vdev->common, COMMON_OFFSET(device_feature));
|
||||
vpm_iowrite32(vdev, &vdev->common, 1, COMMON_OFFSET(device_feature_select));
|
||||
features_hi = vpm_ioread32(vdev, &vdev->common, COMMON_OFFSET(device_feature));
|
||||
|
||||
return ((u64)features_hi << 32) | features_lo;
|
||||
}
|
||||
|
||||
static inline void vpm_set_features(struct virtio_pci_modern_device *vdev,
|
||||
u64 features)
|
||||
{
|
||||
u32 features_lo = (u32)features;
|
||||
u32 features_hi = features >> 32;
|
||||
|
||||
vpm_iowrite32(vdev, &vdev->common, 0, COMMON_OFFSET(guest_feature_select));
|
||||
vpm_iowrite32(vdev, &vdev->common, features_lo, COMMON_OFFSET(guest_feature));
|
||||
vpm_iowrite32(vdev, &vdev->common, 1, COMMON_OFFSET(guest_feature_select));
|
||||
vpm_iowrite32(vdev, &vdev->common, features_hi, COMMON_OFFSET(guest_feature));
|
||||
}
|
||||
|
||||
static inline void vpm_get(struct virtio_pci_modern_device *vdev,
|
||||
unsigned offset, void *buf, unsigned len)
|
||||
{
|
||||
u8 *ptr = buf;
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
ptr[i] = vpm_ioread8(vdev, &vdev->device, offset + i);
|
||||
}
|
||||
|
||||
static inline u8 vpm_get_isr(struct virtio_pci_modern_device *vdev)
|
||||
{
|
||||
return vpm_ioread8(vdev, &vdev->isr, 0);
|
||||
}
|
||||
|
||||
void vpm_notify(struct virtio_pci_modern_device *vdev,
|
||||
struct vring_virtqueue *vq);
|
||||
|
||||
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);
|
||||
|
||||
int virtio_pci_find_capability(struct pci_device *pci, uint8_t cfg_type);
|
||||
|
||||
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);
|
||||
|
||||
void virtio_pci_unmap_capability(struct virtio_pci_region *region);
|
||||
#endif /* _VIRTIO_PCI_H_ */
|
||||
@@ -1,155 +0,0 @@
|
||||
#ifndef _VIRTIO_RING_H_
|
||||
# define _VIRTIO_RING_H_
|
||||
|
||||
#include <ipxe/virtio-pci.h>
|
||||
#include <ipxe/dma.h>
|
||||
|
||||
/* Status byte for guest to report progress, and synchronize features. */
|
||||
/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */
|
||||
#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
|
||||
/* We have found a driver for the device. */
|
||||
#define VIRTIO_CONFIG_S_DRIVER 2
|
||||
/* Driver has used its parts of the config, and is happy */
|
||||
#define VIRTIO_CONFIG_S_DRIVER_OK 4
|
||||
/* Driver has finished configuring features */
|
||||
#define VIRTIO_CONFIG_S_FEATURES_OK 8
|
||||
/* We've given up on this device. */
|
||||
#define VIRTIO_CONFIG_S_FAILED 0x80
|
||||
|
||||
/* Virtio feature flags used to negotiate device and driver features. */
|
||||
/* Can the device handle any descriptor layout? */
|
||||
#define VIRTIO_F_ANY_LAYOUT 27
|
||||
/* v1.0 compliant. */
|
||||
#define VIRTIO_F_VERSION_1 32
|
||||
#define VIRTIO_F_IOMMU_PLATFORM 33
|
||||
|
||||
#define MAX_QUEUE_NUM (256)
|
||||
|
||||
#define VRING_DESC_F_NEXT 1
|
||||
#define VRING_DESC_F_WRITE 2
|
||||
|
||||
#define VRING_AVAIL_F_NO_INTERRUPT 1
|
||||
|
||||
#define VRING_USED_F_NO_NOTIFY 1
|
||||
|
||||
struct vring_desc
|
||||
{
|
||||
u64 addr;
|
||||
u32 len;
|
||||
u16 flags;
|
||||
u16 next;
|
||||
};
|
||||
|
||||
struct vring_avail
|
||||
{
|
||||
u16 flags;
|
||||
u16 idx;
|
||||
u16 ring[0];
|
||||
};
|
||||
|
||||
struct vring_used_elem
|
||||
{
|
||||
u32 id;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
struct vring_used
|
||||
{
|
||||
u16 flags;
|
||||
u16 idx;
|
||||
struct vring_used_elem ring[];
|
||||
};
|
||||
|
||||
struct vring {
|
||||
unsigned int num;
|
||||
struct vring_desc *desc;
|
||||
struct vring_avail *avail;
|
||||
struct vring_used *used;
|
||||
};
|
||||
|
||||
#define vring_size(num) \
|
||||
(((((sizeof(struct vring_desc) * num) + \
|
||||
(sizeof(struct vring_avail) + sizeof(u16) * num)) \
|
||||
+ PAGE_MASK) & ~PAGE_MASK) + \
|
||||
(sizeof(struct vring_used) + sizeof(struct vring_used_elem) * num))
|
||||
|
||||
struct vring_virtqueue {
|
||||
unsigned char *queue;
|
||||
size_t queue_size;
|
||||
struct dma_mapping map;
|
||||
struct dma_device *dma;
|
||||
struct vring vring;
|
||||
u16 free_head;
|
||||
u16 last_used_idx;
|
||||
void **vdata;
|
||||
struct virtio_net_hdr_modern *empty_header;
|
||||
/* PCI */
|
||||
int queue_index;
|
||||
struct virtio_pci_region notification;
|
||||
};
|
||||
|
||||
struct vring_list {
|
||||
physaddr_t addr;
|
||||
unsigned int length;
|
||||
};
|
||||
|
||||
static inline void vring_init(struct vring *vr,
|
||||
unsigned int num, unsigned char *queue)
|
||||
{
|
||||
unsigned int i;
|
||||
unsigned long pa;
|
||||
|
||||
vr->num = num;
|
||||
|
||||
/* physical address of desc must be page aligned */
|
||||
|
||||
pa = virt_to_phys(queue);
|
||||
pa = (pa + PAGE_MASK) & ~PAGE_MASK;
|
||||
vr->desc = phys_to_virt(pa);
|
||||
|
||||
vr->avail = (struct vring_avail *)&vr->desc[num];
|
||||
|
||||
/* physical address of used must be page aligned */
|
||||
|
||||
pa = virt_to_phys(&vr->avail->ring[num]);
|
||||
pa = (pa + PAGE_MASK) & ~PAGE_MASK;
|
||||
vr->used = phys_to_virt(pa);
|
||||
|
||||
for (i = 0; i < num - 1; i++)
|
||||
vr->desc[i].next = i + 1;
|
||||
vr->desc[i].next = 0;
|
||||
}
|
||||
|
||||
static inline void vring_enable_cb(struct vring_virtqueue *vq)
|
||||
{
|
||||
vq->vring.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
|
||||
}
|
||||
|
||||
static inline void vring_disable_cb(struct vring_virtqueue *vq)
|
||||
{
|
||||
vq->vring.avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* vring_more_used
|
||||
*
|
||||
* is there some used buffers ?
|
||||
*
|
||||
*/
|
||||
|
||||
static inline int vring_more_used(struct vring_virtqueue *vq)
|
||||
{
|
||||
wmb();
|
||||
return vq->last_used_idx != vq->vring.used->idx;
|
||||
}
|
||||
|
||||
void vring_detach(struct vring_virtqueue *vq, unsigned int head);
|
||||
void *vring_get_buf(struct vring_virtqueue *vq, unsigned int *len);
|
||||
void vring_add_buf(struct vring_virtqueue *vq, struct vring_list list[],
|
||||
unsigned int out, unsigned int in,
|
||||
void *index, int num_added);
|
||||
void vring_kick(struct virtio_pci_modern_device *vdev, unsigned int ioaddr,
|
||||
struct vring_virtqueue *vq, int num_added);
|
||||
|
||||
#endif /* _VIRTIO_RING_H_ */
|
||||
@@ -0,0 +1,476 @@
|
||||
#ifndef _IPXE_VIRTIO_H
|
||||
#define _IPXE_VIRTIO_H
|
||||
|
||||
/** @file
|
||||
*
|
||||
* Virtual I/O device
|
||||
*
|
||||
*/
|
||||
|
||||
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
|
||||
FILE_SECBOOT ( PERMITTED );
|
||||
|
||||
#include <stdint.h>
|
||||
#include <byteswap.h>
|
||||
#include <ipxe/dma.h>
|
||||
#include <ipxe/pci.h>
|
||||
|
||||
/** Virtio page alignment */
|
||||
#define VIRTIO_PAGE 4096
|
||||
|
||||
/** Maximum time to wait for reset (in ms) */
|
||||
#define VIRTIO_RESET_MAX_WAIT_MS 100
|
||||
|
||||
/**
|
||||
* @defgroup virtio_legacy Original ("legacy") common device registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Legacy device supported features register */
|
||||
#define VIRTIO_LEG_FEAT 0x00
|
||||
|
||||
/** Legacy negotiated in-use features register */
|
||||
#define VIRTIO_LEG_USED 0x04
|
||||
|
||||
/** Legacy queue base address register */
|
||||
#define VIRTIO_LEG_BASE 0x08
|
||||
|
||||
/** Legacy queue size register */
|
||||
#define VIRTIO_LEG_SIZE 0x0c
|
||||
|
||||
/** Legacy queue select register */
|
||||
#define VIRTIO_LEG_SEL 0x0e
|
||||
|
||||
/** Legacy queue doorbell notification register */
|
||||
#define VIRTIO_LEG_DB 0x10
|
||||
|
||||
/** Legacy driver status register */
|
||||
#define VIRTIO_LEG_STAT 0x12
|
||||
#define VIRTIO_STAT_ACKNOWLEDGE 0x0001 /**< Guest has found device */
|
||||
#define VIRTIO_STAT_DRIVER 0x0002 /**< Guest driver exists */
|
||||
#define VIRTIO_STAT_DRIVER_OK 0x0004 /**< Guest driver is ready */
|
||||
#define VIRTIO_STAT_FEATURES_OK 0x0008 /**< Guest driver has set features */
|
||||
#define VIRTIO_STAT_FAIL 0x0080 /**< Guest driver has failed */
|
||||
|
||||
/** Legacy device-specific registers */
|
||||
#define VIRTIO_LEG_DEV 0x14
|
||||
|
||||
/** Legacy device-specific register (if MSI-X is enabled) */
|
||||
#define VIRTIO_LEG_DEV_MSIX 0x18
|
||||
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @defgroup virtio_pci_cap PCI capability registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** Capability type */
|
||||
#define VIRTIO_PCI_CAP_TYPE 0x03
|
||||
#define VIRTIO_PCI_CAP_TYPE_COMMON 0x01 /**< Common registers */
|
||||
#define VIRTIO_PCI_CAP_TYPE_NOTIFY 0x02 /**< Notification doorbells */
|
||||
#define VIRTIO_PCI_CAP_TYPE_DEVICE 0x04 /**< Device-specific registers */
|
||||
|
||||
/** Capability BAR index */
|
||||
#define VIRTIO_PCI_CAP_BAR 0x04
|
||||
|
||||
/** Capability BAR offset */
|
||||
#define VIRTIO_PCI_CAP_OFFSET 0x08
|
||||
|
||||
/** Capability minimum length */
|
||||
#define VIRTIO_PCI_CAP_END 0x10
|
||||
|
||||
/** Notification doorbell capability multiplier offset */
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_MULT 0x10
|
||||
|
||||
/** Notification doorbell capability minimum length */
|
||||
#define VIRTIO_PCI_CAP_NOTIFY_END 0x14
|
||||
|
||||
/** @} */
|
||||
|
||||
/** A virtio PCI capability */
|
||||
struct virtio_pci_capability {
|
||||
/** Capability type */
|
||||
uint8_t type;
|
||||
/** Capability offset */
|
||||
uint8_t pos;
|
||||
/** Capability length */
|
||||
uint8_t len;
|
||||
/** BAR number */
|
||||
uint8_t bar;
|
||||
/** Offset within BAR */
|
||||
uint32_t offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @defgroup virtio_pci_common PCI common device registers
|
||||
* @{
|
||||
*/
|
||||
|
||||
/** PCI device supported features select register */
|
||||
#define VIRTIO_PCI_FEAT_SEL 0x00
|
||||
|
||||
/** PCI device supported features register */
|
||||
#define VIRTIO_PCI_FEAT 0x04
|
||||
|
||||
/** PCI negotiated in-use features select register */
|
||||
#define VIRTIO_PCI_USED_SEL 0x08
|
||||
|
||||
/** PCI negotiated in-use features register */
|
||||
#define VIRTIO_PCI_USED 0x0c
|
||||
|
||||
/** PCI device status register */
|
||||
#define VIRTIO_PCI_STAT 0x14
|
||||
|
||||
/** PCI configuration generation register */
|
||||
#define VIRTIO_PCI_GEN 0x15
|
||||
|
||||
/** PCI queue select register */
|
||||
#define VIRTIO_PCI_SEL 0x16
|
||||
|
||||
/** PCI queue size register */
|
||||
#define VIRTIO_PCI_SIZE 0x18
|
||||
|
||||
/** PCI queue enable register */
|
||||
#define VIRTIO_PCI_ENABLE 0x1c
|
||||
|
||||
/** PCI queue doorbell notification offset register */
|
||||
#define VIRTIO_PCI_DBOFF 0x1e
|
||||
|
||||
/** PCI queue descriptor array base address register */
|
||||
#define VIRTIO_PCI_DESC 0x20
|
||||
|
||||
/** PCI queue submission queue base address register */
|
||||
#define VIRTIO_PCI_SQ 0x28
|
||||
|
||||
/** PCI queue completion queue base address register */
|
||||
#define VIRTIO_PCI_CQ 0x30
|
||||
|
||||
/** @} */
|
||||
|
||||
/** A virtio buffer descriptor */
|
||||
struct virtio_desc {
|
||||
/** Buffer address */
|
||||
uint64_t addr;
|
||||
/** Buffer length */
|
||||
uint32_t len;
|
||||
/** Flags */
|
||||
uint16_t flags;
|
||||
/** Next descriptor index */
|
||||
uint16_t next;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** Next descriptor index is valid */
|
||||
#define VIRTIO_DESC_FL_NEXT 0x0001
|
||||
|
||||
/** Buffer is write-only */
|
||||
#define VIRTIO_DESC_FL_WRITE 0x0002
|
||||
|
||||
/** A virtio submission queue entry */
|
||||
struct virtio_sqe {
|
||||
/** Starting descriptor index */
|
||||
uint16_t index;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** A virtio submission ("available") queue */
|
||||
struct virtio_sq {
|
||||
/** Flags */
|
||||
uint16_t flags;
|
||||
/** Producer index */
|
||||
uint16_t prod;
|
||||
/** Queue entries */
|
||||
struct virtio_sqe sqe[];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** Do not generate interrupt */
|
||||
#define VIRTIO_SQ_FL_NO_INTERRUPT 0x0001
|
||||
|
||||
/** A virtio completion queue entry */
|
||||
struct virtio_cqe {
|
||||
/** Starting descriptor index */
|
||||
uint32_t index;
|
||||
/** Length written */
|
||||
uint32_t len;
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** A virtio completion ("used") queue */
|
||||
struct virtio_cq {
|
||||
/** Flags */
|
||||
uint16_t flags;
|
||||
/** Producer index */
|
||||
uint16_t prod;
|
||||
/** Queue entries */
|
||||
struct virtio_cqe cqe[];
|
||||
} __attribute__ (( packed ));
|
||||
|
||||
/** A virtio queue */
|
||||
struct virtio_queue {
|
||||
/** Queue index */
|
||||
unsigned int index;
|
||||
/** Queue size (must be a power of two) */
|
||||
unsigned int count;
|
||||
/** Queue mask */
|
||||
unsigned int mask;
|
||||
/** Submission queue producer index */
|
||||
unsigned int prod;
|
||||
/** Completion queue consumer index */
|
||||
unsigned int cons;
|
||||
/** Total length of queue */
|
||||
size_t len;
|
||||
/** DMA mapping */
|
||||
struct dma_mapping map;
|
||||
/** Descriptor array (and start of DMA allocation) */
|
||||
struct virtio_desc *desc;
|
||||
/** Submission queue */
|
||||
struct virtio_sq *sq;
|
||||
/** Completion queue */
|
||||
struct virtio_cq *cq;
|
||||
/** Notification doorbell */
|
||||
void *db;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialise virtio queue
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v index Queue index
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) void
|
||||
virtio_queue_init ( struct virtio_queue *queue, unsigned int index ) {
|
||||
|
||||
queue->index = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate aligned size
|
||||
*
|
||||
* @v size Unaligned size
|
||||
* @ret size Aligned size
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) size_t
|
||||
virtio_align ( size_t size ) {
|
||||
|
||||
return ( ( size + VIRTIO_PAGE - 1 ) & ~( VIRTIO_PAGE - 1 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate (unaligned) descriptor array size
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v count Queue size
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) size_t
|
||||
virtio_desc_size ( unsigned int count ) {
|
||||
struct virtio_desc *desc;
|
||||
|
||||
return ( count * sizeof ( desc[0] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate (unaligned) submission queue size
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v count Queue size
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) size_t
|
||||
virtio_sq_size ( unsigned int count ) {
|
||||
struct virtio_sq *sq;
|
||||
|
||||
return ( sizeof ( *sq ) + ( count * sizeof ( sq->sqe[0] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate (unaligned) completion queue size
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v count Queue size
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) size_t
|
||||
virtio_cq_size ( unsigned int count ) {
|
||||
struct virtio_cq *cq;
|
||||
|
||||
return ( sizeof ( *cq ) + ( count * sizeof ( cq->cqe[0] ) ) );
|
||||
}
|
||||
|
||||
/** Number of 32-bit feature words */
|
||||
#define VIRTIO_FEATURE_WORDS 2
|
||||
|
||||
/** A virtio feature set */
|
||||
struct virtio_features {
|
||||
/** Feature words */
|
||||
uint32_t word[VIRTIO_FEATURE_WORDS];
|
||||
};
|
||||
|
||||
/** Arbitrary descriptor layouts may be used */
|
||||
#define VIRTIO_FEAT0_ANY_LAYOUT 0x08000000
|
||||
|
||||
/** Virtio version 1.0 or above */
|
||||
#define VIRTIO_FEAT1_MODERN 0x00000001
|
||||
|
||||
/** A virtio device */
|
||||
struct virtio_device {
|
||||
/** Device name */
|
||||
const char *name;
|
||||
/** Device operations */
|
||||
struct virtio_operations *op;
|
||||
/** DMA device */
|
||||
struct dma_device *dma;
|
||||
/** Common registers */
|
||||
void *common;
|
||||
/** Doorbell notification registers */
|
||||
void *notify;
|
||||
/** Device-specific registers */
|
||||
void *device;
|
||||
/** Driver status */
|
||||
unsigned int stat;
|
||||
/** Device supported features */
|
||||
struct virtio_features supported;
|
||||
/** Negotiated features */
|
||||
struct virtio_features features;
|
||||
/** Notification doorbell multiplier */
|
||||
unsigned int multiplier;
|
||||
};
|
||||
|
||||
/** Virtio device operations */
|
||||
struct virtio_operations {
|
||||
/**
|
||||
* Reset device
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
* @ret rc Return status code
|
||||
*/
|
||||
int ( * reset ) ( struct virtio_device *virtio );
|
||||
/**
|
||||
* Report driver status
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
* @ret stat Actual device status
|
||||
*/
|
||||
unsigned int ( * status ) ( struct virtio_device *virtio );
|
||||
/**
|
||||
* Get supported features
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
*/
|
||||
void ( * supported ) ( struct virtio_device *virtio );
|
||||
/**
|
||||
* Set negotiated features
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
*/
|
||||
void ( * negotiate ) ( struct virtio_device *virtio );
|
||||
/**
|
||||
* Set queue size
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
* @v queue Virtio queue
|
||||
* @v count Requested size
|
||||
*/
|
||||
void ( * size ) ( struct virtio_device *virtio,
|
||||
struct virtio_queue *queue, unsigned int count );
|
||||
/**
|
||||
* Enable queue
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
* @v queue Virtio queue
|
||||
*/
|
||||
void ( * enable ) ( struct virtio_device *virtio,
|
||||
struct virtio_queue *queue );
|
||||
};
|
||||
|
||||
/**
|
||||
* Submit descriptor(s) to queue
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v index Starting descriptor index
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) void
|
||||
virtio_submit ( struct virtio_queue *queue, unsigned int index ) {
|
||||
struct virtio_sqe *sqe;
|
||||
|
||||
/* Get next submission queue entry */
|
||||
sqe = &queue->sq->sqe[ queue->prod++ & queue->mask ];
|
||||
|
||||
/* Populate submission queue entry */
|
||||
sqe->index = cpu_to_le16 ( index );
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify queue
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) void
|
||||
virtio_notify ( struct virtio_queue *queue ) {
|
||||
|
||||
/* Write producer index */
|
||||
wmb();
|
||||
queue->sq->prod = cpu_to_le16 ( queue->prod );
|
||||
wmb();
|
||||
|
||||
/* Ring doorbell */
|
||||
iowrite16 ( queue->index, queue->db );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for completed descriptors
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v completions Number of pending completions
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) unsigned int
|
||||
virtio_completions ( struct virtio_queue *queue ) {
|
||||
uint16_t completions;
|
||||
|
||||
/* Get completion count */
|
||||
completions = ( le16_to_cpu ( queue->cq->prod ) - queue->cons );
|
||||
return completions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete descriptor(s)
|
||||
*
|
||||
* @v queue Virtio queue
|
||||
* @v len Length to fill in, or NULL
|
||||
* @ret index Starting descriptor index
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) unsigned int
|
||||
virtio_complete ( struct virtio_queue *queue, size_t *len ) {
|
||||
struct virtio_cqe *cqe;
|
||||
|
||||
/* Get next completion queue entry */
|
||||
cqe = &queue->cq->cqe[ queue->cons++ & queue->mask ];
|
||||
|
||||
/* Parse completion queue entry */
|
||||
if ( len )
|
||||
*len = le32_to_cpu ( cqe->len );
|
||||
return le32_to_cpu ( cqe->index );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if device is using the legacy interface
|
||||
*
|
||||
* @v virtio Virtio device
|
||||
* @ret is_legacy Device is using the legacy interface
|
||||
*/
|
||||
static inline __attribute__ (( always_inline )) int
|
||||
virtio_is_legacy ( struct virtio_device *virtio ) {
|
||||
|
||||
/* Check negotiation of version 1.0 or above */
|
||||
return ( ! ( virtio->features.word[1] & VIRTIO_FEAT1_MODERN ) );
|
||||
}
|
||||
|
||||
extern int virtio_pci_map ( struct virtio_device *virtio,
|
||||
struct pci_device *pci );
|
||||
extern int virtio_reset ( struct virtio_device *virtio );
|
||||
extern unsigned int virtio_status ( struct virtio_device *virtio,
|
||||
unsigned int stat );
|
||||
extern int virtio_init ( struct virtio_device *virtio,
|
||||
const struct virtio_features *driver );
|
||||
extern int virtio_enable ( struct virtio_device *virtio,
|
||||
struct virtio_queue *queue, unsigned int count );
|
||||
extern void virtio_free ( struct virtio_device *virtio,
|
||||
struct virtio_queue *queue );
|
||||
extern void virtio_unmap ( struct virtio_device *virtio );
|
||||
|
||||
#endif /* _IPXE_VIRTIO_H */
|
||||
Reference in New Issue
Block a user