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; iqh.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; itoggle, !!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 +// +// 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 */