[PATCH 07/13] virtio: Implement a simple block-device emulator

Simon Glass sjg at chromium.org
Wed May 27 18:10:17 CEST 2026


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 <sjg at chromium.org>
---

 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 <sjg at chromium.org>
+ */
+
+#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



More information about the U-Boot mailing list