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@chromium.org>
154 lines
3.6 KiB
C
154 lines
3.6 KiB
C
// 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@chromium.org>
|
|
*/
|
|
|
|
#define LOG_CATEGORY UCLASS_VIRTIO
|
|
|
|
#include <dm.h>
|
|
#include <malloc.h>
|
|
#include <asm/io.h>
|
|
#include <dt-bindings/virtio.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;
|
|
};
|
|
|
|
static int blk_emul_process_request(struct udevice *dev,
|
|
struct vring_desc *descs, u32 head_idx,
|
|
int *writtenp)
|
|
{
|
|
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;
|
|
*writtenp = 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);
|
|
*writtenp = 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);
|
|
*writtenp = 0;
|
|
break;
|
|
default:
|
|
log_warning("unknown request type 0x%x\n", hdr->type);
|
|
*status = VIRTIO_BLK_S_UNSUPP;
|
|
*writtenp = 1;
|
|
return 0;
|
|
}
|
|
|
|
*status = VIRTIO_BLK_S_OK;
|
|
*writtenp += 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_request,
|
|
.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),
|
|
};
|