Hi all,
this is the first bit of working code for the mythical virtio-scsi HBA. This patch mostly aims at making code more generic in SeaBIOS for both SCSI and virtio.
For SCSI, it introduces common bits for LUN detection and fixes some bugs. This is visible for example in that I can now boot the FreeDOS image on bochs.sf.net from a USB drive.
For virtio, it adds a few extra interface bits that are common to both virtio-scsi and virtio-blk.
The final patch adds the virtio-scsi code itself. I think the first 13 patches can go in before the virtio-scsi QEMU code, anyway I'm sending now the whole batch for review.
Paolo Bonzini (16): cdrom: use TEST UNIT READY to detect ready medium usb-msc: add usb_msc_send usb-uhci: wait tick after sending data usb-msc: support WRITE and TEST UNIT READY usb-msc: move READ CAPACITY to usb_msc_init, fix off-by-one usb-msc: pass drive to setup_drive_* usb-msc: rename INQUIRY types usb-msc: always go through TEST UNIT READY usb-msc: move common scsi code to blockcmd.c scsi: get physical chs geometry from mode page 0x04 always specify virtio-blk rather than virtio virtio-pci: introduce vp_init_simple virtio-pci: allocate vq in vp_find_vq virtio-pci: add vp_set usb-msc: move cdb dispatch to block.c add virtio-scsi driver
Makefile | 2 +- src/Kconfig | 10 ++- src/block.c | 35 +++++++-- src/blockcmd.c | 155 ++++++++++++++++++++++++++++++++++++++ src/blockcmd.h | 42 ++++++++++- src/boot.c | 14 ++++ src/boot.h | 1 + src/cdrom.c | 58 +-------------- src/disk.c | 5 +- src/disk.h | 19 +++-- src/pci_ids.h | 1 + src/post.c | 2 + src/usb-msc.c | 132 ++++++++++---------------------- src/usb-msc.h | 3 - src/usb-uhci.c | 1 + src/virtio-blk.c | 30 +++----- src/virtio-blk.h | 2 +- src/virtio-pci.c | 33 +++++++- src/virtio-pci.h | 13 +++- src/virtio-scsi.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/virtio-scsi.h | 43 +++++++++++ 21 files changed, 620 insertions(+), 197 deletions(-) create mode 100644 src/virtio-scsi.c create mode 100644 src/virtio-scsi.h
The READ CAPACITY output is not used except for some debugging messages. In the future, we will use this code for USB sticks too, but those already send READ CAPACITY. To avoid code duplication, switch to TEST UNIT READY for this task.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.c | 12 ++++++++++++ src/blockcmd.h | 6 ++++-- src/cdrom.c | 16 +++------------- 3 files changed, 19 insertions(+), 15 deletions(-)
diff --git a/src/blockcmd.c b/src/blockcmd.c index c9c6845..c127729 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -56,6 +56,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) diff --git a/src/blockcmd.h b/src/blockcmd.h index 903c435..49921b0 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; @@ -70,6 +71,7 @@ struct cdbres_inquiry { // 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_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data); int cdb_read(struct disk_op_s *op); diff --git a/src/cdrom.c b/src/cdrom.c index 6351fec..b817999 100644 --- a/src/cdrom.c +++ b/src/cdrom.c @@ -189,19 +189,18 @@ 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 + /* 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. */ - 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"); + dprintf(1, "test unit ready failed\n"); return -1; }
- int ret = cdb_read_capacity(op, &info); + int ret = cdb_test_unit_ready(op); if (!ret) // Success break; @@ -226,15 +225,6 @@ atapi_is_ready(struct disk_op_s *op) 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; }
This makes it a bit nicer to later introduce writes.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/usb-msc.c | 19 ++++++++++++++----- 1 files changed, 14 insertions(+), 5 deletions(-)
diff --git a/src/usb-msc.c b/src/usb-msc.c index 13ef93e..07a7ea2 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,8 +68,6 @@ 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; @@ -73,19 +82,19 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) 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); + ret = usb_msc_send(udrive_g, USB_DIR_IN, 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;
This fixes a timing problem when doing two consecutive USB_DIR_OUT sends. It is visible for example when writing to a USB device.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/usb-uhci.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)
diff --git a/src/usb-uhci.c b/src/usb-uhci.c index f3680d3..2435aff 100644 --- a/src/usb-uhci.c +++ b/src/usb-uhci.c @@ -496,6 +496,7 @@ uhci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize) }
SET_FLATPTR(pipe->toggle, !!toggle); + uhci_waittick(GET_FLATPTR(pipe->iobase)); return 0; fail: dprintf(1, "uhci_send_bulk failed\n");
Writes only require building the CDB and some care with the direction in the USB packet. TEST UNIT READY is special because it has no data, so it cannot have any residue either.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.c | 12 ++++++++++++ src/blockcmd.h | 1 + src/usb-msc.c | 21 ++++++++++++--------- 3 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/src/blockcmd.c b/src/blockcmd.c index c127729..f5e2ce3 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -91,3 +91,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 49921b0..84ab659 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -75,5 +75,6 @@ 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_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);
#endif // blockcmd.h diff --git a/src/usb-msc.c b/src/usb-msc.c index 07a7ea2..c8b9fbe 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -73,13 +73,13 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) 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_msc_send(udrive_g, USB_DIR_OUT @@ -87,10 +87,12 @@ usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) if (ret) goto fail;
- // Transfer data from device. - ret = usb_msc_send(udrive_g, USB_DIR_IN, op->buf_fl, bytes); - if (ret) - goto fail; + // Transfer data to/from device. + if (op->count) { + ret = usb_msc_send(udrive_g, cbw.bmCBWFlags, op->buf_fl, bytes); + if (ret) + goto fail; + }
// Transfer csw info. struct csw_s csw; @@ -104,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 (csw.dCSWDataResidue) + op->count -= csw.dCSWDataResidue / blocksize; return DISK_RET_EBADTRACK;
fail: @@ -128,9 +131,9 @@ process_usb_op(struct disk_op_s *op) switch (op->command) { case CMD_READ: return cdb_read(op); - case CMD_FORMAT: case CMD_WRITE: - return DISK_RET_EWRITEPROTECT; + return cdb_write(op); + case CMD_FORMAT: case CMD_RESET: case CMD_ISREADY: case CMD_VERIFY:
Only leave the bootprio code in setup_drive_hd, like in setup_drive_cdrom. This is a preparatory step; later, the SCSI code in usb_msc_init will become entirely generic.
Also, the returned number of sectors is off by one. This will become more important when CHS translation is added later.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/usb-msc.c | 32 +++++++++++++++----------------- 1 files changed, 15 insertions(+), 17 deletions(-)
diff --git a/src/usb-msc.c b/src/usb-msc.c index c8b9fbe..a0b79a5 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -153,7 +153,6 @@ process_usb_op(struct disk_op_s *op) static int setup_drive_cdrom(struct disk_op_s *op, char *desc) { - op->drive_g->blksize = CDROM_SECTOR_SIZE; op->drive_g->sectors = (u64)-1; struct usb_pipe *pipe = container_of( op->drive_g, struct usbdrive_s, drive)->bulkout; @@ -165,29 +164,16 @@ setup_drive_cdrom(struct disk_op_s *op, char *desc) static int setup_drive_hd(struct disk_op_s *op, 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 (op->drive_g->blksize != DISK_SECTOR_SIZE) { + dprintf(1, "Unsupported USB MSC block size %d\n", op->drive_g->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; int prio = bootprio_find_usb(pipe->cntl->pci, pipe->path); boot_add_hd(op->drive_g, desc, prio); - return 0; }
@@ -252,7 +238,19 @@ usb_msc_init(struct usb_pipe *pipe , vendor, product, rev, pdt, removable); udrive_g->drive.removable = removable;
- if (pdt == USB_MSC_TYPE_CDROM) { + struct cdbres_read_capacity capdata; + ret = cdb_read_capacity(&dop, &capdata); + if (ret) + return ret; + // XXX - retry for some timeout? + + // READ CAPACITY returns the address of the last block + udrive_g->drive.blksize = ntohl(capdata.blksize); + udrive_g->drive.sectors = ntohl(capdata.sectors) + 1; + dprintf(1, "USB MSC blksize=%d sectors=%d\n", + udrive_g->drive.blksize, (int)udrive_g->drive.sectors); + + if (pdt == USB_MSC_TYPE_CDROM || udrive_g->drive.blksize == CDROM_SECTOR_SIZE) { char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]" , vendor, product, rev); ret = setup_drive_cdrom(&dop, desc);
The two functions do not need anymore a disk_op_s.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/usb-msc.c | 22 +++++++++++----------- 1 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/usb-msc.c b/src/usb-msc.c index a0b79a5..b76bb57 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -151,29 +151,29 @@ process_usb_op(struct disk_op_s *op) ****************************************************************/
static int -setup_drive_cdrom(struct disk_op_s *op, char *desc) +setup_drive_cdrom(struct drive_s *drive, char *desc) { - 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) { - if (op->drive_g->blksize != DISK_SECTOR_SIZE) { - dprintf(1, "Unsupported USB MSC block size %d\n", op->drive_g->blksize); + if (drive->blksize != DISK_SECTOR_SIZE) { + dprintf(1, "Unsupported USB MSC block size %d\n", drive->blksize); return -1; }
// 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; }
@@ -253,11 +253,11 @@ usb_msc_init(struct usb_pipe *pipe if (pdt == USB_MSC_TYPE_CDROM || udrive_g->drive.blksize == CDROM_SECTOR_SIZE) { char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]" , vendor, product, rev); - ret = setup_drive_cdrom(&dop, desc); + ret = setup_drive_cdrom(&udrive_g->drive, desc); } else { char *desc = znprintf(MAXDESCSIZE, "USB Drive %s %s %s" , vendor, product, rev); - ret = setup_drive_hd(&dop, desc); + ret = setup_drive_hd(&udrive_g->drive, desc); } if (ret) goto fail;
They are generic SCSI constants, rename them as such.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.h | 3 +++ src/usb-msc.c | 2 +- src/usb-msc.h | 3 --- 3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/blockcmd.h b/src/blockcmd.h index 84ab659..6923e4c 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -57,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; diff --git a/src/usb-msc.c b/src/usb-msc.c index b76bb57..6cadb71 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -250,7 +250,7 @@ usb_msc_init(struct usb_pipe *pipe dprintf(1, "USB MSC blksize=%d sectors=%d\n", udrive_g->drive.blksize, (int)udrive_g->drive.sectors);
- if (pdt == USB_MSC_TYPE_CDROM || udrive_g->drive.blksize == CDROM_SECTOR_SIZE) { + if (pdt == SCSI_TYPE_CDROM || udrive_g->drive.blksize == CDROM_SECTOR_SIZE) { char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]" , vendor, product, rev); ret = setup_drive_cdrom(&udrive_g->drive, desc); 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
Add the wait loop that CDs are already using to usb-msc. USB drives might also report NOT READY at startup.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/blockcmd.h | 2 ++ src/cdrom.c | 48 ++---------------------------------------------- src/usb-msc.c | 7 ++++++- 4 files changed, 54 insertions(+), 47 deletions(-)
diff --git a/src/blockcmd.c b/src/blockcmd.c index f5e2ce3..e86fe29 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -32,6 +32,50 @@ 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; +} + +int cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data) { struct cdb_request_sense cmd; diff --git a/src/blockcmd.h b/src/blockcmd.h index 6923e4c..106a529 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -80,4 +80,6 @@ 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); + #endif // blockcmd.h diff --git a/src/cdrom.c b/src/cdrom.c index b817999..170ffc4 100644 --- a/src/cdrom.c +++ b/src/cdrom.c @@ -184,50 +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 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; -} - int cdrom_boot(struct drive_s *drive_g) { @@ -238,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/usb-msc.c b/src/usb-msc.c index 6cadb71..6f77275 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -238,11 +238,16 @@ usb_msc_init(struct usb_pipe *pipe , vendor, product, rev, pdt, removable); udrive_g->drive.removable = removable;
+ 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; - // XXX - retry for some timeout?
// READ CAPACITY returns the address of the last block udrive_g->drive.blksize = ntohl(capdata.blksize);
Finally move the INQUIRY/TEST UNIT READY/READ CAPACITY sequence to generic code, so that virtio-scsi will be able to use it.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/blockcmd.h | 1 + src/usb-msc.c | 49 ++++++------------------------------------------- 3 files changed, 60 insertions(+), 43 deletions(-)
diff --git a/src/blockcmd.c b/src/blockcmd.c index e86fe29..8e82f09 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -75,6 +75,58 @@ scsi_is_ready(struct disk_op_s *op) 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) +{ + 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 (scsi_is_ready(&dop)) + 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 + drive->blksize = ntohl(capdata.blksize); + drive->sectors = ntohl(capdata.sectors) + 1; + dprintf(1, "%s blksize=%d sectors=%d\n" + , s, drive->blksize, (unsigned)drive->sectors); + + if (drive->blksize == CDROM_SECTOR_SIZE) + *pdt = SCSI_TYPE_CDROM; + + if (*pdt == SCSI_TYPE_CDROM) + *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]" + , s, vendor, product, rev); + else + *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) { diff --git a/src/blockcmd.h b/src/blockcmd.h index 106a529..13ae991 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -81,5 +81,6 @@ 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/usb-msc.c b/src/usb-msc.c index 6f77275..d927e42 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -216,54 +216,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; - - 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 - udrive_g->drive.blksize = ntohl(capdata.blksize); - udrive_g->drive.sectors = ntohl(capdata.sectors) + 1; - dprintf(1, "USB MSC blksize=%d sectors=%d\n", - udrive_g->drive.blksize, (int)udrive_g->drive.sectors);
- if (pdt == SCSI_TYPE_CDROM || udrive_g->drive.blksize == CDROM_SECTOR_SIZE) { - char *desc = znprintf(MAXDESCSIZE, "DVD/CD [USB Drive %s %s %s]" - , vendor, product, rev); + if (pdt == SCSI_TYPE_CDROM) ret = setup_drive_cdrom(&udrive_g->drive, desc); - } else { - char *desc = znprintf(MAXDESCSIZE, "USB Drive %s %s %s" - , vendor, product, rev); + else ret = setup_drive_hd(&udrive_g->drive, desc); - } + if (ret) goto fail;
The mode page is marked as obsolete, but QEMU can provide the information.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/blockcmd.c | 33 ++++++++++++++++++++++++++++++++- src/blockcmd.h | 29 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletions(-)
diff --git a/src/blockcmd.c b/src/blockcmd.c index 8e82f09..39adcfc 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -120,9 +120,25 @@ scsi_init_drive(struct drive_s *drive, const char *s, int *pdt, char **desc) if (*pdt == SCSI_TYPE_CDROM) *desc = znprintf(MAXDESCSIZE, "DVD/CD [%s Drive %s %s %s]" , s, vendor, product, rev); - else + else { + 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 && + ((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; } @@ -176,6 +192,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) diff --git a/src/blockcmd.h b/src/blockcmd.h index 13ae991..bace649 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -71,11 +71,40 @@ 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);
Avoid ambiguity when virtio-scsi will be introduced.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/Kconfig | 4 ++-- src/block.c | 6 +++--- src/disk.c | 4 ++-- src/disk.h | 18 +++++++++--------- src/virtio-blk.c | 4 ++-- src/virtio-blk.h | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/Kconfig b/src/Kconfig index 338f51a..f8d245a 100644 --- a/src/Kconfig +++ b/src/Kconfig @@ -109,10 +109,10 @@ 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 FLOPPY depends on DRIVES bool "Floppy controller" diff --git a/src/block.c b/src/block.c index f7e7851..705c477 100644 --- a/src/block.c +++ b/src/block.c @@ -12,7 +12,7 @@ #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
u8 FloppyCount VAR16VISIBLE; u8 CDCount; @@ -295,8 +295,8 @@ process_op(struct disk_op_s *op) 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); default: diff --git a/src/disk.c b/src/disk.c index 8f7c61f..f2c6621 100644 --- a/src/disk.c +++ b/src/disk.c @@ -546,7 +546,7 @@ 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)) { disk_ret(regs, DISK_RET_SUCCESS); return; } @@ -651,7 +651,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..dd7c46a 100644 --- a/src/disk.h +++ b/src/disk.h @@ -198,15 +198,15 @@ 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 MAXDESCSIZE 80
diff --git a/src/virtio-blk.c b/src/virtio-blk.c index b1274fc..a81a873 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; @@ -110,7 +110,7 @@ init_virtio_blk(struct pci_device *pci) } 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;
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 */
Put together the common parts of all virtio device initialization.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/virtio-blk.c | 9 +-------- src/virtio-pci.c | 11 +++++++++++ src/virtio-pci.h | 1 + 3 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/src/virtio-blk.c b/src/virtio-blk.c index a81a873..9ed82e0 100644 --- a/src/virtio-blk.c +++ b/src/virtio-blk.c @@ -114,15 +114,8 @@ init_virtio_blk(struct pci_device *pci) 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 ) { dprintf(1, "fail to find vq for virtio-blk %x:%x\n", pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)); diff --git a/src/virtio-pci.c b/src/virtio-pci.c index db19e97..6a23d5d 100644 --- a/src/virtio-pci.c +++ b/src/virtio-pci.c @@ -67,3 +67,14 @@ int vp_find_vq(unsigned int ioaddr, int queue_index,
return num; } + +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..60e7b35 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); #endif /* _VIRTIO_PCI_H_ */
Another common bit that can be made generic.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/virtio-blk.c | 17 +++++++++-------- src/virtio-pci.c | 22 +++++++++++++++++----- src/virtio-pci.h | 2 +- 3 files changed, 27 insertions(+), 14 deletions(-)
diff --git a/src/virtio-blk.c b/src/virtio-blk.c index 9ed82e0..2def947 100644 --- a/src/virtio-blk.c +++ b/src/virtio-blk.c @@ -103,22 +103,21 @@ 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; } memset(vdrive_g, 0, sizeof(*vdrive_g)); - memset(vq, 0, sizeof(*vq)); vdrive_g->drive.type = DTYPE_VIRTIO_BLK; vdrive_g->drive.cntl_id = bdf; - vdrive_g->vq = vq;
u16 ioaddr = vp_init_simple(bdf); vdrive_g->ioaddr = ioaddr; - 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)); + if (vp_find_vq(ioaddr, 0, &vdrive_g->vq) < 0 ) { + if (vdrive_g->vq) { + dprintf(1, "fail to find vq for virtio-blk %x:%x\n", + pci_bdf_to_bus(bdf), pci_bdf_to_dev(bdf)); + } goto fail; }
@@ -154,8 +153,10 @@ init_virtio_blk(struct pci_device *pci) return;
fail: + if (vdrive_g) { + free(vdrive_g->vq); + } free(vdrive_g); - free(vq); }
void diff --git a/src/virtio-pci.c b/src/virtio-pci.c index 6a23d5d..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,6 +73,11 @@ 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) diff --git a/src/virtio-pci.h b/src/virtio-pci.h index 60e7b35..e1d972d 100644 --- a/src/virtio-pci.h +++ b/src/virtio-pci.h @@ -101,5 +101,5 @@ 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_ */
virtio-scsi will need to write to config space, add a helper to do that.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/virtio-pci.h | 10 ++++++++++ 1 files changed, 10 insertions(+), 0 deletions(-)
diff --git a/src/virtio-pci.h b/src/virtio-pci.h index e1d972d..ea40edf 100644 --- a/src/virtio-pci.h +++ b/src/virtio-pci.h @@ -64,6 +64,16 @@ static inline u8 vp_get_status(unsigned int ioaddr) return inb(ioaddr + VIRTIO_PCI_STATUS); }
+static inline void vp_set(unsigned int ioaddr, unsigned offset, + void *buf, unsigned len) +{ + u8 *ptr = buf; + unsigned i; + + for (i = 0; i < len; i++) + outb(ioaddr + VIRTIO_PCI_CONFIG + offset + i, ptr[i]); +} + static inline void vp_set_status(unsigned int ioaddr, u8 status) { if (status == 0) /* reset */
virtio-scsi's low-level dispatch code is exactly the same as USB's, since in the end both are actually SCSI HBAs. Move it to common code.
Signed-off-by: Paolo Bonzini pbonzini@redhat.com --- src/block.c | 28 +++++++++++++++++++++++++--- src/usb-msc.c | 28 ---------------------------- 2 files changed, 25 insertions(+), 31 deletions(-)
diff --git a/src/block.c b/src/block.c index 705c477..eeebd83 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_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_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,12 @@ 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_BLK: return process_virtio_blk_op(op); case DTYPE_AHCI: return process_ahci_op(op); + case DTYPE_USB: + return process_scsi_op(op); default: op->count = 0; return DISK_RET_EPARAM; diff --git a/src/usb-msc.c b/src/usb-msc.c index d927e42..bed381f 100644 --- a/src/usb-msc.c +++ b/src/usb-msc.c @@ -119,34 +119,6 @@ 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_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; - } -} - - -/**************************************************************** * Setup ****************************************************************/
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 */
On Tue, Nov 15, 2011 at 05:01:08PM +0100, Paolo Bonzini wrote:
Hi all,
this is the first bit of working code for the mythical virtio-scsi HBA. This patch mostly aims at making code more generic in SeaBIOS for both SCSI and virtio.
For SCSI, it introduces common bits for LUN detection and fixes some bugs. This is visible for example in that I can now boot the FreeDOS image on bochs.sf.net from a USB drive.
For virtio, it adds a few extra interface bits that are common to both virtio-scsi and virtio-blk.
The final patch adds the virtio-scsi code itself. I think the first 13 patches can go in before the virtio-scsi QEMU code, anyway I'm sending now the whole batch for review.
Thanks! The patch series looks good to me.
Question on patch 4 - can you elaborate on why waittick is needed?
On patch 5, the READ CAPACITY is made unconditional. The intent was to only run READ CAPACITY on harddrives, because CDROMs need to get the info from the CD and it can take some time (eg, 10 seconds) to spin the CD up on real hardware. One wouldn't want their computer to boot longer just because they had a CD in the drive on boot. (BTW, the check for blksize==CDROM_SECTOR_SIZE to force a CDROM type was something I added when testing - to the best of my knowledge no drive requires it.)
-Kevin
On 11/16/2011 02:46 AM, Kevin O'Connor wrote:
Question on patch 4 - can you elaborate on why waittick is needed?
What happens is that if you do two consecutive OUT sends, the second fails ~90% of the time with a timeout in wait_td. I initially wanted to add the wait in usb-msc.c and only for writes, but there is no public interface for [uoe]hci_waittick.
The speed wasn't measurably different, but uhci is glacial anyway and I haven't tested ehci.
On patch 5, the READ CAPACITY is made unconditional. The intent was to only run READ CAPACITY on harddrives, because CDROMs need to get the info from the CD and it can take some time (eg, 10 seconds) to spin the CD up on real hardware.
Ah ok, so also the scsi_is_ready is something that you do not want on CDs (unless booting from them, in which case it is done by src/cdrom.c). Will adjust.
One wouldn't want their computer to boot longer just because they had a CD in the drive on boot. (BTW, the check for blksize==CDROM_SECTOR_SIZE to force a CDROM type was something I added when testing - to the best of my knowledge no drive requires it.)
Good, I'll remove it.
Paolo