
From: Simon Glass <sjg@chromium.org> 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> --- arch/Kconfig | 1 + configs/tools-only_defconfig | 2 +- drivers/virtio/Kconfig | 8 + drivers/virtio/Makefile | 1 + drivers/virtio/sandbox_emul.c | 313 ++++++++++++++++++++++++++++++++++ drivers/virtio/sandbox_emul.h | 110 ++++++++++++ include/dm/uclass-id.h | 1 + 7 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 drivers/virtio/sandbox_emul.c create mode 100644 drivers/virtio/sandbox_emul.h diff --git a/arch/Kconfig b/arch/Kconfig index 493c827d74d..fa7839f6c30 100644 --- a/arch/Kconfig +++ b/arch/Kconfig @@ -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 diff --git a/configs/tools-only_defconfig b/configs/tools-only_defconfig index c0c5387928f..20ac1e63f09 100644 --- a/configs/tools-only_defconfig +++ b/configs/tools-only_defconfig @@ -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 diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 512ac376f18..858556fe802 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -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 diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 4c63a6c6904..d928c7b0ad2 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -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 diff --git a/drivers/virtio/sandbox_emul.c b/drivers/virtio/sandbox_emul.c new file mode 100644 index 00000000000..2c75fd546d1 --- /dev/null +++ b/drivers/virtio/sandbox_emul.c @@ -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 +}; diff --git a/drivers/virtio/sandbox_emul.h b/drivers/virtio/sandbox_emul.h new file mode 100644 index 00000000000..d0a841a38df --- /dev/null +++ b/drivers/virtio/sandbox_emul.h @@ -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 diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 49f98cd2e1a..4818d9bd272 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -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) */ -- 2.43.0