virtio-scsi is a simple HBA that talks to the host via a single
vring. The implementation looks like a hybrid of usb-msc and
virtio-blk.
Signed-off-by: Paolo Bonzini <pbonzini(a)redhat.com>
---
Makefile | 2 +-
src/Kconfig | 6 ++
src/block.c | 3 +-
src/blockcmd.c | 5 +-
src/boot.c | 14 ++++
src/boot.h | 1 +
src/disk.c | 3 +-
src/disk.h | 1 +
src/pci_ids.h | 1 +
src/post.c | 2 +
src/virtio-scsi.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/virtio-scsi.h | 47 ++++++++++++
12 files changed, 290 insertions(+), 4 deletions(-)
create mode 100644 src/virtio-scsi.c
create mode 100644 src/virtio-scsi.h
diff --git a/Makefile b/Makefile
index 90e1bce..818d2b3 100644
--- a/Makefile
+++ b/Makefile
@@ -15,7 +15,7 @@ SRCBOTH=misc.c stacks.c pmm.c output.c util.c block.c floppy.c ata.c mouse.c \
kbd.c pci.c serial.c clock.c pic.c cdrom.c ps2port.c smp.c resume.c \
pnpbios.c pirtable.c vgahooks.c ramdisk.c pcibios.c blockcmd.c \
usb.c usb-uhci.c usb-ohci.c usb-ehci.c usb-hid.c usb-msc.c \
- virtio-ring.c virtio-pci.c virtio-blk.c apm.c ahci.c
+ virtio-ring.c virtio-pci.c virtio-blk.c virtio-scsi.c apm.c ahci.c
SRC16=$(SRCBOTH) system.c disk.c font.c
SRC32FLAT=$(SRCBOTH) post.c shadow.c memmap.c coreboot.c boot.c \
acpi.c smm.c mptable.c smbios.c pciinit.c optionroms.c mtrr.c \
diff --git a/src/Kconfig b/src/Kconfig
index 250663a..53fda9b 100644
--- a/src/Kconfig
+++ b/src/Kconfig
@@ -113,6 +113,12 @@ menu "Hardware support"
default y
help
Support boot from virtio-blk storage.
+ config VIRTIO_SCSI
+ depends on DRIVES && !COREBOOT
+ bool "virtio-scsi controllers"
+ default y
+ help
+ Support boot from virtio-scsi storage.
config FLOPPY
depends on DRIVES
bool "Floppy controller"
diff --git a/src/block.c b/src/block.c
index eeebd83..e607d67 100644
--- a/src/block.c
+++ b/src/block.c
@@ -279,7 +279,7 @@ map_floppy_drive(struct drive_s *drive_g)
int
process_scsi_op(struct disk_op_s *op)
{
- if (!CONFIG_USB_MSC)
+ if (!CONFIG_VIRTIO_SCSI && !CONFIG_USB_MSC)
return 0;
switch (op->command) {
case CMD_READ:
@@ -320,6 +320,7 @@ process_op(struct disk_op_s *op)
case DTYPE_AHCI:
return process_ahci_op(op);
case DTYPE_USB:
+ case DTYPE_VIRTIO_SCSI:
return process_scsi_op(op);
default:
op->count = 0;
diff --git a/src/blockcmd.c b/src/blockcmd.c
index 3476cfc..b2a8d71 100644
--- a/src/blockcmd.c
+++ b/src/blockcmd.c
@@ -12,6 +12,7 @@
#include "ata.h" // atapi_cmd_data
#include "ahci.h" // atapi_cmd_data
#include "usb-msc.h" // usb_cmd_data
+#include "virtio-scsi.h" // virtio_scsi_cmd_data
// Route command to low-level handler.
static int
@@ -25,6 +26,8 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
return usb_cmd_data(op, cdbcmd, blocksize);
case DTYPE_AHCI:
return ahci_cmd_data(op, cdbcmd, blocksize);
+ case DTYPE_VIRTIO_SCSI:
+ return virtio_scsi_cmd_data(op, cdbcmd, blocksize);
default:
op->count = 0;
return DISK_RET_EPARAM;
@@ -79,7 +82,7 @@ scsi_is_ready(struct disk_op_s *op)
int
scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc)
{
- if (!CONFIG_USB_MSC)
+ if (!CONFIG_USB_MSC && !CONFIG_VIRTIO_SCSI)
return 0;
struct disk_op_s dop;
diff --git a/src/boot.c b/src/boot.c
index e26dad1..c737ba4 100644
--- a/src/boot.c
+++ b/src/boot.c
@@ -128,6 +128,20 @@ int bootprio_find_pci_device(struct pci_device *pci)
return find_prio(desc);
}
+int bootprio_find_scsi_device(struct pci_device *pci, int target, int lun)
+{
+ if (!CONFIG_BOOTORDER)
+ return -1;
+ if (!pci)
+ // support only pci machine for now
+ return -1;
+ // Find scsi drive - for example: /pci@i0cf8/scsi@5/channel@0/disk@1,0
+ char desc[256], *p;
+ p = build_pci_path(desc, sizeof(desc), "*", pci);
+ snprintf(p, desc+sizeof(desc)-p, "/*@0/*@%d,%d", target, lun);
+ return find_prio(desc);
+}
+
int bootprio_find_ata_device(struct pci_device *pci, int chanid, int slave)
{
if (!CONFIG_BOOTORDER)
diff --git a/src/boot.h b/src/boot.h
index d776aa1..686f04d 100644
--- a/src/boot.h
+++ b/src/boot.h
@@ -14,6 +14,7 @@ void boot_add_cbfs(void *data, const char *desc, int prio);
void boot_prep(void);
struct pci_device;
int bootprio_find_pci_device(struct pci_device *pci);
+int bootprio_find_scsi_device(struct pci_device *pci, int target, int lun);
int bootprio_find_ata_device(struct pci_device *pci, int chanid, int slave);
int bootprio_find_fdc_device(struct pci_device *pci, int port, int fdid);
int bootprio_find_pci_rom(struct pci_device *pci, int instance);
diff --git a/src/disk.c b/src/disk.c
index a124a0f..2812e47 100644
--- a/src/disk.c
+++ b/src/disk.c
@@ -550,7 +550,8 @@ disk_1348(struct bregs *regs, struct drive_s *drive_g)
SET_INT13DPT(regs, blksize, blksize);
if (size < 30 ||
- (type != DTYPE_ATA && type != DTYPE_ATAPI && type != DTYPE_VIRTIO_BLK)) {
+ (type != DTYPE_ATA && type != DTYPE_ATAPI &&
+ type != DTYPE_VIRTIO_BLK && type != DTYPE_VIRTIO_SCSI)) {
disk_ret(regs, DISK_RET_SUCCESS);
return;
}
diff --git a/src/disk.h b/src/disk.h
index dd7c46a..d344399 100644
--- a/src/disk.h
+++ b/src/disk.h
@@ -207,6 +207,7 @@ struct drive_s {
#define DTYPE_USB 0x06
#define DTYPE_VIRTIO_BLK 0x07
#define DTYPE_AHCI 0x08
+#define DTYPE_VIRTIO_SCSI 0x09
#define MAXDESCSIZE 80
diff --git a/src/pci_ids.h b/src/pci_ids.h
index e1cded2..4b59585 100644
--- a/src/pci_ids.h
+++ b/src/pci_ids.h
@@ -2608,3 +2608,4 @@
#define PCI_VENDOR_ID_REDHAT_QUMRANET 0x1af4
#define PCI_DEVICE_ID_VIRTIO_BLK 0x1001
+#define PCI_DEVICE_ID_VIRTIO_SCSI 0x1004
diff --git a/src/post.c b/src/post.c
index b4ad1fa..d7bbcdd 100644
--- a/src/post.c
+++ b/src/post.c
@@ -26,6 +26,7 @@
#include "xen.h" // xen_probe_hvm_info
#include "ps2port.h" // ps2port_setup
#include "virtio-blk.h" // virtio_blk_setup
+#include "virtio-scsi.h" // virtio_scsi_setup
/****************************************************************
@@ -190,6 +191,7 @@ init_hw(void)
cbfs_payload_setup();
ramdisk_setup();
virtio_blk_setup();
+ virtio_scsi_setup();
}
// Begin the boot process by invoking an int0x19 in 16bit mode.
diff --git a/src/virtio-scsi.c b/src/virtio-scsi.c
new file mode 100644
index 0000000..4a39d60
--- /dev/null
+++ b/src/virtio-scsi.c
@@ -0,0 +1,209 @@
+// Virtio SCSI boot support.
+//
+// Copyright (C) 2011 Red Hat Inc.
+//
+// Authors:
+// Paolo Bonzini <pbonzini(a)redhat.com>
+//
+// This file may be distributed under the terms of the GNU LGPLv3 license.
+
+#include "util.h" // dprintf
+#include "pci.h" // foreachpci
+#include "config.h" // CONFIG_*
+#include "biosvar.h" // GET_GLOBAL
+#include "pci_ids.h" // PCI_DEVICE_ID_VIRTIO_BLK
+#include "pci_regs.h" // PCI_VENDOR_ID
+#include "boot.h" // boot_add_hd
+#include "virtio-pci.h"
+#include "virtio-ring.h"
+#include "virtio-scsi.h"
+#include "disk.h"
+
+struct virtio_lun_s {
+ struct drive_s drive;
+ struct pci_device *pci;
+ struct vring_virtqueue *vq;
+ u16 ioaddr;
+ u16 target;
+ u16 lun;
+};
+
+static int
+virtio_scsi_cmd(u16 ioaddr, struct vring_virtqueue *vq, struct disk_op_s *op,
+ void *cdbcmd, u16 target, u16 lun, u32 len)
+{
+ struct virtio_scsi_req_cmd req;
+ struct virtio_scsi_resp_cmd resp;
+ struct vring_list sg[3];
+
+ memset(&req, 0, sizeof(req));
+ req.lun[0] = 1;
+ req.lun[1] = target;
+ req.lun[2] = (lun >> 8) | 0x40;
+ req.lun[3] = (lun & 0xff);
+ memcpy(req.cdb, cdbcmd, 16);
+
+ int datain = (req.cdb[0] != CDB_CMD_WRITE_10);
+ int data_idx = (datain ? 2 : 1);
+ int out_num = (datain ? 1 : 2);
+ int in_num = (len ? 3 : 2) - out_num;
+
+ sg[0].addr = MAKE_FLATPTR(GET_SEG(SS), &req);
+ sg[0].length = sizeof(req);
+
+ sg[out_num].addr = MAKE_FLATPTR(GET_SEG(SS), &resp);
+ sg[out_num].length = sizeof(resp);
+
+ sg[data_idx].addr = op->buf_fl;
+ sg[data_idx].length = len;
+
+ /* Add to virtqueue and kick host */
+ vring_add_buf(vq, sg, out_num, in_num, 0, 0);
+ vring_kick(ioaddr, vq, 1);
+
+ /* Wait for reply */
+ while (!vring_more_used(vq))
+ usleep(5);
+
+ /* Reclaim virtqueue element */
+ vring_get_buf(vq, NULL);
+
+ /* Clear interrupt status register. Avoid leaving interrupts stuck if
+ * VRING_AVAIL_F_NO_INTERRUPT was ignored and interrupts were raised.
+ */
+ vp_get_isr(ioaddr);
+
+ if (resp.response == VIRTIO_SCSI_S_OK && resp.status == 0) {
+ return DISK_RET_SUCCESS;
+ }
+ return DISK_RET_EBADTRACK;
+}
+
+int
+virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
+{
+ struct virtio_lun_s *vlun =
+ container_of(op->drive_g, struct virtio_lun_s, drive);
+
+ return virtio_scsi_cmd(GET_GLOBAL(vlun->ioaddr),
+ GET_GLOBAL(vlun->vq), op, cdbcmd,
+ GET_GLOBAL(vlun->target), GET_GLOBAL(vlun->lun),
+ blocksize * op->count);
+}
+
+static int
+setup_lun_cdrom(struct virtio_lun_s *vlun, char *desc)
+{
+ int prio = bootprio_find_scsi_device(vlun->pci, vlun->target, vlun->lun);
+ boot_add_cd(&vlun->drive, desc, prio);
+ return 0;
+}
+
+static int
+setup_lun_hd(struct virtio_lun_s *vlun, char *desc)
+{
+ if (vlun->drive.blksize != DISK_SECTOR_SIZE) {
+ dprintf(1, "Unsupported block size %d\n", vlun->drive.blksize);
+ return -1;
+ }
+
+ // Register with bcv system.
+ int prio = bootprio_find_scsi_device(vlun->pci, vlun->target, vlun->lun);
+ boot_add_hd(&vlun->drive, desc, prio);
+
+ return 0;
+}
+
+static int
+virtio_scsi_add_lun(struct pci_device *pci, u16 ioaddr,
+ struct vring_virtqueue *vq, u16 target, u16 lun)
+{
+ struct virtio_lun_s *vlun = malloc_fseg(sizeof(*vlun));
+ if (!vlun) {
+ warn_noalloc();
+ return -1;
+ }
+ memset(vlun, 0, sizeof(*vlun));
+ vlun->drive.type = DTYPE_VIRTIO_SCSI;
+ vlun->drive.cntl_id = pci->bdf;
+ vlun->pci = pci;
+ vlun->ioaddr = ioaddr;
+ vlun->vq = vq;
+ vlun->target = target;
+ vlun->lun = lun;
+
+ int pdt, ret;
+ char *desc = NULL;
+ ret = scsi_init_drive(&vlun->drive, "virtio-scsi", &pdt, &desc);
+ if (ret)
+ goto fail;
+
+ if (pdt == SCSI_TYPE_CDROM)
+ ret = setup_lun_cdrom(vlun, desc);
+ else
+ ret = setup_lun_hd(vlun, desc);
+ if (ret)
+ goto fail;
+ return ret;
+
+fail:
+ free(desc);
+ free(vlun);
+ return -1;
+}
+
+static int
+virtio_scsi_scan_target(struct pci_device *pci, u16 ioaddr,
+ struct vring_virtqueue *vq, u16 target)
+{
+ /* TODO: send REPORT LUNS. For now, only LUN 0 is recognized. */
+ int ret = virtio_scsi_add_lun(pci, ioaddr, vq, target, 0);
+ return ret < 0 ? 0 : 1;
+}
+
+static void
+init_virtio_scsi(struct pci_device *pci)
+{
+ u16 bdf = pci->bdf;
+ dprintf(1, "found virtio-scsi at %x:%x\n", pci_bdf_to_bus(bdf),
+ pci_bdf_to_dev(bdf));
+ struct vring_virtqueue *vq = NULL;
+ u16 ioaddr = vp_init_simple(bdf);
+ if (vp_find_vq(ioaddr, 2, &vq) < 0 ) {
+ dprintf(1, "fail to find vq for virtio-scsi %x:%x\n",
+ pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
+ goto fail;
+ }
+
+ int i, tot;
+ for (tot = 0, i = 0; i < 256; i++)
+ tot += virtio_scsi_scan_target(pci, ioaddr, vq, i);
+
+ if (!tot)
+ goto fail;
+
+ vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
+ VIRTIO_CONFIG_S_DRIVER | VIRTIO_CONFIG_S_DRIVER_OK);
+ return;
+
+fail:
+ free(vq);
+}
+
+void
+virtio_scsi_setup(void)
+{
+ ASSERT32FLAT();
+ if (! CONFIG_VIRTIO_SCSI || CONFIG_COREBOOT)
+ return;
+
+ dprintf(3, "init virtio-scsi\n");
+
+ struct pci_device *pci;
+ foreachpci(pci) {
+ if (pci->vendor != PCI_VENDOR_ID_REDHAT_QUMRANET
+ || pci->device != PCI_DEVICE_ID_VIRTIO_SCSI)
+ continue;
+ init_virtio_scsi(pci);
+ }
+}
diff --git a/src/virtio-scsi.h b/src/virtio-scsi.h
new file mode 100644
index 0000000..bbfbf30
--- /dev/null
+++ b/src/virtio-scsi.h
@@ -0,0 +1,47 @@
+#ifndef _VIRTIO_SCSI_H
+#define _VIRTIO_SCSI_H
+
+#define VIRTIO_SCSI_CDB_SIZE 32
+#define VIRTIO_SCSI_SENSE_SIZE 96
+
+struct virtio_scsi_config
+{
+ u32 num_queues;
+ u32 seg_max;
+ u32 max_sectors;
+ u32 cmd_per_lun;
+ u32 event_info_size;
+ u32 sense_size;
+ u32 cdb_size;
+ u16 max_channel;
+ u16 max_target;
+ u32 max_lun;
+} __attribute__((packed));
+
+/* This is the first element of the "out" scatter-gather list. */
+struct virtio_scsi_req_cmd {
+ u8 lun[8];
+ u64 id;
+ u8 task_attr;
+ u8 prio;
+ u8 crn;
+ char cdb[VIRTIO_SCSI_CDB_SIZE];
+};
+
+/* This is the first element of the "in" scatter-gather list. */
+struct virtio_scsi_resp_cmd {
+ u32 sense_len;
+ u32 residual;
+ u16 status_qualifier;
+ u8 status;
+ u8 response;
+ u8 sense[VIRTIO_SCSI_SENSE_SIZE];
+};
+
+#define VIRTIO_SCSI_S_OK 0
+
+struct disk_op_s;
+int virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize);
+void virtio_scsi_setup(void);
+
+#endif /* _VIRTIO_SCSI_H */
--
1.7.7.6