This patch adds initial support for USB Mass Storage Controllers. This includes support for bulk transfers on UHCI controllers. Code to detect a USB MSC device is added, and wrappers for sending "cdb" block commands over USB are added. The scsi "inquiry" command is also added. --- Makefile | 2 +- src/block.c | 6 ++ src/blockcmd.c | 18 ++++- src/blockcmd.h | 14 +++ src/config.h | 2 + src/disk.h | 1 + src/usb-msc.c | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/usb-msc.h | 25 ++++++ src/usb-uhci.c | 114 ++++++++++++++++++++++++ src/usb-uhci.h | 2 + src/usb.c | 36 +++++++- src/usb.h | 2 + 12 files changed, 479 insertions(+), 4 deletions(-) create mode 100644 src/usb-msc.c create mode 100644 src/usb-msc.h
diff --git a/Makefile b/Makefile index e087818..b896b54 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ OUT=out/ SRCBOTH=misc.c pmm.c stacks.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-hid.c usb-hub.c paravirt.c + usb.c usb-uhci.c usb-ohci.c usb-hid.c usb-hub.c usb-msc.c paravirt.c SRC16=$(SRCBOTH) system.c disk.c apm.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/block.c b/src/block.c index 365053e..3b43f97 100644 --- a/src/block.c +++ b/src/block.c @@ -10,6 +10,7 @@ #include "cmos.h" // inb_cmos #include "util.h" // dprintf #include "ata.h" // process_ata_op +#include "usb-msc.h" // process_usb_op
struct drives_s Drives VAR16VISIBLE;
@@ -280,6 +281,9 @@ describe_drive(struct drive_s *drive_g) case DTYPE_RAMDISK: describe_ramdisk(drive_g); break; + case DTYPE_USB: + describe_usb(drive_g); + break; default: printf("Unknown"); break; @@ -308,6 +312,8 @@ 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); default: op->count = 0; return DISK_RET_EPARAM; diff --git a/src/blockcmd.c b/src/blockcmd.c index 5efbdce..48568e6 100644 --- a/src/blockcmd.c +++ b/src/blockcmd.c @@ -10,7 +10,9 @@ #include "disk.h" // struct disk_op_s #include "blockcmd.h" // struct cdb_request_sense #include "ata.h" // atapi_cmd_data +#include "usb-msc.h" // usb_cmd_data
+// Route command to low-level handler. static int cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) { @@ -18,12 +20,26 @@ cdb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) switch (type) { case DTYPE_ATAPI: return atapi_cmd_data(op, cdbcmd, blocksize); + case DTYPE_USB: + return usb_cmd_data(op, cdbcmd, blocksize); default: op->count = 0; return DISK_RET_EPARAM; } }
+int +cdb_get_inquiry(struct disk_op_s *op, struct cdbres_inquiry *data) +{ + struct cdb_request_sense cmd; + memset(&cmd, 0, sizeof(cmd)); + cmd.command = CDB_CMD_INQUIRY; + cmd.length = sizeof(*data); + op->count = 1; + op->buf_fl = data; + return cdb_cmd_data(op, &cmd, sizeof(*data)); +} + // Request SENSE int cdb_get_sense(struct disk_op_s *op, struct cdbres_request_sense *data) @@ -58,5 +74,5 @@ cdb_read(struct disk_op_s *op) cmd.command = CDB_CMD_READ_10; cmd.lba = htonl(op->lba); cmd.count = htons(op->count); - return cdb_cmd_data(op, &cmd, CDROM_SECTOR_SIZE); + return cdb_cmd_data(op, &cmd, GET_GLOBAL(op->drive_g->blksize)); } diff --git a/src/blockcmd.h b/src/blockcmd.h index d645ebe..903c435 100644 --- a/src/blockcmd.h +++ b/src/blockcmd.h @@ -32,6 +32,7 @@ struct cdbres_read_capacity { u32 blksize; } PACKED;
+#define CDB_CMD_INQUIRY 0x12 #define CDB_CMD_REQUEST_SENSE 0x03
struct cdb_request_sense { @@ -55,9 +56,22 @@ struct cdbres_request_sense { u32 reserved_0e; } PACKED;
+struct cdbres_inquiry { + u8 pdt; + u8 removable; + u8 reserved_02[2]; + u8 additional; + u8 reserved_05[3]; + char vendor[8]; + char product[16]; + char rev[4]; +} 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_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);
#endif // blockcmd.h diff --git a/src/config.h b/src/config.h index e359652..1362581 100644 --- a/src/config.h +++ b/src/config.h @@ -36,6 +36,8 @@ #define CONFIG_USB_UHCI 1 // Support USB OHCI controllers #define CONFIG_USB_OHCI 1 +// Support USB disks +#define CONFIG_USB_MSC 1 // Support USB hubs #define CONFIG_USB_HUB 1 // Support USB keyboards diff --git a/src/disk.h b/src/disk.h index 90ca04d..d87d71a 100644 --- a/src/disk.h +++ b/src/disk.h @@ -199,6 +199,7 @@ struct drive_s { #define DTYPE_ATAPI 0x03 #define DTYPE_RAMDISK 0x04 #define DTYPE_CDEMU 0x05 +#define DTYPE_USB 0x06
#define TRANSLATION_NONE 0 #define TRANSLATION_LBA 1 diff --git a/src/usb-msc.c b/src/usb-msc.c new file mode 100644 index 0000000..8d4a44e --- /dev/null +++ b/src/usb-msc.c @@ -0,0 +1,261 @@ +// Code for handling USB Mass Storage Controller devices. +// +// Copyright (C) 2010 Kevin O'Connor kevin@koconnor.net +// +// This file may be distributed under the terms of the GNU LGPLv3 license. + +#include "util.h" // dprintf +#include "config.h" // CONFIG_USB_MSC +#include "usb-msc.h" // usb_msc_init +#include "usb.h" // struct usb_s +#include "biosvar.h" // GET_GLOBAL +#include "blockcmd.h" // cdb_read +#include "disk.h" // DTYPE_USB +#include "boot.h" // add_bcv_internal + +#define DESCSIZE 80 + +struct usbdrive_s { + struct drive_s drive; + struct usb_pipe *bulkin, *bulkout; + char *desc; +}; + + +/**************************************************************** + * Bulk-only drive command processing + ****************************************************************/ + +#define USB_CDB_SIZE 12 + +#define CBW_SIGNATURE 0x43425355 // USBC + +struct cbw_s { + u32 dCBWSignature; + u32 dCBWTag; + u32 dCBWDataTransferLength; + u8 bmCBWFlags; + u8 bCBWLUN; + u8 bCBWCBLength; + u8 CBWCB[16]; +} PACKED; + +#define CSW_SIGNATURE 0x53425355 // USBS + +struct csw_s { + u32 dCSWSignature; + u32 dCSWTag; + u32 dCSWDataResidue; + u8 bCSWStatus; +} PACKED; + +// Low-level usb command transmit function. +int +usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize) +{ + dprintf(1, "usb_cmd_data id=%p write=%d count=%d bs=%d buf=%p\n" + , 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)); + cbw.dCBWSignature = CBW_SIGNATURE; + cbw.dCBWTag = 999; // XXX + cbw.dCBWDataTransferLength = bytes; + cbw.bmCBWFlags = USB_DIR_IN; // XXX + 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 + , 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 csw info. + struct csw_s csw; + ret = usb_send_bulk(bulkin, USB_DIR_IN + , MAKE_FLATPTR(GET_SEG(SS), &csw), sizeof(csw)); + if (ret) + goto fail; + + if (!csw.bCSWStatus) + return DISK_RET_SUCCESS; + if (csw.bCSWStatus == 2) + goto fail; + + op->count -= csw.dCSWDataResidue / blocksize; + return DISK_RET_EBADTRACK; + +fail: + // XXX - reset connection + dprintf(1, "USB transmission failed\n"); + op->count = 0; + return DISK_RET_EBADTRACK; +} + + +/**************************************************************** + * Drive ops + ****************************************************************/ + +// 16bit command demuxer for ATAPI cdroms. +int +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; + case CMD_RESET: + case CMD_ISREADY: + case CMD_VERIFY: + case CMD_SEEK: + return DISK_RET_SUCCESS; + default: + op->count = 0; + return DISK_RET_EPARAM; + } +} + +void +describe_usb(struct drive_s *drive_g) +{ + struct usbdrive_s *udrive_g = container_of( + drive_g, struct usbdrive_s, drive); + printf("%s", udrive_g->desc); +} + + +/**************************************************************** + * Setup + ****************************************************************/ + +static int +setup_drive_cdrom(struct disk_op_s *op) +{ + op->drive_g->blksize = CDROM_SECTOR_SIZE; + op->drive_g->sectors = (u64)-1; + map_cd_drive(op->drive_g); + return 0; +} + +static int +setup_drive_hd(struct disk_op_s *op) +{ + 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); + dprintf(1, "Unsupported USB MSC block size %d\n", blksize); + return -1; + } + op->drive_g->blksize = blksize; + op->drive_g->sectors = sectors; + dprintf(1, "USB MSC blksize=%d sectors=%d\n", blksize, sectors); + + // Setup disk geometry translation. + setup_translation(op->drive_g); + + // Register with bcv system. + add_bcv_internal(op->drive_g); + + return 0; +} + +// Configure a usb msc device. +int +usb_msc_init(u32 endp, struct usb_interface_descriptor *iface, int imax) +{ + if (!CONFIG_USB_MSC) + return -1; + + // Verify right kind of device + if (iface->bInterfaceSubClass != US_SC_SCSI + || iface->bInterfaceProtocol != US_PR_BULK) { + dprintf(1, "Unsupported MSC USB device (subclass=%02x proto=%02x)\n" + , iface->bInterfaceSubClass, iface->bInterfaceProtocol); + return -1; + } + + // Find bulk in and bulk out endpoints. + struct usb_endpoint_descriptor *indesc = findEndPointDesc( + iface, imax, USB_ENDPOINT_XFER_BULK, USB_DIR_IN); + struct usb_endpoint_descriptor *outdesc = findEndPointDesc( + iface, imax, USB_ENDPOINT_XFER_BULK, USB_DIR_OUT); + if (!indesc || !outdesc) + goto fail; + u32 inendp = mkendpFromDesc(endp, indesc); + struct usb_pipe *bulkin = alloc_bulk_pipe(inendp); + u32 outendp = mkendpFromDesc(endp, outdesc); + struct usb_pipe *bulkout = alloc_bulk_pipe(outendp); + if (!bulkin || !bulkout) + goto fail; + + // Allocate drive structure. + char *desc = malloc_tmphigh(DESCSIZE); + struct usbdrive_s *udrive_g = malloc_fseg(sizeof(*udrive_g)); + if (!udrive_g || !desc) { + warn_noalloc(); + goto fail; + } + memset(udrive_g, 0, sizeof(*udrive_g)); + udrive_g->drive.type = DTYPE_USB; + udrive_g->bulkin = bulkin; + udrive_g->bulkout = bulkout; + + // 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); + if (ret) + goto fail; + char vendor[sizeof(data.vendor)+1], product[sizeof(data.product)+1]; + char rev[sizeof(data.rev)+1]; + 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" + , strtcpy(vendor, data.vendor, sizeof(vendor)) + , strtcpy(product, data.product, sizeof(product)) + , strtcpy(rev, data.rev, sizeof(rev)) + , pdt, removable); + + if (pdt == USB_MSC_TYPE_CDROM) + ret = setup_drive_cdrom(&dop); + else + ret = setup_drive_hd(&dop); + if (ret) + goto fail; + + snprintf(desc, DESCSIZE, "USB Drive %s %s %s", vendor, product, rev); + udrive_g->desc = desc; + + return 0; +fail: + dprintf(1, "Unable to configure USB MSC device.\n"); + free(desc); + free(udrive_g); + return -1; +} diff --git a/src/usb-msc.h b/src/usb-msc.h new file mode 100644 index 0000000..4bad91f --- /dev/null +++ b/src/usb-msc.h @@ -0,0 +1,25 @@ +#ifndef __USB_MSC_H +#define __USB_MSC_H + +// usb-msc.c +struct disk_op_s; +int usb_cmd_data(struct disk_op_s *op, void *cdbcmd, u16 blocksize); +struct usb_interface_descriptor; +int usb_msc_init(u32 endp, struct usb_interface_descriptor *iface, int imax); +int process_usb_op(struct disk_op_s *op); +struct drive_s; +void describe_usb(struct drive_s *drive_g); + + +/**************************************************************** + * MSC flags + ****************************************************************/ + +#define US_SC_SCSI 0x06 + +#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 ca51337..881e345 100644 --- a/src/usb-uhci.c +++ b/src/usb-uhci.c @@ -253,6 +253,120 @@ uhci_control(u32 endp, int dir, const void *cmd, int cmdsize }
struct usb_pipe * +uhci_alloc_bulk_pipe(u32 endp) +{ + if (! CONFIG_USB_UHCI) + return NULL; + struct usb_s *cntl = endp2cntl(endp); + dprintf(7, "uhci_alloc_bulk_pipe %x\n", endp); + + // Allocate a queue head. + struct uhci_qh *qh = malloc_low(sizeof(*qh)); + if (!qh) { + warn_noalloc(); + return NULL; + } + qh->element = UHCI_PTR_TERM; + qh->next_td = 0; + qh->pipe.endp = endp; + + // Add queue head to controller list. + struct uhci_qh *data_qh = cntl->uhci.qh; + qh->link = data_qh->link; + barrier(); + data_qh->link = (u32)qh | UHCI_PTR_QH; + + return &qh->pipe; +} + +static int +wait_td(struct uhci_td *td) +{ + u64 end = calc_future_tsc(5000); // XXX - lookup real time. + u32 status; + for (;;) { + status = td->status; + if (!(status & TD_CTRL_ACTIVE)) + break; + if (check_time(end)) { + warn_timeout(); + return -1; + } + yield(); + } + if (status & TD_CTRL_ANY_ERROR) { + dprintf(1, "wait_td error - status=%x\n", status); + return -2; + } + return 0; +} + +#define STACKTDS 4 +#define TDALIGN 16 + +int +uhci_send_bulk(struct usb_pipe *pipe, int dir, void *data, int datasize) +{ + struct uhci_qh *qh = container_of(pipe, struct uhci_qh, pipe); + u32 endp = GET_FLATPTR(qh->pipe.endp); + dprintf(7, "uhci_send_bulk qh=%p endp=%x dir=%d data=%p size=%d\n" + , qh, endp, dir, data, datasize); + int maxpacket = endp2maxsize(endp); + int lowspeed = endp2speed(endp); + int devaddr = endp2devaddr(endp) | (endp2ep(endp) << 7); + int toggle = (u32)GET_FLATPTR(qh->next_td); // XXX + + // Allocate 4 tds on stack (16byte aligned) + u8 tdsbuf[sizeof(struct uhci_td) * STACKTDS + TDALIGN - 1]; + struct uhci_td *tds = (void*)ALIGN((u32)tdsbuf, TDALIGN); + memset(tds, 0, sizeof(*tds) * STACKTDS); + + // Enable tds + SET_FLATPTR(qh->element, (u32)MAKE_FLATPTR(GET_SEG(SS), tds)); + + int tdpos = 0; + while (datasize) { + struct uhci_td *td = &tds[tdpos++ % STACKTDS]; + int ret = wait_td(td); + if (ret) + goto fail; + + int transfer = datasize; + if (transfer > maxpacket) + transfer = maxpacket; + struct uhci_td *nexttd_fl = MAKE_FLATPTR(GET_SEG(SS) + , &tds[tdpos % STACKTDS]); + td->link = (transfer==datasize ? UHCI_PTR_TERM : (u32)nexttd_fl); + td->token = (uhci_explen(transfer) | toggle + | (devaddr << TD_TOKEN_DEVADDR_SHIFT) + | (dir ? USB_PID_IN : USB_PID_OUT)); + td->buffer = data; + barrier(); + td->status = (uhci_maxerr(3) | (lowspeed ? TD_CTRL_LS : 0) + | TD_CTRL_ACTIVE); + toggle ^= TD_TOKEN_TOGGLE; + + data += transfer; + datasize -= transfer; + } + int i; + for (i=0; i<STACKTDS; i++) { + struct uhci_td *td = &tds[tdpos++ % STACKTDS]; + int ret = wait_td(td); + if (ret) + goto fail; + } + + SET_FLATPTR(qh->next_td, (void*)toggle); // XXX + return 0; +fail: + dprintf(1, "uhci_send_bulk failed\n"); + SET_FLATPTR(qh->element, UHCI_PTR_TERM); + uhci_waittick(); + return -1; +} + +struct usb_pipe * uhci_alloc_intr_pipe(u32 endp, int frameexp) { if (! CONFIG_USB_UHCI) diff --git a/src/usb-uhci.h b/src/usb-uhci.h index d9c10ae..d7ebb4e 100644 --- a/src/usb-uhci.h +++ b/src/usb-uhci.h @@ -8,6 +8,8 @@ struct usb_s; void uhci_init(void *data); int uhci_control(u32 endp, int dir, const void *cmd, int cmdsize , void *data, int datasize); +struct usb_pipe *uhci_alloc_bulk_pipe(u32 endp); +int uhci_send_bulk(struct usb_pipe *pipe, int dir, void *data, int datasize); struct usb_pipe *uhci_alloc_intr_pipe(u32 endp, int frameexp); int uhci_poll_intr(struct usb_pipe *pipe, void *data);
diff --git a/src/usb.c b/src/usb.c index 1ed3c80..cc3b201 100644 --- a/src/usb.c +++ b/src/usb.c @@ -13,6 +13,7 @@ #include "usb-ohci.h" // ohci_init #include "usb-hid.h" // usb_keyboard_setup #include "usb-hub.h" // usb_hub_init +#include "usb-msc.h" // usb_msc_init #include "usb.h" // struct usb_s #include "biosvar.h" // GET_GLOBAL
@@ -39,6 +40,33 @@ send_control(u32 endp, int dir, const void *cmd, int cmdsize }
struct usb_pipe * +alloc_bulk_pipe(u32 endp) +{ + struct usb_s *cntl = endp2cntl(endp); + switch (cntl->type) { + default: + case USB_TYPE_UHCI: + return uhci_alloc_bulk_pipe(endp); + case USB_TYPE_OHCI: + return NULL; + } +} + +int +usb_send_bulk(struct usb_pipe *pipe, int dir, void *data, int datasize) +{ + u32 endp = GET_FLATPTR(pipe->endp); + struct usb_s *cntl = endp2cntl(endp); + switch (cntl->type) { + default: + case USB_TYPE_UHCI: + return uhci_send_bulk(pipe, dir, data, datasize); + case USB_TYPE_OHCI: + return -1; + } +} + +struct usb_pipe * alloc_intr_pipe(u32 endp, int period) { struct usb_s *cntl = endp2cntl(endp); @@ -220,6 +248,7 @@ configure_usb_device(struct usb_s *cntl, int lowspeed) if ((iface->bInterfaceClass != USB_CLASS_HID || iface->bInterfaceSubClass != USB_INTERFACE_SUBCLASS_BOOT || iface->bInterfaceProtocol != USB_INTERFACE_PROTOCOL_KEYBOARD) + && (iface->bInterfaceClass != USB_CLASS_MASS_STORAGE) && (iface->bInterfaceClass != USB_CLASS_HUB)) // Not a supported device. goto fail; @@ -237,8 +266,11 @@ configure_usb_device(struct usb_s *cntl, int lowspeed) free(config); return usb_hub_init(endp); } - ret = usb_keyboard_init(endp, iface, ((void*)config + config->wTotalLength - - (void*)iface)); + int imax = (void*)config + config->wTotalLength - (void*)iface; + if (iface->bInterfaceClass == USB_CLASS_MASS_STORAGE) + ret = usb_msc_init(endp, iface, imax); + else + ret = usb_keyboard_init(endp, iface, imax); if (ret) goto fail;
diff --git a/src/usb.h b/src/usb.h index 925f8ae..d947891 100644 --- a/src/usb.h +++ b/src/usb.h @@ -37,6 +37,8 @@ int configure_usb_device(struct usb_s *cntl, int lowspeed); struct usb_ctrlrequest; int send_default_control(u32 endp, const struct usb_ctrlrequest *req , void *data); +int usb_send_bulk(struct usb_pipe *pipe, int dir, void *data, int datasize); +struct usb_pipe *alloc_bulk_pipe(u32 endp); struct usb_pipe *alloc_intr_pipe(u32 endp, int period); int usb_poll_intr(struct usb_pipe *pipe, void *data); struct usb_interface_descriptor;