[virtio] Replace the virtio core and network device driver

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

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

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2026-05-13 15:32:17 +01:00
parent 2b4a3efcc6
commit ca85200809
10 changed files with 1896 additions and 1742 deletions
+1 -1
View File
@@ -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 )
+3
View File
@@ -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 */
-314
View File
@@ -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_ */
-155
View File
@@ -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_ */
+476
View File
@@ -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 */