virtio: Implement a proper sandbox emulator
The existing sandbox implementation of virtio only tests the basic API.
It is not able to provide a block device, for example.
Add a new implementation which operations at a higher level. It makes
use of the existing MMIO driver to perform virtio operations.
This emulator-device should be the parent of a function-specific
emulator. That emulator uses this MMIO transport to communicate with the
controller:
virtio-blk {
compatible = "sandbox,virtio-blk-emul";
mmio {
compatible = "sandbox,virtio-emul";
};
};
A new UCLASS_VIRTIO_EMUL uclass is created for the child devices, which
implement the actual function (block device, random-number generator,
etc.)
Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
@@ -220,6 +220,7 @@ config SANDBOX
|
||||
imply VIRTIO_MMIO
|
||||
imply VIRTIO_PCI
|
||||
imply VIRTIO_SANDBOX
|
||||
imply VIRTIO_SANDBOX_EMUL
|
||||
# Re-enable this when fully implemented
|
||||
# imply VIRTIO_BLK
|
||||
imply VIRTIO_NET
|
||||
|
||||
@@ -9,7 +9,6 @@ CONFIG_PCI=y
|
||||
CONFIG_ANDROID_BOOT_IMAGE=y
|
||||
CONFIG_TIMESTAMP=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_SIGNATURE=y
|
||||
# CONFIG_BOOTSTD_FULL is not set
|
||||
# CONFIG_BOOTMETH_CROS is not set
|
||||
# CONFIG_BOOTMETH_VBE is not set
|
||||
@@ -38,5 +37,6 @@ CONFIG_TIMER=y
|
||||
# CONFIG_VIRTIO_MMIO is not set
|
||||
# CONFIG_VIRTIO_PCI is not set
|
||||
# CONFIG_VIRTIO_SANDBOX is not set
|
||||
# CONFIG_VIRTIO_SANDBOX_EMUL is not set
|
||||
# CONFIG_GENERATE_ACPI_TABLE is not set
|
||||
CONFIG_TOOLS_MKEFICAPSULE=y
|
||||
|
||||
@@ -54,6 +54,14 @@ config VIRTIO_SANDBOX
|
||||
This driver provides support for Sandbox implementation of virtio
|
||||
transport driver which is used for testing purpose only.
|
||||
|
||||
config VIRTIO_SANDBOX_EMUL
|
||||
bool "Sandbox MMIO emulator for virtio devices"
|
||||
depends on SANDBOX
|
||||
select VIRTIO
|
||||
help
|
||||
This driver provides an MMIO interface to an emulation of a block
|
||||
device. It is used for testing purpose only.
|
||||
|
||||
config VIRTIO_NET
|
||||
bool "virtio net driver"
|
||||
depends on VIRTIO && NETDEVICES
|
||||
|
||||
@@ -8,6 +8,7 @@ obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o
|
||||
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci_modern.o
|
||||
obj-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o
|
||||
obj-$(CONFIG_VIRTIO_SANDBOX) += virtio_sandbox.o
|
||||
obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o
|
||||
obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
|
||||
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
|
||||
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
|
||||
|
||||
313
drivers/virtio/sandbox_emul.c
Normal file
313
drivers/virtio/sandbox_emul.c
Normal file
@@ -0,0 +1,313 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU
|
||||
* side of virtio, using the MMIO driver and handling any accesses
|
||||
*
|
||||
* This handles traffic from the virtio_ring
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_VIRTIO
|
||||
|
||||
#include <dm.h>
|
||||
#include <malloc.h>
|
||||
#include <virtio.h>
|
||||
#include <asm/io.h>
|
||||
#include <dt-bindings/virtio.h>
|
||||
#include <asm/state.h>
|
||||
#include <linux/sizes.h>
|
||||
#include "sandbox_emul.h"
|
||||
#include "virtio_types.h"
|
||||
#include "virtio_blk.h"
|
||||
#include "virtio_internal.h"
|
||||
#include "virtio_mmio.h"
|
||||
#include "virtio_ring.h"
|
||||
|
||||
enum {
|
||||
MMIO_SIZE = 0x200,
|
||||
VENDOR_ID = 0xf003,
|
||||
DEVICE_ID = VIRTIO_ID_BLOCK,
|
||||
DISK_SIZE_MB = 16,
|
||||
};
|
||||
|
||||
void process_queue(struct udevice *emul_dev, struct sandbox_emul_priv *priv,
|
||||
uint32_t queue_idx)
|
||||
{
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
bool processed_something = false;
|
||||
struct virtio_emul_queue *q;
|
||||
struct vring_avail *avail;
|
||||
struct vring_desc *desc;
|
||||
struct vring_used *used;
|
||||
uint old_used_idx;
|
||||
|
||||
if (queue_idx >= priv->num_queues)
|
||||
return;
|
||||
log_debug("Notified on queue %u\n", queue_idx);
|
||||
|
||||
q = &priv->queues[queue_idx];
|
||||
if (!q->ready)
|
||||
return;
|
||||
|
||||
desc = (struct vring_desc *)q->desc_addr;
|
||||
avail = (struct vring_avail *)q->avail_addr;
|
||||
used = (struct vring_used *)q->used_addr;
|
||||
old_used_idx = used->idx;
|
||||
|
||||
while (q->last_avail_idx != avail->idx) {
|
||||
processed_something = true;
|
||||
uint ring_idx = q->last_avail_idx % q->num;
|
||||
uint desc_head_idx = avail->ring[ring_idx];
|
||||
uint used_ring_idx;
|
||||
int written;
|
||||
int ret;
|
||||
|
||||
log_debug("Found request at avail ring index %u (desc head %u)\n",
|
||||
ring_idx, desc_head_idx);
|
||||
|
||||
ret = ops->process_request(emul_dev, desc, desc_head_idx,
|
||||
&written);
|
||||
if (ret)
|
||||
log_warning("Failed to process request (err=%dE)\n",
|
||||
ret);
|
||||
|
||||
used_ring_idx = used->idx % q->num;
|
||||
used->ring[used_ring_idx].id = desc_head_idx;
|
||||
used->ring[used_ring_idx].len = written;
|
||||
used->idx++;
|
||||
q->last_avail_idx++;
|
||||
}
|
||||
|
||||
if (processed_something) {
|
||||
bool needs_interrupt = true;
|
||||
|
||||
log_debug("finished processing, new used_idx is %d.\n",
|
||||
used->idx);
|
||||
if (priv->driver_features & BIT(VIRTIO_RING_F_EVENT_IDX)) {
|
||||
struct {
|
||||
struct vring_avail *avail;
|
||||
unsigned int num;
|
||||
} vr;
|
||||
|
||||
vr.avail = avail;
|
||||
vr.num = q->num;
|
||||
|
||||
needs_interrupt =
|
||||
vring_need_event(vring_used_event((&vr)),
|
||||
used->idx, old_used_idx);
|
||||
log_debug("EVENT_IDX is enabled; driver wants event "
|
||||
"at %u needs_interrupt %d\n",
|
||||
vring_used_event(&vr), needs_interrupt);
|
||||
}
|
||||
|
||||
if (needs_interrupt) {
|
||||
log_debug("sending VRING interrupt\n");
|
||||
priv->interrupt_status |= VIRTIO_MMIO_INT_VRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long h_read(void *ctx, const void *addr, enum sandboxio_size_t size)
|
||||
{
|
||||
struct udevice *dev = ctx;
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
ulong offset = (ulong)addr - (ulong)priv->mmio.base;
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
struct virtio_emul_queue *q;
|
||||
u32 val = 0;
|
||||
|
||||
if (offset >= VIRTIO_MMIO_CONFIG) {
|
||||
ulong config_offset = offset - VIRTIO_MMIO_CONFIG;
|
||||
int ret;
|
||||
|
||||
ret = ops->get_config(emul_dev, config_offset, &val, size);
|
||||
if (ret)
|
||||
log_warning("Failed to process request (err=%dE)\n",
|
||||
ret);
|
||||
return val;
|
||||
}
|
||||
|
||||
if (priv->queue_sel >= priv->num_queues) {
|
||||
log_debug("invalid queue_sel %d\n", priv->queue_sel);
|
||||
return 0;
|
||||
}
|
||||
q = &priv->queues[priv->queue_sel];
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_MMIO_MAGIC_VALUE:
|
||||
return ('v' | 'i' << 8 | 'r' << 16 | 't' << 24);
|
||||
case VIRTIO_MMIO_VERSION:
|
||||
return 2;
|
||||
case VIRTIO_MMIO_DEVICE_ID:
|
||||
return ops->get_device_id(emul_dev);
|
||||
case VIRTIO_MMIO_VENDOR_ID:
|
||||
return VENDOR_ID;
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES:
|
||||
return !priv->features_sel ?
|
||||
(priv->features & 0xffffffff) :
|
||||
(priv->features >> 32);
|
||||
case VIRTIO_MMIO_QUEUE_NUM_MAX:
|
||||
return QUEUE_MAX_SIZE;
|
||||
case VIRTIO_MMIO_QUEUE_READY:
|
||||
return q->ready;
|
||||
case VIRTIO_MMIO_INTERRUPT_STATUS:
|
||||
return priv->interrupt_status;
|
||||
case VIRTIO_MMIO_STATUS:
|
||||
return priv->status;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_LOW:
|
||||
return q->desc_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
|
||||
return q->desc_addr >> 32;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_LOW:
|
||||
return q->avail_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_HIGH:
|
||||
return q->avail_addr >> 32;
|
||||
case VIRTIO_MMIO_QUEUE_USED_LOW:
|
||||
return q->used_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_USED_HIGH:
|
||||
return q->used_addr >> 32;
|
||||
case VIRTIO_MMIO_CONFIG_GENERATION:
|
||||
return priv->config_generation;
|
||||
default:
|
||||
log_debug("unhandled read from offset 0x%lx\n", offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void h_write(void *ctx, void *addr, unsigned int val,
|
||||
enum sandboxio_size_t size)
|
||||
{
|
||||
struct udevice *dev = ctx;
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
ulong offset = (ulong)addr - (ulong)priv->mmio.base;
|
||||
struct virtio_emul_queue *q;
|
||||
|
||||
if (offset >= VIRTIO_MMIO_CONFIG)
|
||||
return;
|
||||
|
||||
if (priv->queue_sel >= priv->num_queues && offset != VIRTIO_MMIO_QUEUE_SEL)
|
||||
return;
|
||||
q = &priv->queues[priv->queue_sel];
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
|
||||
priv->features_sel = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES:
|
||||
if (priv->features_sel == 0)
|
||||
priv->driver_features = (priv->driver_features &
|
||||
0xffffffff00000000) | val;
|
||||
else
|
||||
priv->driver_features = (priv->driver_features &
|
||||
0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES_SEL:
|
||||
priv->features_sel = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_SEL:
|
||||
if (val < priv->num_queues)
|
||||
priv->queue_sel = val;
|
||||
else
|
||||
log_debug("tried to select invalid queue %u\n", val);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NUM:
|
||||
q->num = (val > 0 && val <= QUEUE_MAX_SIZE) ? val : 0;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_READY:
|
||||
q->ready = val & 0x1;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NOTIFY:
|
||||
process_queue(emul_dev, priv, val);
|
||||
break;
|
||||
case VIRTIO_MMIO_INTERRUPT_ACK:
|
||||
priv->interrupt_status &= ~val;
|
||||
break;
|
||||
case VIRTIO_MMIO_STATUS:
|
||||
priv->status = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_LOW:
|
||||
q->desc_addr = (q->desc_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
|
||||
q->desc_addr = (q->desc_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_LOW:
|
||||
q->avail_addr = (q->avail_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_HIGH:
|
||||
q->avail_addr = (q->avail_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_USED_LOW:
|
||||
q->used_addr = (q->used_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_USED_HIGH:
|
||||
q->used_addr = (q->used_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
default:
|
||||
log_debug("unhandled write to offset 0x%lx\n", offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int sandbox_emul_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
/* set up the MMIO base so that virtio_mmio_probe() can find it */
|
||||
priv->mmio.base = memalign(SZ_4K, MMIO_SIZE);
|
||||
if (!priv->mmio.base)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = sandbox_mmio_add(priv->mmio.base, MMIO_SIZE, h_read, h_write,
|
||||
dev);
|
||||
if (ret) {
|
||||
free(priv->mmio.base);
|
||||
return log_msg_ret("sep", ret);
|
||||
}
|
||||
|
||||
priv->num_queues = MAX_VIRTIO_QUEUES;
|
||||
priv->features = BIT(VIRTIO_F_VERSION_1) |
|
||||
BIT(VIRTIO_RING_F_EVENT_IDX) |
|
||||
ops->get_features(emul_dev);
|
||||
|
||||
log_debug("sandbox virtio emulator, mmio %p\n", priv->mmio.base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sandbox_emul_remove(struct udevice *dev)
|
||||
{
|
||||
sandbox_mmio_remove(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id virtio_sandbox2_ids[] = {
|
||||
{ .compatible = "sandbox,virtio-emul" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(virtio_emul) = {
|
||||
.name = "virtio-emul",
|
||||
.id = UCLASS_VIRTIO,
|
||||
.of_match = virtio_sandbox2_ids,
|
||||
.probe = virtio_mmio_probe,
|
||||
.remove = sandbox_emul_remove,
|
||||
.ops = &virtio_mmio_ops,
|
||||
.of_to_plat = sandbox_emul_of_to_plat,
|
||||
.priv_auto = sizeof(struct sandbox_emul_priv),
|
||||
};
|
||||
|
||||
UCLASS_DRIVER(virtio_emul) = {
|
||||
.name = "virtio_emul",
|
||||
.id = UCLASS_VIRTIO_EMUL,
|
||||
#if CONFIG_IS_ENABLED(OF_REAL)
|
||||
.post_bind = dm_scan_fdt_dev,
|
||||
#endif
|
||||
};
|
||||
110
drivers/virtio/sandbox_emul.h
Normal file
110
drivers/virtio/sandbox_emul.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU
|
||||
* side of virtio, using the MMIO driver and handling any accesses
|
||||
*
|
||||
* This handles traffic from the virtio_ring
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#ifndef __SANDBOX_EMUL_H
|
||||
#define __SANDBOX_EMUL_H
|
||||
|
||||
#include "virtio_mmio.h"
|
||||
#include "virtio_types.h"
|
||||
|
||||
enum sandboxio_size_t;
|
||||
struct udevice;
|
||||
struct vring_desc;
|
||||
|
||||
enum {
|
||||
MAX_VIRTIO_QUEUES = 8,
|
||||
QUEUE_MAX_SIZE = 256,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct virtio_emul_queue - Emulator's state for a single virtqueue
|
||||
*/
|
||||
struct virtio_emul_queue {
|
||||
__virtio32 num;
|
||||
__virtio32 ready;
|
||||
__virtio64 desc_addr;
|
||||
__virtio64 avail_addr;
|
||||
__virtio64 used_addr;
|
||||
__virtio16 last_avail_idx; // Device's internal counter
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sandbox_emul_priv - Private info for the emulator
|
||||
*/
|
||||
struct sandbox_emul_priv {
|
||||
struct virtio_mmio_priv mmio;
|
||||
int num_queues;
|
||||
int queue_sel;
|
||||
u32 status;
|
||||
u64 features_sel;
|
||||
u64 features;
|
||||
u64 driver_features;
|
||||
u32 interrupt_status;
|
||||
u32 config_generation;
|
||||
struct virtio_emul_queue queues[MAX_VIRTIO_QUEUES];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct virtio_emul_ops - Operations for a virtio device emulator
|
||||
*
|
||||
* @process_request:
|
||||
* @get_config: Reads from the device-specific configuration space
|
||||
* @get_features: Returns the device-specific feature bits
|
||||
*/
|
||||
struct virtio_emul_ops {
|
||||
/**
|
||||
* process_request() - Handles a single request from the driver
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @descs: Pointer to the virtqueue's descriptor table
|
||||
* @head_idx: The index of the first descriptor in the chain for
|
||||
* this request
|
||||
* @writtenp: Returns the total number of bytes written by the
|
||||
* device into the driver's buffers (e.g. for a read
|
||||
* request and the status byte). This is what will be
|
||||
* placed in the `len` field of the used ring element.
|
||||
* @return 0 on success, negative on error.
|
||||
*/
|
||||
int (*process_request)(struct udevice *dev, struct vring_desc *descs,
|
||||
u32 head_idx, int *writtenp);
|
||||
|
||||
/**
|
||||
* get_config() - Reads from the device-specific configuration space
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @offset: The byte offset into the configuration space to read from
|
||||
* @buf: The buffer to copy the configuration data into
|
||||
* @size: The number of bytes to read
|
||||
* @return 0 on success, negative on error.
|
||||
*/
|
||||
int (*get_config)(struct udevice *dev, ulong offset, void *buf,
|
||||
enum sandboxio_size_t size);
|
||||
|
||||
/**
|
||||
* get_features() - Returns the device-specific feature bits
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @return A bitmask of the device-specific features to be OR'd
|
||||
* with the transport features.
|
||||
*/
|
||||
u64 (*get_features)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* get_device_id() - Returns the virtio device ID
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @return The virtio device ID for this emulator
|
||||
*/
|
||||
u32 (*get_device_id)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define virtio_emul_get_ops(dev) ((struct virtio_emul_ops *)(dev)->driver->ops)
|
||||
|
||||
#endif
|
||||
@@ -35,6 +35,7 @@ enum uclass_id {
|
||||
UCLASS_USB_EMUL, /* sandbox USB bus device emulator */
|
||||
UCLASS_AXI_EMUL, /* sandbox AXI bus device emulator */
|
||||
UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */
|
||||
UCLASS_VIRTIO_EMUL, /* Emulator for a virtIO transport device */
|
||||
|
||||
/* U-Boot uclasses start here - in alphabetical order */
|
||||
UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */
|
||||
|
||||
Reference in New Issue
Block a user