virtio: Add support for virtio-scsi

This feature can be useful where the host wants to provide more than one
block device, since it allows all of them to be under one virtio device.

Add an implementation of virtio-scsi

Series-to: concept
Cover-letter:
virtio: Support SCSI over virtio
U-Boot supports virtio-blk and in most cases this is sufficient for
providing access to a disk. However some larger users such as LXD prefer
virtio-scsi since it groups disks together and provides unified probing
of devices.

This series implements this protocol.
END

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass
2025-06-28 14:20:03 -06:00
parent cc21b53ce9
commit 9a6d610a34
6 changed files with 265 additions and 0 deletions

View File

@@ -98,4 +98,16 @@ config VIRTIO_FS
A specification for the protocol is available at
https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
config VIRTIO_SCSI
bool "SCSI driver for virtio devices"
depends on SCSI
select VIRTIO
default y
help
This driver provides support for virtio-based paravirtual SCSI. It
allows access to storage devices using the
A specification for the protocol is available at
https://docs.oasis-open.org/virtio/virtio/v1.3/virtio-v1.3.html
endmenu

View File

@@ -13,3 +13,4 @@ obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
obj-$(CONFIG_VIRTIO_FS) += fs.o fs_dir.o fs_file.o fs_compat.o
obj-$(CONFIG_VIRTIO_SCSI) += virtio_scsi.o

View File

@@ -31,6 +31,7 @@ static const char *const virtio_drv_name[VIRTIO_ID_MAX_NUM] = {
[VIRTIO_ID_BLOCK] = VIRTIO_BLK_DRV_NAME,
[VIRTIO_ID_RNG] = VIRTIO_RNG_DRV_NAME,
[VIRTIO_ID_FS] = VIRTIO_FS_DRV_NAME,
[VIRTIO_ID_SCSI] = VIRTIO_SCSI_DRV_NAME,
};
int virtio_get_config(struct udevice *vdev, unsigned int offset,

View File

@@ -0,0 +1,249 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* U-Boot driver for VirtIO SCSI host devices
*
* This driver implements the host-side interface for VirtIO SCSI devices,
* allowing U-Boot to communicate with SCSI LUNs provided by a hypervisor.
*
* Based on the VirtIO v1.3 specification, Section 5.6: SCSI Host Device
*/
#define LOG_CATEGORY UCLASS_VIRTIO
#include <blk.h>
#include <dm.h>
#include <malloc.h>
#include <scsi.h>
#include <virtio.h>
#include <virtio_ring.h>
#include "virtio_scsi.h"
static_assert(CMD_BUF_SIZE >= VIRTIO_SCSI_CDB_SIZE);
static_assert(SENSE_BUF_LEN >= VIRTIO_SCSI_SENSE_SIZE);
/*
* We need three virtqueues: controlq, eventq, and requestq.
* The request queue is the one we will use for actual SCSI commands.
*/
enum {
QUEUE_CONTROL,
QUEUE_EVENT,
QUEUE_REQUEST,
QUEUE_COUNT,
};
/*
* struct virtio_scsi_cmd - represents a pending SCSI command
*
* The address of this struct is used as a 'cookie' to identify the request in
* the virtqueue, although in practice there is only one pending request at a
* time
*/
struct virtio_scsi_cmd {
struct virtio_scsi_cmd_req req;
struct virtio_scsi_cmd_resp resp;
};
/**
* struct virtio_scsi_priv - Per-device private data
*
* @vqs: Array of virtqueues for this device
* @max_target: Maximum target ID supported by the device
* @max_lun: Maximum LUN ID supported by the device
* @v_cmd: Pre-allocated virtio-command struct to avoid allocation in the
* I/O path
* @id: ID number for the next transation
*/
struct virtio_scsi_priv {
struct virtqueue *vqs[QUEUE_COUNT];
int max_target;
int max_lun;
int id;
/* Pre-allocated command struct to avoid allocation in send_cmd */
struct virtio_scsi_cmd v_cmd;
};
u32 features[] = {
VIRTIO_SCSI_F_INOUT,
};
static int virtio_scsi_exec(struct udevice *dev, struct scsi_cmd *cmd)
{
struct virtio_scsi_priv *priv = dev_get_priv(dev);
struct virtqueue *vq = priv->vqs[QUEUE_REQUEST];
struct virtio_scsi_cmd *v_cmd = &priv->v_cmd;
struct virtio_sg out_sgs[2], in_sgs[2];
struct virtio_sg *sgs[4];
uint out_count = 0, in_count = 0;
int len, i, ret;
uint dummy_len;
/* Clear the pre-allocated command struct to avoid stale data */
memset(v_cmd, '\0', sizeof(*v_cmd));
/*
* Fill in the request header.
* The LUN is encoded in a specific way for virtio-scsi.
* LUN 0 is encoded as 0x4000. LUNs 1-255 are encoded as themselves.
*/
v_cmd->req.lun[0] = 1;
v_cmd->req.lun[1] = cmd->target;
v_cmd->req.lun[2] = 0x40 | ((cmd->lun >> 8) & 0x3f);
v_cmd->req.lun[3] = cmd->lun & 0xff;
v_cmd->req.tag = priv->id++;
v_cmd->req.task_attr = VIRTIO_SCSI_S_SIMPLE;
/* Set the command CDB */
memcpy(v_cmd->req.cdb, cmd->cmd, cmd->cmdlen);
log_debug("cmd %x\n", *cmd->cmd);
/*
* Set up scatter-gather lists for the request and response.
* The device writes to IN buffers and reads from OUT buffers.
*/
out_sgs[out_count].addr = &v_cmd->req;
out_sgs[out_count].length = sizeof(v_cmd->req);
out_count++;
in_sgs[in_count].addr = &v_cmd->resp;
in_sgs[in_count].length = sizeof(v_cmd->resp);
in_count++;
if (cmd->datalen > 0) {
switch (cmd->cmd[0]) {
case SCSI_READ6:
case SCSI_READ10:
case SCSI_READ16:
case SCSI_INQUIRY:
in_sgs[in_count].addr = cmd->pdata;
in_sgs[in_count].length = cmd->datalen;
in_count++;
break;
case SCSI_WRITE6:
case SCSI_WRITE10:
out_sgs[out_count].addr = cmd->pdata;
out_sgs[out_count].length = cmd->datalen;
out_count++;
break;
case SCSI_REPORT_LUNS:
case SCSI_RD_CAPAC:
in_sgs[in_count].addr = cmd->pdata;
in_sgs[in_count].length = cmd->datalen;
in_count++;
break;
default:
printf("Unsupported SCSI command %#02x\n", cmd->cmd[0]);
return -ENOTSUPP;
}
}
for (i = 0; i < out_count; i++)
sgs[i] = &out_sgs[i];
for (i = 0; i < in_count; i++)
sgs[out_count + i] = &in_sgs[i];
/* Add the buffers to the request virtqueue */
len = virtqueue_add(vq, sgs, out_count, in_count);
if (len < 0) {
printf("Failed to add buffer to virtqueue: %d\n", len);
return len;
}
virtqueue_kick(vq);
log_debug("wait...");
while (virtqueue_get_buf(vq, &dummy_len) != v_cmd)
;
log_debug("done\n");
/* Process the response */
ret = 0;
if (v_cmd->resp.response) {
if (v_cmd->resp.response == VIRTIO_SCSI_S_BAD_TARGET) {
/*
* This is an expected result when scanning for a
* non-existent device. Handle it silently.
*/
ret = -ENODEV;
} else {
log_err("virtio-scsi: response error: %#x\n",
v_cmd->resp.response);
ret = -EIO;
}
} else {
ret = v_cmd->resp.status;
if (ret)
log_debug("status %x\n", v_cmd->resp.status);
cmd->sensedatalen = min_t(u32, SENSE_BUF_LEN,
v_cmd->resp.sense_len);
if (cmd->sensedatalen)
memcpy(cmd->sense_buf, v_cmd->resp.sense,
cmd->sensedatalen);
}
return ret;
}
static int virtio_scsi_probe(struct udevice *dev)
{
struct virtio_scsi_priv *priv = dev_get_priv(dev);
struct virtio_scsi_config config;
struct scsi_plat *uc_plat;
int ret;
/* read the device-specific configuration space */
virtio_cread_bytes(dev, 0, &config, sizeof(config));
priv->max_target = config.max_target;
/* U-Boot only supports up to 8 LUNs */
priv->max_lun = min(config.max_lun, 7u);
log_debug("virtio-scsi: max_target=%d, max_lun=%d, sense_size=%d, cdb_size=%d\n",
priv->max_target, priv->max_lun, config.sense_size,
config.cdb_size);
/* allocate the virtqueues */
ret = virtio_find_vqs(dev, QUEUE_COUNT, priv->vqs);
if (ret) {
printf("Failed to find vqs\n");
return -ENOENT;
}
uc_plat = dev_get_uclass_plat(dev);
uc_plat->max_lun = priv->max_lun;
uc_plat->max_id = priv->max_target;
return 0;
}
static int virtio_scsi_bind(struct udevice *dev)
{
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(dev->parent);
/* Indicate what driver features we support */
virtio_driver_features_init(uc_priv, features, ARRAY_SIZE(features),
NULL, 0);
return 0;
}
static const struct scsi_ops virtio_scsi_ops = {
.exec = virtio_scsi_exec,
};
static const struct udevice_id virtio_scsi_ids[] = {
{ .compatible = "virtio,scsi" },
{ }
};
U_BOOT_DRIVER(virtio_scsi) = {
.name = VIRTIO_SCSI_DRV_NAME,
.id = UCLASS_SCSI,
.of_match = virtio_scsi_ids,
.priv_auto = sizeof(struct virtio_scsi_priv),
.ops = &virtio_scsi_ops,
.probe = virtio_scsi_probe,
.remove = virtio_reset,
.bind = virtio_scsi_bind,
.flags = DM_FLAG_ACTIVE_DMA,
};

View File

@@ -14,6 +14,7 @@
#define VIRTIO_ID_NET 1 /* virtio net */
#define VIRTIO_ID_BLOCK 2 /* virtio block */
#define VIRTIO_ID_RNG 4 /* virtio rng */
#define VIRTIO_ID_SCSI 8
#define VIRTIO_ID_MAX_NUM 27
#define VIRTIO_ID_FS 26 /* virtio filesystem */

View File

@@ -31,6 +31,7 @@
#define VIRTIO_BLK_DRV_NAME "virtio-blk"
#define VIRTIO_RNG_DRV_NAME "virtio-rng"
#define VIRTIO_FS_DRV_NAME "virtio-fs"
#define VIRTIO_SCSI_DRV_NAME "virtio-scsi"
/* Status byte for guest to report progress, and synchronize features */