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@redhat.com --- Makefile | 2 +- src/Kconfig | 6 ++ src/block.c | 3 +- src/blockcmd.c | 3 + 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 | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/virtio-scsi.h | 43 +++++++++++ 12 files changed, 292 insertions(+), 3 deletions(-) create mode 100644 src/virtio-scsi.c create mode 100644 src/virtio-scsi.h
diff --git a/Makefile b/Makefile index 91d9b77..a6c87f4 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 f8d245a..8de3503 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 39adcfc..59b982e 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; diff --git a/src/boot.c b/src/boot.c index 119f290..946850d 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/ethernet@5/disk@0,%d,%d + char desc[256], *p; + p = build_pci_path(desc, sizeof(desc), "*", pci); + snprintf(p, desc+sizeof(desc)-p, "/disk@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 f2c6621..6a170fd 100644 --- a/src/disk.c +++ b/src/disk.c @@ -546,7 +546,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..41b2b25 --- /dev/null +++ b/src/virtio-scsi.c @@ -0,0 +1,216 @@ +// Virtio SCSI boot support. +// +// Copyright (C) 2011 Red Hat Inc. +// +// Authors: +// Paolo Bonzini pbonzini@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 = (op->count ? 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_BLK_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(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 ? ret : 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 ) { + if (vq) { + dprintf(1, "fail to find vq for virtio-scsi %x:%x\n", + pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)); + } + goto fail; + } + + struct virtio_scsi_config cfg; + vp_get(ioaddr, 0, &cfg, sizeof(cfg)); + cfg.cdb_size = VIRTIO_SCSI_CDB_SIZE; + cfg.sense_size = VIRTIO_SCSI_SENSE_SIZE; + vp_set(ioaddr, 0, &cfg, sizeof(cfg)); + + 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..9f1dd19 --- /dev/null +++ b/src/virtio-scsi.h @@ -0,0 +1,43 @@ +#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 event_info_size; + u32 sense_size; + u32 cdb_size; +} __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 process_virtio_scsi_op(struct disk_op_s *op); +int virtio_scsi_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize); +void virtio_scsi_setup(void); + +#endif /* _VIRTIO_SCSI_H */