1558 lines
46 KiB
Diff
1558 lines
46 KiB
Diff
diff --git a/Makefile b/Makefile
|
|
index 7d511e8..fb9bfaf 100644
|
|
--- a/Makefile
|
|
+++ b/Makefile
|
|
@@ -12,10 +12,10 @@ OUT=out/
|
|
|
|
# Source files
|
|
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
|
|
+ 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 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 338f51a..8de3503 100644
|
|
--- a/src/Kconfig
|
|
+++ b/src/Kconfig
|
|
@@ -109,10 +109,16 @@ menu "Hardware support"
|
|
Support for AHCI disk code.
|
|
config VIRTIO_BLK
|
|
depends on DRIVES && !COREBOOT
|
|
- bool "VirtIO controllers"
|
|
+ bool "virtio-blk controllers"
|
|
default y
|
|
help
|
|
- Support boot from virtio storage.
|
|
+ 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 f7e7851..e607d67 100644
|
|
--- a/src/block.c
|
|
+++ b/src/block.c
|
|
@@ -11,8 +11,8 @@
|
|
#include "util.h" // dprintf
|
|
#include "ata.h" // process_ata_op
|
|
#include "ahci.h" // process_ahci_op
|
|
-#include "usb-msc.h" // process_usb_op
|
|
-#include "virtio-blk.h" // process_virtio_op
|
|
+#include "virtio-blk.h" // process_virtio_blk_op
|
|
+#include "blockcmd.h" // cdb_*
|
|
|
|
u8 FloppyCount VAR16VISIBLE;
|
|
u8 CDCount;
|
|
@@ -276,6 +276,28 @@ map_floppy_drive(struct drive_s *drive_g)
|
|
* 16bit calling interface
|
|
****************************************************************/
|
|
|
|
+int
|
|
+process_scsi_op(struct disk_op_s *op)
|
|
+{
|
|
+ if (!CONFIG_VIRTIO_SCSI && !CONFIG_USB_MSC)
|
|
+ return 0;
|
|
+ switch (op->command) {
|
|
+ case CMD_READ:
|
|
+ return cdb_read(op);
|
|
+ case CMD_WRITE:
|
|
+ return cdb_write(op);
|
|
+ case CMD_FORMAT:
|
|
+ case CMD_RESET:
|
|
+ case CMD_ISREADY:
|
|
+ case CMD_VERIFY:
|
|
+ case CMD_SEEK:
|
|
+ return DISK_RET_SUCCESS;
|
|
+ default:
|
|
+ op->count = 0;
|
|
+ return DISK_RET_EPARAM;
|
|
+ }
|
|
+}
|
|
+
|
|
// Execute a disk_op request.
|
|
int
|
|
process_op(struct disk_op_s *op)
|
|
@@ -293,12 +315,13 @@ process_op(struct disk_op_s *op)
|
|
return process_ramdisk_op(op);
|
|
case DTYPE_CDEMU:
|
|
return process_cdemu_op(op);
|
|
- case DTYPE_USB:
|
|
- return process_usb_op(op);
|
|
- case DTYPE_VIRTIO:
|
|
- return process_virtio_op(op);
|
|
+ case DTYPE_VIRTIO_BLK:
|
|
+ return process_virtio_blk_op(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;
|
|
return DISK_RET_EPARAM;
|
|
diff --git a/src/blockcmd.c b/src/blockcmd.c
|
|
index c9c6845..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;
|
|
@@ -32,6 +35,128 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
|
|
}
|
|
|
|
int
|
|
+scsi_is_ready(struct disk_op_s *op)
|
|
+{
|
|
+ dprintf(6, "scsi_is_ready (drive=%p)\n", op->drive_g);
|
|
+
|
|
+ /* Retry TEST UNIT READY for 5 seconds unless MEDIUM NOT PRESENT is
|
|
+ * reported by the device. If the device reports "IN PROGRESS",
|
|
+ * 30 seconds is added. */
|
|
+ int in_progress = 0;
|
|
+ u64 end = calc_future_tsc(5000);
|
|
+ for (;;) {
|
|
+ if (check_tsc(end)) {
|
|
+ dprintf(1, "test unit ready failed\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ int ret = cdb_test_unit_ready(op);
|
|
+ if (!ret)
|
|
+ // Success
|
|
+ break;
|
|
+
|
|
+ struct cdbres_request_sense sense;
|
|
+ ret = cdb_get_sense(op, &sense);
|
|
+ if (ret)
|
|
+ // Error - retry.
|
|
+ continue;
|
|
+
|
|
+ // Sense succeeded.
|
|
+ if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */
|
|
+ dprintf(1, "Device reports MEDIUM NOT PRESENT\n");
|
|
+ return -1;
|
|
+ }
|
|
+
|
|
+ if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) {
|
|
+ /* IN PROGRESS OF BECOMING READY */
|
|
+ printf("Waiting for device to detect medium... ");
|
|
+ /* Allow 30 seconds more */
|
|
+ end = calc_future_tsc(30000);
|
|
+ in_progress = 1;
|
|
+ }
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+// Validate drive and find block size and sector count.
|
|
+int
|
|
+scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc)
|
|
+{
|
|
+ if (!CONFIG_USB_MSC && !CONFIG_VIRTIO_SCSI)
|
|
+ return 0;
|
|
+
|
|
+ struct disk_op_s dop;
|
|
+ memset(&dop, 0, sizeof(dop));
|
|
+ dop.drive_g = drive;
|
|
+ struct cdbres_inquiry data;
|
|
+ int ret = cdb_get_inquiry(&dop, &data);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1];
|
|
+ char rev[sizeof(data.rev)+1];
|
|
+ strtcpy(vendor, data.vendor, sizeof(vendor));
|
|
+ nullTrailingSpace(vendor);
|
|
+ strtcpy(product, data.product, sizeof(product));
|
|
+ nullTrailingSpace(product);
|
|
+ strtcpy(rev, data.rev, sizeof(rev));
|
|
+ nullTrailingSpace(rev);
|
|
+ *pdt = data.pdt & 0x1f;
|
|
+ int removable = !!(data.removable & 0x80);
|
|
+ dprintf(1, "%s vendor='%s' product='%s' rev='%s' type=%d removable=%d\n"
|
|
+ , s, vendor, product, rev, *pdt, removable);
|
|
+ drive->removable = removable;
|
|
+
|
|
+ if (*pdt == SCSI_TYPE_CDROM) {
|
|
+ drive->blksize = CDROM_SECTOR_SIZE;
|
|
+ drive->sectors = (u64)-1;
|
|
+
|
|
+ *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]"
|
|
+ , s, vendor, product, rev);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ ret = scsi_is_ready(&dop);
|
|
+ if (ret) {
|
|
+ dprintf(1, "scsi_is_ready returned %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ struct cdbres_read_capacity capdata;
|
|
+ ret = cdb_read_capacity(&dop, &capdata);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ // READ CAPACITY returns the address of the last block.
|
|
+ // We do not bother with READ CAPACITY(16) because BIOS does not support
|
|
+ // 64-bit LBA anyway.
|
|
+ drive->blksize = ntohl(capdata.blksize);
|
|
+ drive->sectors = (u64)ntohl(capdata.sectors) + 1;
|
|
+ dprintf(1, "%s blksize=%d sectors=%d\n"
|
|
+ , s, drive->blksize, (unsigned)drive->sectors);
|
|
+
|
|
+ struct cdbres_mode_sense_geom geomdata;
|
|
+ ret = cdb_mode_sense_geom(&dop, &geomdata);
|
|
+ if (ret == 0) {
|
|
+ u32 cylinders;
|
|
+ cylinders = geomdata.cyl[0] << 16;
|
|
+ cylinders |= geomdata.cyl[1] << 8;
|
|
+ cylinders |= geomdata.cyl[2];
|
|
+ if (cylinders && geomdata.heads &&
|
|
+ drive->sectors <= 0xFFFFFFFFULL &&
|
|
+ ((u32)drive->sectors % (geomdata.heads * cylinders) == 0)) {
|
|
+ drive->pchs.cylinders = cylinders;
|
|
+ drive->pchs.heads = geomdata.heads;
|
|
+ drive->pchs.spt = (u32)drive->sectors
|
|
+ / (geomdata.heads * cylinders);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ *desc = znprintf(MAXDESCSIZE, "%s Drive %s %s %s"
|
|
+ , s, vendor, product, rev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int
|
|
cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data)
|
|
{
|
|
struct cdb_request_sense cmd;
|
|
@@ -56,6 +181,18 @@ cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data)
|
|
return cdb_cmd_data(op, &cmd, sizeof(*data));
|
|
}
|
|
|
|
+// Test unit ready
|
|
+int
|
|
+cdb_test_unit_ready(struct disk_op_s *op)
|
|
+{
|
|
+ struct cdb_request_sense cmd;
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.command = CDB_CMD_TEST_UNIT_READY;
|
|
+ op->count = 0;
|
|
+ op->buf_fl = NULL;
|
|
+ return cdb_cmd_data(op, &cmd, 0);
|
|
+}
|
|
+
|
|
// Request capacity
|
|
int
|
|
cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data)
|
|
@@ -68,6 +205,21 @@ cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data)
|
|
return cdb_cmd_data(op, &cmd, sizeof(*data));
|
|
}
|
|
|
|
+// Mode sense, geometry page.
|
|
+int
|
|
+cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data)
|
|
+{
|
|
+ struct cdb_mode_sense cmd;
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.command = CDB_CMD_MODE_SENSE;
|
|
+ cmd.flags = 8; /* DBD */
|
|
+ cmd.page = MODE_PAGE_HD_GEOMETRY;
|
|
+ cmd.count = htons(sizeof(*data));
|
|
+ op->count = 1;
|
|
+ op->buf_fl = data;
|
|
+ return cdb_cmd_data(op, &cmd, sizeof(*data));
|
|
+}
|
|
+
|
|
// Read sectors.
|
|
int
|
|
cdb_read(struct disk_op_s *op)
|
|
@@ -79,3 +231,15 @@ cdb_read(struct disk_op_s *op)
|
|
cmd.count = htons(op->count);
|
|
return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize));
|
|
}
|
|
+
|
|
+// Write sectors.
|
|
+int
|
|
+cdb_write(struct disk_op_s *op)
|
|
+{
|
|
+ struct cdb_rwdata_10 cmd;
|
|
+ memset(&cmd, 0, sizeof(cmd));
|
|
+ cmd.command = CDB_CMD_WRITE_10;
|
|
+ cmd.lba = htonl(op->lba);
|
|
+ cmd.count = htons(op->count);
|
|
+ return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize));
|
|
+}
|
|
diff --git a/src/blockcmd.h b/src/blockcmd.h
|
|
index 903c435..bace649 100644
|
|
--- a/src/blockcmd.h
|
|
+++ b/src/blockcmd.h
|
|
@@ -32,8 +32,9 @@ struct cdbres_read_capacity {
|
|
u32 blksize;
|
|
} PACKED;
|
|
|
|
-#define CDB_CMD_INQUIRY 0x12
|
|
-#define CDB_CMD_REQUEST_SENSE 0x03
|
|
+#define CDB_CMD_TEST_UNIT_READY 0x00
|
|
+#define CDB_CMD_INQUIRY 0x12
|
|
+#define CDB_CMD_REQUEST_SENSE 0x03
|
|
|
|
struct cdb_request_sense {
|
|
u8 command;
|
|
@@ -56,6 +57,9 @@ struct cdbres_request_sense {
|
|
u32 reserved_0e;
|
|
} PACKED;
|
|
|
|
+#define SCSI_TYPE_DISK 0x00
|
|
+#define SCSI_TYPE_CDROM 0x05
|
|
+
|
|
struct cdbres_inquiry {
|
|
u8 pdt;
|
|
u8 removable;
|
|
@@ -67,11 +71,45 @@ struct cdbres_inquiry {
|
|
char rev[4];
|
|
} PACKED;
|
|
|
|
+#define CDB_CMD_MODE_SENSE 0x5A
|
|
+#define MODE_PAGE_HD_GEOMETRY 0x04
|
|
+
|
|
+struct cdb_mode_sense {
|
|
+ u8 command;
|
|
+ u8 flags;
|
|
+ u8 page;
|
|
+ u32 reserved_03;
|
|
+ u16 count;
|
|
+ u8 reserved_09;
|
|
+ u8 pad[6];
|
|
+} PACKED;
|
|
+
|
|
+struct cdbres_mode_sense_geom {
|
|
+ u8 unused_00[3];
|
|
+ u8 read_only;
|
|
+ u32 unused_04;
|
|
+ u8 page;
|
|
+ u8 length;
|
|
+ u8 cyl[3];
|
|
+ u8 heads;
|
|
+ u8 precomp[3];
|
|
+ u8 reduced[3];
|
|
+ u16 step_rate;
|
|
+ u8 landing[3];
|
|
+ u16 rpm;
|
|
+} PACKED;
|
|
+
|
|
// blockcmd.c
|
|
int cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data);
|
|
int cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data);
|
|
+int cdb_test_unit_ready(struct disk_op_s *op);
|
|
int cdb_read_capacity(struct disk_op_s *op, struct cdbres_read_capacity *data);
|
|
+int cdb_mode_sense_geom(struct disk_op_s *op, struct cdbres_mode_sense_geom *data);
|
|
int cdb_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data);
|
|
int cdb_read(struct disk_op_s *op);
|
|
+int cdb_write(struct disk_op_s *op);
|
|
+
|
|
+int scsi_is_ready(struct disk_op_s *op);
|
|
+int scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc);
|
|
|
|
#endif // blockcmd.h
|
|
diff --git a/src/boot.c b/src/boot.c
|
|
index 93928d3..c0991cd 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/cdrom.c b/src/cdrom.c
|
|
index 6351fec..170ffc4 100644
|
|
--- a/src/cdrom.c
|
|
+++ b/src/cdrom.c
|
|
@@ -184,60 +184,6 @@ cdemu_134b(struct bregs *regs)
|
|
* CD booting
|
|
****************************************************************/
|
|
|
|
-static int
|
|
-atapi_is_ready(struct disk_op_s *op)
|
|
-{
|
|
- dprintf(6, "atapi_is_ready (drive=%p)\n", op->drive_g);
|
|
-
|
|
- /* Retry READ CAPACITY for 5 seconds unless MEDIUM NOT PRESENT is
|
|
- * reported by the device. If the device reports "IN PROGRESS",
|
|
- * 30 seconds is added. */
|
|
- struct cdbres_read_capacity info;
|
|
- int in_progress = 0;
|
|
- u64 end = calc_future_tsc(5000);
|
|
- for (;;) {
|
|
- if (check_tsc(end)) {
|
|
- dprintf(1, "read capacity failed\n");
|
|
- return -1;
|
|
- }
|
|
-
|
|
- int ret = cdb_read_capacity(op, &info);
|
|
- if (!ret)
|
|
- // Success
|
|
- break;
|
|
-
|
|
- struct cdbres_request_sense sense;
|
|
- ret = cdb_get_sense(op, &sense);
|
|
- if (ret)
|
|
- // Error - retry.
|
|
- continue;
|
|
-
|
|
- // Sense succeeded.
|
|
- if (sense.asc == 0x3a) { /* MEDIUM NOT PRESENT */
|
|
- dprintf(1, "Device reports MEDIUM NOT PRESENT\n");
|
|
- return -1;
|
|
- }
|
|
-
|
|
- if (sense.asc == 0x04 && sense.ascq == 0x01 && !in_progress) {
|
|
- /* IN PROGRESS OF BECOMING READY */
|
|
- printf("Waiting for device to detect medium... ");
|
|
- /* Allow 30 seconds more */
|
|
- end = calc_future_tsc(30000);
|
|
- in_progress = 1;
|
|
- }
|
|
- }
|
|
-
|
|
- u32 blksize = ntohl(info.blksize), sectors = ntohl(info.sectors);
|
|
- if (blksize != GET_GLOBAL(op->drive_g->blksize)) {
|
|
- printf("Unsupported sector size %u\n", blksize);
|
|
- return -1;
|
|
- }
|
|
-
|
|
- dprintf(6, "sectors=%u\n", sectors);
|
|
- printf("%dMB medium detected\n", sectors>>(20-11));
|
|
- return 0;
|
|
-}
|
|
-
|
|
int
|
|
cdrom_boot(struct drive_s *drive_g)
|
|
{
|
|
@@ -248,9 +194,9 @@ cdrom_boot(struct drive_s *drive_g)
|
|
if (!dop.drive_g || cdid < 0)
|
|
return 1;
|
|
|
|
- int ret = atapi_is_ready(&dop);
|
|
+ int ret = scsi_is_ready(&dop);
|
|
if (ret)
|
|
- dprintf(1, "atapi_is_ready returned %d\n", ret);
|
|
+ dprintf(1, "scsi_is_ready returned %d\n", ret);
|
|
|
|
// Read the Boot Record Volume Descriptor
|
|
u8 buffer[2048];
|
|
diff --git a/src/disk.c b/src/disk.c
|
|
index 8f7c61f..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)) {
|
|
+ (type != DTYPE_ATA && type != DTYPE_ATAPI &&
|
|
+ type != DTYPE_VIRTIO_BLK && type != DTYPE_VIRTIO_SCSI)) {
|
|
disk_ret(regs, DISK_RET_SUCCESS);
|
|
return;
|
|
}
|
|
@@ -651,7 +652,7 @@ disk_1348(struct bregs *regs, struct drive_s *drive_g)
|
|
SET_INT13DPT(regs, iface_path, iobase1);
|
|
}
|
|
|
|
- if (type != DTYPE_VIRTIO) {
|
|
+ if (type != DTYPE_VIRTIO_BLK) {
|
|
SET_INT13DPT(regs, iface_type[0], 'A');
|
|
SET_INT13DPT(regs, iface_type[1], 'T');
|
|
SET_INT13DPT(regs, iface_type[2], 'A');
|
|
diff --git a/src/disk.h b/src/disk.h
|
|
index ac33518..d344399 100644
|
|
--- a/src/disk.h
|
|
+++ b/src/disk.h
|
|
@@ -198,15 +198,16 @@ struct drive_s {
|
|
#define DISK_SECTOR_SIZE 512
|
|
#define CDROM_SECTOR_SIZE 2048
|
|
|
|
-#define DTYPE_NONE 0x00
|
|
-#define DTYPE_FLOPPY 0x01
|
|
-#define DTYPE_ATA 0x02
|
|
-#define DTYPE_ATAPI 0x03
|
|
-#define DTYPE_RAMDISK 0x04
|
|
-#define DTYPE_CDEMU 0x05
|
|
-#define DTYPE_USB 0x06
|
|
-#define DTYPE_VIRTIO 0x07
|
|
-#define DTYPE_AHCI 0x08
|
|
+#define DTYPE_NONE 0x00
|
|
+#define DTYPE_FLOPPY 0x01
|
|
+#define DTYPE_ATA 0x02
|
|
+#define DTYPE_ATAPI 0x03
|
|
+#define DTYPE_RAMDISK 0x04
|
|
+#define DTYPE_CDEMU 0x05
|
|
+#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/usb-ehci.c b/src/usb-ehci.c
|
|
index a60c607..9bdd638 100644
|
|
--- a/src/usb-ehci.c
|
|
+++ b/src/usb-ehci.c
|
|
@@ -303,22 +303,12 @@ ehci_init(struct pci_device *pci, int busid, struct pci_device *comppci)
|
|
* End point communication
|
|
****************************************************************/
|
|
|
|
-static int
|
|
-ehci_wait_qh(struct usb_ehci_s *cntl, struct ehci_qh *qh)
|
|
-{
|
|
- // XXX - 500ms just a guess
|
|
- u64 end = calc_future_tsc(500);
|
|
- for (;;) {
|
|
- if (qh->qtd_next & EHCI_PTR_TERM)
|
|
- // XXX - confirm
|
|
- return 0;
|
|
- if (check_tsc(end)) {
|
|
- warn_timeout();
|
|
- return -1;
|
|
- }
|
|
- yield();
|
|
- }
|
|
-}
|
|
+struct ehci_pipe {
|
|
+ struct ehci_qh qh;
|
|
+ struct ehci_qtd *next_td, *tds;
|
|
+ void *data;
|
|
+ struct usb_pipe pipe;
|
|
+};
|
|
|
|
// Wait for next USB async frame to start - for ensuring safe memory release.
|
|
static void
|
|
@@ -362,12 +352,46 @@ ehci_waittick(struct usb_ehci_s *cntl)
|
|
writel(&cntl->regs->usbsts, STS_IAA);
|
|
}
|
|
|
|
-struct ehci_pipe {
|
|
- struct ehci_qh qh;
|
|
- struct ehci_qtd *next_td, *tds;
|
|
- void *data;
|
|
- struct usb_pipe pipe;
|
|
-};
|
|
+static void
|
|
+ehci_reset_pipe(struct ehci_pipe *pipe)
|
|
+{
|
|
+ SET_FLATPTR(pipe->qh.qtd_next, EHCI_PTR_TERM);
|
|
+ SET_FLATPTR(pipe->qh.alt_next, EHCI_PTR_TERM);
|
|
+ barrier();
|
|
+ SET_FLATPTR(pipe->qh.token, GET_FLATPTR(pipe->qh.token) & QTD_TOGGLE);
|
|
+}
|
|
+
|
|
+static int
|
|
+ehci_wait_td(struct ehci_pipe *pipe, struct ehci_qtd *td, int timeout)
|
|
+{
|
|
+ u64 end = calc_future_tsc(timeout);
|
|
+ u32 status;
|
|
+ for (;;) {
|
|
+ status = td->token;
|
|
+ if (!(status & QTD_STS_ACTIVE))
|
|
+ break;
|
|
+ if (check_tsc(end)) {
|
|
+ u32 cur = GET_FLATPTR(pipe->qh.current);
|
|
+ u32 tok = GET_FLATPTR(pipe->qh.token);
|
|
+ u32 next = GET_FLATPTR(pipe->qh.qtd_next);
|
|
+ warn_timeout();
|
|
+ dprintf(1, "ehci pipe=%p cur=%08x tok=%08x next=%x td=%p status=%x\n"
|
|
+ , pipe, cur, tok, next, td, status);
|
|
+ ehci_reset_pipe(pipe);
|
|
+ struct usb_ehci_s *cntl = container_of(
|
|
+ GET_FLATPTR(pipe->pipe.cntl), struct usb_ehci_s, usb);
|
|
+ ehci_waittick(cntl);
|
|
+ return -1;
|
|
+ }
|
|
+ yield();
|
|
+ }
|
|
+ if (status & QTD_STS_HALT) {
|
|
+ dprintf(1, "ehci_wait_td error - status=%x\n", status);
|
|
+ ehci_reset_pipe(pipe);
|
|
+ return -2;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
|
|
void
|
|
ehci_free_pipe(struct usb_pipe *p)
|
|
@@ -416,7 +440,6 @@ ehci_alloc_control_pipe(struct usb_pipe *dummy)
|
|
memset(pipe, 0, sizeof(*pipe));
|
|
memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe));
|
|
pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
|
|
- pipe->qh.token = QTD_STS_HALT;
|
|
|
|
// Add queue head to controller list.
|
|
struct ehci_qh *async_qh = cntl->async_qh;
|
|
@@ -455,15 +478,14 @@ ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
|
|
ASSERT32FLAT();
|
|
if (! CONFIG_USB_EHCI)
|
|
return -1;
|
|
- dprintf(5, "ehci_control %p\n", p);
|
|
+ dprintf(5, "ehci_control %p (dir=%d cmd=%d data=%d)\n"
|
|
+ , p, dir, cmdsize, datasize);
|
|
if (datasize > 4*4096 || cmdsize > 4*4096) {
|
|
// XXX - should support larger sizes.
|
|
warn_noalloc();
|
|
return -1;
|
|
}
|
|
struct ehci_pipe *pipe = container_of(p, struct ehci_pipe, pipe);
|
|
- struct usb_ehci_s *cntl = container_of(
|
|
- pipe->pipe.cntl, struct usb_ehci_s, usb);
|
|
|
|
u16 maxpacket = pipe->pipe.maxpacket;
|
|
int speed = pipe->pipe.speed;
|
|
@@ -513,14 +535,12 @@ ehci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
|
|
// Transfer data
|
|
barrier();
|
|
pipe->qh.qtd_next = (u32)tds;
|
|
- barrier();
|
|
- pipe->qh.token = 0;
|
|
- int ret = ehci_wait_qh(cntl, &pipe->qh);
|
|
- pipe->qh.token = QTD_STS_HALT;
|
|
- if (ret) {
|
|
- pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
|
|
- // XXX - halt qh?
|
|
- ehci_waittick(cntl);
|
|
+ int i, ret=0;
|
|
+ for (i=0; i<3; i++) {
|
|
+ struct ehci_qtd *td = &tds[i];
|
|
+ ret = ehci_wait_td(pipe, td, 500);
|
|
+ if (ret)
|
|
+ break;
|
|
}
|
|
free(tds);
|
|
return ret;
|
|
@@ -545,7 +565,6 @@ ehci_alloc_bulk_pipe(struct usb_pipe *dummy)
|
|
memset(pipe, 0, sizeof(*pipe));
|
|
memcpy(&pipe->pipe, dummy, sizeof(pipe->pipe));
|
|
pipe->qh.qtd_next = pipe->qh.alt_next = EHCI_PTR_TERM;
|
|
- pipe->qh.token = QTD_STS_HALT;
|
|
|
|
// Add queue head to controller list.
|
|
struct ehci_qh *async_qh = cntl->async_qh;
|
|
@@ -555,28 +574,6 @@ ehci_alloc_bulk_pipe(struct usb_pipe *dummy)
|
|
return &pipe->pipe;
|
|
}
|
|
|
|
-static int
|
|
-ehci_wait_td(struct ehci_qtd *td)
|
|
-{
|
|
- u64 end = calc_future_tsc(5000); // XXX - lookup real time.
|
|
- u32 status;
|
|
- for (;;) {
|
|
- status = td->token;
|
|
- if (!(status & QTD_STS_ACTIVE))
|
|
- break;
|
|
- if (check_tsc(end)) {
|
|
- warn_timeout();
|
|
- return -1;
|
|
- }
|
|
- yield();
|
|
- }
|
|
- if (status & QTD_STS_HALT) {
|
|
- dprintf(1, "ehci_wait_td error - status=%x\n", status);
|
|
- return -2;
|
|
- }
|
|
- return 0;
|
|
-}
|
|
-
|
|
#define STACKQTDS 4
|
|
|
|
int
|
|
@@ -607,15 +604,13 @@ ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
|
|
| (GET_FLATPTR(pipe->pipe.tt_devaddr) << QH_HUBADDR_SHIFT)));
|
|
barrier();
|
|
SET_FLATPTR(pipe->qh.qtd_next, (u32)MAKE_FLATPTR(GET_SEG(SS), tds));
|
|
- barrier();
|
|
- SET_FLATPTR(pipe->qh.token, GET_FLATPTR(pipe->qh.token) & QTD_TOGGLE);
|
|
|
|
int tdpos = 0;
|
|
while (datasize) {
|
|
struct ehci_qtd *td = &tds[tdpos++ % STACKQTDS];
|
|
- int ret = ehci_wait_td(td);
|
|
+ int ret = ehci_wait_td(pipe, td, 5000);
|
|
if (ret)
|
|
- goto fail;
|
|
+ return -1;
|
|
|
|
struct ehci_qtd *nexttd_fl = MAKE_FLATPTR(GET_SEG(SS)
|
|
, &tds[tdpos % STACKQTDS]);
|
|
@@ -633,21 +628,12 @@ ehci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
|
|
int i;
|
|
for (i=0; i<STACKQTDS; i++) {
|
|
struct ehci_qtd *td = &tds[tdpos++ % STACKQTDS];
|
|
- int ret = ehci_wait_td(td);
|
|
+ int ret = ehci_wait_td(pipe, td, 5000);
|
|
if (ret)
|
|
- goto fail;
|
|
+ return -1;
|
|
}
|
|
|
|
return 0;
|
|
-fail:
|
|
- dprintf(1, "ehci_send_bulk failed\n");
|
|
- SET_FLATPTR(pipe->qh.qtd_next, EHCI_PTR_TERM);
|
|
- SET_FLATPTR(pipe->qh.alt_next, EHCI_PTR_TERM);
|
|
- // XXX - halt qh?
|
|
- struct usb_ehci_s *cntl = container_of(
|
|
- GET_FLATPTR(pipe->pipe.cntl), struct usb_ehci_s, usb);
|
|
- ehci_waittick(cntl);
|
|
- return -1;
|
|
}
|
|
|
|
struct usb_pipe *
|
|
diff --git a/src/usb-msc.c b/src/usb-msc.c
|
|
index 13ef93e..4a09972 100644
|
|
--- a/src/usb-msc.c
|
|
+++ b/src/usb-msc.c
|
|
@@ -46,6 +46,17 @@ struct csw_s {
|
|
u8 bCSWStatus;
|
|
} PACKED;
|
|
|
|
+static int
|
|
+usb_msc_send(struct usbdrive_s *udrive_g, int dir, void *buf, u32 bytes)
|
|
+{
|
|
+ struct usb_pipe *pipe;
|
|
+ if (dir == USB_DIR_OUT)
|
|
+ pipe = GET_GLOBAL(udrive_g->bulkout);
|
|
+ else
|
|
+ pipe = GET_GLOBAL(udrive_g->bulkin);
|
|
+ return usb_send_bulk(pipe, dir, buf, bytes);
|
|
+}
|
|
+
|
|
// Low-level usb command transmit function.
|
|
int
|
|
usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
|
|
@@ -57,35 +68,35 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
|
|
, op->drive_g, 0, op->count, blocksize, op->buf_fl);
|
|
struct usbdrive_s *udrive_g = container_of(
|
|
op->drive_g, struct usbdrive_s, drive);
|
|
- struct usb_pipe *bulkin = GET_GLOBAL(udrive_g->bulkin);
|
|
- struct usb_pipe *bulkout = GET_GLOBAL(udrive_g->bulkout);
|
|
|
|
// Setup command block wrapper.
|
|
u32 bytes = blocksize * op->count;
|
|
struct cbw_s cbw;
|
|
memset(&cbw, 0, sizeof(cbw));
|
|
+ memcpy(cbw.CBWCB, cdbcmd, USB_CDB_SIZE);
|
|
cbw.dCBWSignature = CBW_SIGNATURE;
|
|
cbw.dCBWTag = 999; // XXX
|
|
cbw.dCBWDataTransferLength = bytes;
|
|
- cbw.bmCBWFlags = USB_DIR_IN; // XXX
|
|
+ cbw.bmCBWFlags = (cbw.CBWCB[0] == CDB_CMD_WRITE_10) ? USB_DIR_OUT : USB_DIR_IN;
|
|
cbw.bCBWLUN = 0; // XXX
|
|
cbw.bCBWCBLength = USB_CDB_SIZE;
|
|
- memcpy(cbw.CBWCB, cdbcmd, USB_CDB_SIZE);
|
|
|
|
// Transfer cbw to device.
|
|
- int ret = usb_send_bulk(bulkout, USB_DIR_OUT
|
|
+ int ret = usb_msc_send(udrive_g, USB_DIR_OUT
|
|
, MAKE_FLATPTR(GET_SEG(SS), &cbw), sizeof(cbw));
|
|
if (ret)
|
|
goto fail;
|
|
|
|
- // Transfer data from device.
|
|
- ret = usb_send_bulk(bulkin, USB_DIR_IN, op->buf_fl, bytes);
|
|
- if (ret)
|
|
- goto fail;
|
|
+ // Transfer data to/from device.
|
|
+ if (bytes) {
|
|
+ ret = usb_msc_send(udrive_g, cbw.bmCBWFlags, op->buf_fl, bytes);
|
|
+ if (ret)
|
|
+ goto fail;
|
|
+ }
|
|
|
|
// Transfer csw info.
|
|
struct csw_s csw;
|
|
- ret = usb_send_bulk(bulkin, USB_DIR_IN
|
|
+ ret = usb_msc_send(udrive_g, USB_DIR_IN
|
|
, MAKE_FLATPTR(GET_SEG(SS), &csw), sizeof(csw));
|
|
if (ret)
|
|
goto fail;
|
|
@@ -95,7 +106,8 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize)
|
|
if (csw.bCSWStatus == 2)
|
|
goto fail;
|
|
|
|
- op->count -= csw.dCSWDataResidue / blocksize;
|
|
+ if (blocksize)
|
|
+ op->count -= csw.dCSWDataResidue / blocksize;
|
|
return DISK_RET_EBADTRACK;
|
|
|
|
fail:
|
|
@@ -107,75 +119,33 @@ fail:
|
|
|
|
|
|
/****************************************************************
|
|
- * Drive ops
|
|
- ****************************************************************/
|
|
-
|
|
-// 16bit command demuxer for ATAPI cdroms.
|
|
-int
|
|
-process_usb_op(struct disk_op_s *op)
|
|
-{
|
|
- if (!CONFIG_USB_MSC)
|
|
- return 0;
|
|
- switch (op->command) {
|
|
- case CMD_READ:
|
|
- return cdb_read(op);
|
|
- case CMD_FORMAT:
|
|
- case CMD_WRITE:
|
|
- return DISK_RET_EWRITEPROTECT;
|
|
- case CMD_RESET:
|
|
- case CMD_ISREADY:
|
|
- case CMD_VERIFY:
|
|
- case CMD_SEEK:
|
|
- return DISK_RET_SUCCESS;
|
|
- default:
|
|
- op->count = 0;
|
|
- return DISK_RET_EPARAM;
|
|
- }
|
|
-}
|
|
-
|
|
-
|
|
-/****************************************************************
|
|
* Setup
|
|
****************************************************************/
|
|
|
|
static int
|
|
-setup_drive_cdrom(struct disk_op_s *op, char *desc)
|
|
+setup_drive_cdrom(struct drive_s *drive, char *desc)
|
|
{
|
|
- op->drive_g->blksize = CDROM_SECTOR_SIZE;
|
|
- op->drive_g->sectors = (u64)-1;
|
|
+ drive->sectors = (u64)-1;
|
|
struct usb_pipe *pipe = container_of(
|
|
- op->drive_g, struct usbdrive_s, drive)->bulkout;
|
|
+ drive, struct usbdrive_s, drive)->bulkout;
|
|
int prio = bootprio_find_usb(pipe->cntl->pci, pipe->path);
|
|
- boot_add_cd(op->drive_g, desc, prio);
|
|
+ boot_add_cd(drive, desc, prio);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
-setup_drive_hd(struct disk_op_s *op, char *desc)
|
|
+setup_drive_hd(struct drive_s *drive, char *desc)
|
|
{
|
|
- struct cdbres_read_capacity info;
|
|
- int ret = cdb_read_capacity(op, &info);
|
|
- if (ret)
|
|
- return ret;
|
|
- // XXX - retry for some timeout?
|
|
-
|
|
- u32 blksize = ntohl(info.blksize), sectors = ntohl(info.sectors);
|
|
- if (blksize != DISK_SECTOR_SIZE) {
|
|
- if (blksize == CDROM_SECTOR_SIZE)
|
|
- return setup_drive_cdrom(op, desc);
|
|
- dprintf(1, "Unsupported USB MSC block size %d\n", blksize);
|
|
+ if (drive->blksize != DISK_SECTOR_SIZE) {
|
|
+ dprintf(1, "Unsupported USB MSC block size %d\n", drive->blksize);
|
|
return -1;
|
|
}
|
|
- op->drive_g->blksize = blksize;
|
|
- op->drive_g->sectors = sectors;
|
|
- dprintf(1, "USB MSC blksize=%d sectors=%d\n", blksize, sectors);
|
|
|
|
// Register with bcv system.
|
|
struct usb_pipe *pipe = container_of(
|
|
- op->drive_g, struct usbdrive_s, drive)->bulkout;
|
|
+ drive, struct usbdrive_s, drive)->bulkout;
|
|
int prio = bootprio_find_usb(pipe->cntl->pci, pipe->path);
|
|
- boot_add_hd(op->drive_g, desc, prio);
|
|
-
|
|
+ boot_add_hd(drive, desc, prio);
|
|
return 0;
|
|
}
|
|
|
|
@@ -218,37 +188,17 @@ usb_msc_init(struct usb_pipe *pipe
|
|
if (!udrive_g->bulkin || !udrive_g->bulkout)
|
|
goto fail;
|
|
|
|
- // Validate drive and find block size and sector count.
|
|
- struct disk_op_s dop;
|
|
- memset(&dop, 0, sizeof(dop));
|
|
- dop.drive_g = &udrive_g->drive;
|
|
- struct cdbres_inquiry data;
|
|
- int ret = cdb_get_inquiry(&dop, &data);
|
|
+ int ret, pdt;
|
|
+ char *desc = NULL;
|
|
+ ret = scsi_init_drive(&udrive_g->drive, "USB MSC", &pdt, &desc);
|
|
if (ret)
|
|
goto fail;
|
|
- char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1];
|
|
- char rev[sizeof(data.rev)+1];
|
|
- strtcpy(vendor, data.vendor, sizeof(vendor));
|
|
- nullTrailingSpace(vendor);
|
|
- strtcpy(product, data.product, sizeof(product));
|
|
- nullTrailingSpace(product);
|
|
- strtcpy(rev, data.rev, sizeof(rev));
|
|
- nullTrailingSpace(rev);
|
|
- int pdt = data.pdt & 0x1f;
|
|
- int removable = !!(data.removable & 0x80);
|
|
- dprintf(1, "USB MSC vendor='%s' product='%s' rev='%s' type=%d removable=%d\n"
|
|
- , vendor, product, rev, pdt, removable);
|
|
- udrive_g->drive.removable = removable;
|
|
-
|
|
- if (pdt == USB_MSC_TYPE_CDROM) {
|
|
- char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]"
|
|
- , vendor, product, rev);
|
|
- ret = setup_drive_cdrom(&dop, desc);
|
|
- } else {
|
|
- char *desc = znprintf(MAXDESCSIZE, "USB Drive %s %s %s"
|
|
- , vendor, product, rev);
|
|
- ret = setup_drive_hd(&dop, desc);
|
|
- }
|
|
+
|
|
+ if (pdt == SCSI_TYPE_CDROM)
|
|
+ ret = setup_drive_cdrom(&udrive_g->drive, desc);
|
|
+ else
|
|
+ ret = setup_drive_hd(&udrive_g->drive, desc);
|
|
+
|
|
if (ret)
|
|
goto fail;
|
|
|
|
diff --git a/src/usb-msc.h b/src/usb-msc.h
|
|
index 71adb20..a8686a3 100644
|
|
--- a/src/usb-msc.h
|
|
+++ b/src/usb-msc.h
|
|
@@ -21,7 +21,4 @@ int process_usb_op(struct disk_op_s *op);
|
|
|
|
#define US_PR_BULK 0x50
|
|
|
|
-#define USB_MSC_TYPE_DISK 0x00
|
|
-#define USB_MSC_TYPE_CDROM 0x05
|
|
-
|
|
#endif // ush-msc.h
|
|
diff --git a/src/usb-uhci.c b/src/usb-uhci.c
|
|
index f3680d3..a78dbca 100644
|
|
--- a/src/usb-uhci.c
|
|
+++ b/src/usb-uhci.c
|
|
@@ -212,26 +212,13 @@ uhci_init(struct pci_device *pci, int busid)
|
|
* End point communication
|
|
****************************************************************/
|
|
|
|
-static int
|
|
-wait_qh(struct usb_uhci_s *cntl, struct uhci_qh *qh)
|
|
-{
|
|
- // XXX - 500ms just a guess
|
|
- u64 end = calc_future_tsc(500);
|
|
- for (;;) {
|
|
- if (qh->element & UHCI_PTR_TERM)
|
|
- return 0;
|
|
- if (check_tsc(end)) {
|
|
- warn_timeout();
|
|
- struct uhci_td *td = (void*)(qh->element & ~UHCI_PTR_BITS);
|
|
- dprintf(1, "Timeout on wait_qh %p (td=%p s=%x c=%x/%x)\n"
|
|
- , qh, td, td->status
|
|
- , inw(cntl->iobase + USBCMD)
|
|
- , inw(cntl->iobase + USBSTS));
|
|
- return -1;
|
|
- }
|
|
- yield();
|
|
- }
|
|
-}
|
|
+struct uhci_pipe {
|
|
+ struct uhci_qh qh;
|
|
+ struct uhci_td *next_td;
|
|
+ struct usb_pipe pipe;
|
|
+ u16 iobase;
|
|
+ u8 toggle;
|
|
+};
|
|
|
|
// Wait for next USB frame to start - for ensuring safe memory release.
|
|
static void
|
|
@@ -251,13 +238,29 @@ uhci_waittick(u16 iobase)
|
|
}
|
|
}
|
|
|
|
-struct uhci_pipe {
|
|
- struct uhci_qh qh;
|
|
- struct uhci_td *next_td;
|
|
- struct usb_pipe pipe;
|
|
- u16 iobase;
|
|
- u8 toggle;
|
|
-};
|
|
+static int
|
|
+wait_pipe(struct uhci_pipe *pipe, int timeout)
|
|
+{
|
|
+ u64 end = calc_future_tsc(timeout);
|
|
+ for (;;) {
|
|
+ u32 el_link = GET_FLATPTR(pipe->qh.element);
|
|
+ if (el_link & UHCI_PTR_TERM)
|
|
+ return 0;
|
|
+ if (check_tsc(end)) {
|
|
+ warn_timeout();
|
|
+ u16 iobase = GET_FLATPTR(pipe->iobase);
|
|
+ struct uhci_td *td = (void*)(el_link & ~UHCI_PTR_BITS);
|
|
+ dprintf(1, "Timeout on wait_pipe %p (td=%p s=%x c=%x/%x)\n"
|
|
+ , pipe, (void*)el_link, GET_FLATPTR(td->status)
|
|
+ , inw(iobase + USBCMD)
|
|
+ , inw(iobase + USBSTS));
|
|
+ SET_FLATPTR(pipe->qh.element, UHCI_PTR_TERM);
|
|
+ uhci_waittick(iobase);
|
|
+ return -1;
|
|
+ }
|
|
+ yield();
|
|
+ }
|
|
+}
|
|
|
|
void
|
|
uhci_free_pipe(struct usb_pipe *p)
|
|
@@ -331,8 +334,6 @@ uhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
|
|
return -1;
|
|
dprintf(5, "uhci_control %p\n", p);
|
|
struct uhci_pipe *pipe = container_of(p, struct uhci_pipe, pipe);
|
|
- struct usb_uhci_s *cntl = container_of(
|
|
- pipe->pipe.cntl, struct usb_uhci_s, usb);
|
|
|
|
int maxpacket = pipe->pipe.maxpacket;
|
|
int lowspeed = pipe->pipe.speed;
|
|
@@ -376,11 +377,7 @@ uhci_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
|
|
// Transfer data
|
|
barrier();
|
|
pipe->qh.element = (u32)&tds[0];
|
|
- int ret = wait_qh(cntl, &pipe->qh);
|
|
- if (ret) {
|
|
- pipe->qh.element = UHCI_PTR_TERM;
|
|
- uhci_waittick(pipe->iobase);
|
|
- }
|
|
+ int ret = wait_pipe(pipe, 500);
|
|
free(tds);
|
|
return ret;
|
|
}
|
|
@@ -487,16 +484,8 @@ uhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
|
|
data += transfer;
|
|
datasize -= transfer;
|
|
}
|
|
- int i;
|
|
- for (i=0; i<STACKTDS; i++) {
|
|
- struct uhci_td *td = &tds[tdpos++ % STACKTDS];
|
|
- int ret = wait_td(td);
|
|
- if (ret)
|
|
- goto fail;
|
|
- }
|
|
-
|
|
SET_FLATPTR(pipe->toggle, !!toggle);
|
|
- return 0;
|
|
+ return wait_pipe(pipe, 5000);
|
|
fail:
|
|
dprintf(1, "uhci_send_bulk failed\n");
|
|
SET_FLATPTR(pipe->qh.element, UHCI_PTR_TERM);
|
|
diff --git a/src/virtio-blk.c b/src/virtio-blk.c
|
|
index b1274fc..b869189 100644
|
|
--- a/src/virtio-blk.c
|
|
+++ b/src/virtio-blk.c
|
|
@@ -75,7 +75,7 @@ virtio_blk_op(struct disk_op_s *op, int write)
|
|
}
|
|
|
|
int
|
|
-process_virtio_op(struct disk_op_s *op)
|
|
+process_virtio_blk_op(struct disk_op_s *op)
|
|
{
|
|
if (! CONFIG_VIRTIO_BLK || CONFIG_COREBOOT)
|
|
return 0;
|
|
@@ -103,27 +103,17 @@ init_virtio_blk(struct pci_device *pci)
|
|
dprintf(1, "found virtio-blk at %x:%x\n", pci_bdf_to_bus(bdf),
|
|
pci_bdf_to_dev(bdf));
|
|
struct virtiodrive_s *vdrive_g = malloc_fseg(sizeof(*vdrive_g));
|
|
- struct vring_virtqueue *vq = memalign_low(PAGE_SIZE, sizeof(*vq));
|
|
- if (!vdrive_g || !vq) {
|
|
+ if (!vdrive_g) {
|
|
warn_noalloc();
|
|
- goto fail;
|
|
+ return;
|
|
}
|
|
memset(vdrive_g, 0, sizeof(*vdrive_g));
|
|
- memset(vq, 0, sizeof(*vq));
|
|
- vdrive_g->drive.type = DTYPE_VIRTIO;
|
|
+ vdrive_g->drive.type = DTYPE_VIRTIO_BLK;
|
|
vdrive_g->drive.cntl_id = bdf;
|
|
- vdrive_g->vq = vq;
|
|
-
|
|
- u16 ioaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0) &
|
|
- PCI_BASE_ADDRESS_IO_MASK;
|
|
|
|
+ u16 ioaddr = vp_init_simple(bdf);
|
|
vdrive_g->ioaddr = ioaddr;
|
|
-
|
|
- vp_reset(ioaddr);
|
|
- vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
|
|
- VIRTIO_CONFIG_S_DRIVER );
|
|
-
|
|
- if (vp_find_vq(ioaddr, 0, vdrive_g->vq) < 0 ) {
|
|
+ if (vp_find_vq(ioaddr, 0, &vdrive_g->vq) < 0 ) {
|
|
dprintf(1, "fail to find vq for virtio-blk %x:%x\n",
|
|
pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf));
|
|
goto fail;
|
|
@@ -161,8 +151,8 @@ init_virtio_blk(struct pci_device *pci)
|
|
return;
|
|
|
|
fail:
|
|
+ free(vdrive_g->vq);
|
|
free(vdrive_g);
|
|
- free(vq);
|
|
}
|
|
|
|
void
|
|
diff --git a/src/virtio-blk.h b/src/virtio-blk.h
|
|
index 7243704..0825f09 100644
|
|
--- a/src/virtio-blk.h
|
|
+++ b/src/virtio-blk.h
|
|
@@ -37,7 +37,7 @@ struct virtio_blk_outhdr {
|
|
#define VIRTIO_BLK_S_UNSUPP 2
|
|
|
|
struct disk_op_s;
|
|
-int process_virtio_op(struct disk_op_s *op);
|
|
+int process_virtio_blk_op(struct disk_op_s *op);
|
|
void virtio_blk_setup(void);
|
|
|
|
#endif /* _VIRTIO_BLK_H */
|
|
diff --git a/src/virtio-pci.c b/src/virtio-pci.c
|
|
index db19e97..7e0c1a5 100644
|
|
--- a/src/virtio-pci.c
|
|
+++ b/src/virtio-pci.c
|
|
@@ -21,12 +21,18 @@
|
|
#include "util.h" // dprintf
|
|
|
|
int vp_find_vq(unsigned int ioaddr, int queue_index,
|
|
- struct vring_virtqueue *vq)
|
|
+ struct vring_virtqueue **p_vq)
|
|
{
|
|
- struct vring * vr = &vq->vring;
|
|
u16 num;
|
|
|
|
ASSERT32FLAT();
|
|
+ struct vring_virtqueue *vq = *p_vq = memalign_low(PAGE_SIZE, sizeof(*vq));
|
|
+ if (!vq) {
|
|
+ warn_noalloc();
|
|
+ goto fail;
|
|
+ }
|
|
+ memset(vq, 0, sizeof(*vq));
|
|
+
|
|
/* select the queue */
|
|
|
|
outw(queue_index, ioaddr + VIRTIO_PCI_QUEUE_SEL);
|
|
@@ -36,25 +42,26 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
|
|
num = inw(ioaddr + VIRTIO_PCI_QUEUE_NUM);
|
|
if (!num) {
|
|
dprintf(1, "ERROR: queue size is 0\n");
|
|
- return -1;
|
|
+ goto fail;
|
|
}
|
|
|
|
if (num > MAX_QUEUE_NUM) {
|
|
dprintf(1, "ERROR: queue size %d > %d\n", num, MAX_QUEUE_NUM);
|
|
- return -1;
|
|
+ goto fail;
|
|
}
|
|
|
|
/* check if the queue is already active */
|
|
|
|
if (inl(ioaddr + VIRTIO_PCI_QUEUE_PFN)) {
|
|
dprintf(1, "ERROR: queue already active\n");
|
|
- return -1;
|
|
+ goto fail;
|
|
}
|
|
|
|
vq->queue_index = queue_index;
|
|
|
|
/* initialize the queue */
|
|
|
|
+ struct vring * vr = &vq->vring;
|
|
vring_init(vr, num, (unsigned char*)&vq->queue);
|
|
|
|
/* activate the queue
|
|
@@ -66,4 +73,20 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
|
|
ioaddr + VIRTIO_PCI_QUEUE_PFN);
|
|
|
|
return num;
|
|
+
|
|
+fail:
|
|
+ free(vq);
|
|
+ *p_vq = NULL;
|
|
+ return -1;
|
|
+}
|
|
+
|
|
+u16 vp_init_simple(u16 bdf)
|
|
+{
|
|
+ u16 ioaddr = pci_config_readl(bdf, PCI_BASE_ADDRESS_0) &
|
|
+ PCI_BASE_ADDRESS_IO_MASK;
|
|
+
|
|
+ vp_reset(ioaddr);
|
|
+ vp_set_status(ioaddr, VIRTIO_CONFIG_S_ACKNOWLEDGE |
|
|
+ VIRTIO_CONFIG_S_DRIVER );
|
|
+ return ioaddr;
|
|
}
|
|
diff --git a/src/virtio-pci.h b/src/virtio-pci.h
|
|
index d21d5a5..e1d972d 100644
|
|
--- a/src/virtio-pci.h
|
|
+++ b/src/virtio-pci.h
|
|
@@ -99,6 +99,7 @@ static inline void vp_del_vq(unsigned int ioaddr, int queue_index)
|
|
}
|
|
|
|
struct vring_virtqueue;
|
|
+u16 vp_init_simple(u16 bdf);
|
|
int vp_find_vq(unsigned int ioaddr, int queue_index,
|
|
- struct vring_virtqueue *vq);
|
|
+ struct vring_virtqueue **p_vq);
|
|
#endif /* _VIRTIO_PCI_H_ */
|
|
diff --git a/src/virtio-scsi.c b/src/virtio-scsi.c
|
|
new file mode 100644
|
|
index 0000000..2f6c48a
|
|
--- /dev/null
|
|
+++ b/src/virtio-scsi.c
|
|
@@ -0,0 +1,211 @@
|
|
+// 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 "blockcmd.h" // CDB_CMD_WRITE_10, SCSI_TYPE_CDROM
|
|
+#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(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;
|
|
+ }
|
|
+
|
|
+ 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..28d48b0
|
|
--- /dev/null
|
|
+++ b/src/virtio-scsi.h
|
|
@@ -0,0 +1,48 @@
|
|
+#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 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 */
|