
From: Simon Glass <sjg@chromium.org> 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 Signed-off-by: Simon Glass <sjg@chromium.org> --- drivers/virtio/Kconfig | 12 ++ drivers/virtio/Makefile | 1 + drivers/virtio/virtio-uclass.c | 1 + drivers/virtio/virtio_scsi.c | 249 +++++++++++++++++++++++++++++++++ include/dt-bindings/virtio.h | 1 + include/virtio.h | 1 + 6 files changed, 265 insertions(+) create mode 100644 drivers/virtio/virtio_scsi.c diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index cfebb733cce..eb4ff368f12 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -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 diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 60ec9b65b17..f101536a863 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -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 diff --git a/drivers/virtio/virtio-uclass.c b/drivers/virtio/virtio-uclass.c index 947d224bbd2..262d8ee4655 100644 --- a/drivers/virtio/virtio-uclass.c +++ b/drivers/virtio/virtio-uclass.c @@ -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, diff --git a/drivers/virtio/virtio_scsi.c b/drivers/virtio/virtio_scsi.c new file mode 100644 index 00000000000..ee54f1c7449 --- /dev/null +++ b/drivers/virtio/virtio_scsi.c @@ -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, +}; diff --git a/include/dt-bindings/virtio.h b/include/dt-bindings/virtio.h index 8de4a9efcfa..df785b079df 100644 --- a/include/dt-bindings/virtio.h +++ b/include/dt-bindings/virtio.h @@ -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 */ diff --git a/include/virtio.h b/include/virtio.h index b0811cbdc24..d30c3eae59b 100644 --- a/include/virtio.h +++ b/include/virtio.h @@ -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 */ -- 2.43.0