Add an emulator driver for block devices, so that sandbox can test these
fully. The emulator uses MMIO to communicate with the controlling virtio
device.

Signed-off-by: Simon Glass <[email protected]>
---

 configs/sandbox_defconfig   |   1 +
 drivers/virtio/Makefile     |   2 +-
 drivers/virtio/emul_blk.c   | 176 ++++++++++++++++++++++++++++++++++++
 drivers/virtio/virtio_blk.h |   3 +
 4 files changed, 181 insertions(+), 1 deletion(-)
 create mode 100644 drivers/virtio/emul_blk.c

diff --git a/configs/sandbox_defconfig b/configs/sandbox_defconfig
index ba800f7d19d..fbc30969534 100644
--- a/configs/sandbox_defconfig
+++ b/configs/sandbox_defconfig
@@ -363,6 +363,7 @@ CONFIG_OSD=y
 CONFIG_SANDBOX_OSD=y
 CONFIG_BMP_16BPP=y
 CONFIG_BMP_24BPP=y
+CONFIG_VIRTIO_BLK=y
 CONFIG_W1=y
 CONFIG_W1_GPIO=y
 CONFIG_W1_EEPROM=y
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index d928c7b0ad2..4709e16f789 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -8,7 +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_SANDBOX_EMUL) += sandbox_emul.o emul_blk.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/emul_blk.c b/drivers/virtio/emul_blk.c
new file mode 100644
index 00000000000..62ee125cf3c
--- /dev/null
+++ b/drivers/virtio/emul_blk.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Emulation of a block device. This implements a simple version of the QEMU
+ * side of the interface.
+ *
+ * Copyright 2025 Simon Glass <[email protected]>
+ */
+
+#define LOG_CATEGORY   UCLASS_VIRTIO
+
+#include <dm.h>
+#include <malloc.h>
+#include <virtio.h>
+#include <asm/io.h>
+#include <linux/sizes.h>
+#include "virtio_blk.h"
+#include "virtio_ring.h"
+#include "sandbox_emul.h"
+
+enum {
+       DISK_SIZE_MB    = 1,
+       SECTOR_SIZE     = 512,
+};
+
+/**
+ * struct virtio_blk_emul_priv - private data for the block emulator
+ *
+ * @config: virtio block-device-configuration structure, exposed to the driver
+ *     through the config space
+ * @disk_data: allocated memory for the virtual disk
+ * @disk_size: total size of the virtual disk in bytes
+ */
+struct virtio_blk_emul_priv {
+       struct virtio_blk_config config;
+       void *disk_data;
+       u64 disk_size;
+};
+
+/**
+ * blk_emul_process_req() - Handle one virtio-blk request from the driver
+ *
+ * Implements the .process_request callback for the virtio-blk emulator.
+ * Each request is a chain of three descriptors:
+ *
+ *     1. virtio_blk_outhdr    - request header (type, sector)
+ *     2. data buffer          - payload for read or write
+ *     3. status byte          - result code returned to the driver
+ *
+ * For VIRTIO_BLK_T_IN the data buffer is filled from @priv->disk_data; for
+ * VIRTIO_BLK_T_OUT it is copied into it. Out-of-range and unsupported
+ * requests are reported through the status byte rather than returned as
+ * errors. The number of bytes the device wrote (data + status) is reported
+ * via @lenp so the caller can populate the used-ring entry.
+ *
+ * @dev: The block emulator device
+ * @descs: Pointer to the virtqueue's descriptor table
+ * @head_idx: Index of the first descriptor in the chain
+ * @lenp: Returns total bytes written by the device
+ * Return: 0 (a malformed chain returns -EIO; payload errors are reported
+ *     through the status byte and still return 0)
+ */
+static int blk_emul_process_req(struct udevice *dev,
+                               struct vring_desc *descs, u32 head_idx,
+                               int *lenp)
+{
+       struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
+       struct vring_desc *hdr_desc, *data_desc, *status_desc;
+       struct virtio_blk_outhdr *hdr;
+       void *data_buf;
+       u64 offset;
+       u8 *status;
+
+       hdr_desc = &descs[head_idx];
+       if (!(hdr_desc->flags & VRING_DESC_F_NEXT))
+               return -EIO;
+       data_desc = &descs[hdr_desc->next];
+       if (!(data_desc->flags & VRING_DESC_F_NEXT))
+               return -EIO;
+       status_desc = &descs[data_desc->next];
+
+       hdr = (struct virtio_blk_outhdr *)hdr_desc->addr;
+       status = (u8 *)status_desc->addr;
+
+       offset = hdr->sector * SECTOR_SIZE;
+       if (offset + data_desc->len > priv->disk_size) {
+               *status = VIRTIO_BLK_S_IOERR;
+               *lenp = 1;
+               return 0;
+       }
+
+       data_buf = (void *)data_desc->addr;
+
+       switch (hdr->type) {
+       case VIRTIO_BLK_T_IN:
+               log_debug("read: sector %lld, len %u\n", hdr->sector,
+                         data_desc->len);
+               memcpy(data_buf, priv->disk_data + offset, data_desc->len);
+               *lenp = data_desc->len;
+               break;
+       case VIRTIO_BLK_T_OUT:
+               log_debug("write: sector %lld, len %u\n", hdr->sector,
+                         data_desc->len);
+               memcpy(priv->disk_data + offset, data_buf, data_desc->len);
+               *lenp = 0;
+               break;
+       default:
+               log_warning("unknown request type 0x%x\n", hdr->type);
+               *status = VIRTIO_BLK_S_UNSUPP;
+               *lenp = 1;
+               return 0;
+       }
+
+       *status = VIRTIO_BLK_S_OK;
+       *lenp += 1; /* For the status byte */
+
+       return 0;
+}
+
+static int blk_emul_get_config(struct udevice *dev, ulong offset, void *buf,
+                              enum sandboxio_size_t size)
+{
+       struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
+
+       if (offset + size > sizeof(priv->config))
+               return -EIO;
+
+       memcpy(buf, (u8 *)&priv->config + offset, size);
+
+       return 0;
+}
+
+static u64 blk_emul_get_features(struct udevice *dev)
+{
+       return BIT(VIRTIO_BLK_F_BLK_SIZE);
+}
+
+static u32 blk_emul_get_device_id(struct udevice *dev)
+{
+       return VIRTIO_ID_BLOCK;
+}
+
+static int virtio_blk_emul_probe(struct udevice *dev)
+{
+       struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
+
+       priv->disk_size = (u64)DISK_SIZE_MB * SZ_1M;
+       priv->disk_data = calloc(1, priv->disk_size);
+       if (!priv->disk_data)
+               return -ENOMEM;
+
+       priv->config.capacity = priv->disk_size / SECTOR_SIZE;
+       priv->config.blk_size = SECTOR_SIZE;
+
+       return 0;
+}
+
+static struct virtio_emul_ops blk_emul_ops = {
+       .process_request = blk_emul_process_req,
+       .get_config = blk_emul_get_config,
+       .get_features = blk_emul_get_features,
+       .get_device_id = blk_emul_get_device_id,
+};
+
+static const struct udevice_id virtio_blk_emul_ids[] = {
+       { .compatible = "sandbox,virtio-blk-emul" },
+       { }
+};
+
+U_BOOT_DRIVER(virtio_blk_emul) = {
+       .name   = "virtio_blk_emul",
+       .id     = UCLASS_VIRTIO_EMUL,
+       .of_match = virtio_blk_emul_ids,
+       .probe  = virtio_blk_emul_probe,
+       .ops    = &blk_emul_ops,
+       .priv_auto      = sizeof(struct virtio_blk_emul_priv),
+};
diff --git a/drivers/virtio/virtio_blk.h b/drivers/virtio/virtio_blk.h
index b37ba264df4..cbb6996ebde 100644
--- a/drivers/virtio/virtio_blk.h
+++ b/drivers/virtio/virtio_blk.h
@@ -9,6 +9,9 @@
 #ifndef _LINUX_VIRTIO_BLK_H
 #define _LINUX_VIRTIO_BLK_H
 
+#include <compiler.h>
+#include "virtio_types.h"
+
 /* Feature bits */
 #define VIRTIO_BLK_F_SIZE_MAX  1       /* Indicates maximum segment size */
 #define VIRTIO_BLK_F_SEG_MAX   2       /* Indicates maximum # of segments */
-- 
2.43.0

Reply via email to