Initial support for booting from USB drives.

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.
diff --git a/Makefile b/Makefile
index e087818..b896b54 100644
--- a/Makefile
+++ b/Makefile
@@ -14,7 +14,7 @@
 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 @@
     case DTYPE_RAMDISK:
         describe_ramdisk(drive_g);
         break;
+    case DTYPE_USB:
+        describe_usb(drive_g);
+        break;
     default:
         printf("Unknown");
         break;
@@ -308,6 +312,8 @@
         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 @@
     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 @@
     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 @@
     u32 blksize;
 } PACKED;
 
+#define CDB_CMD_INQUIRY 0x12
 #define CDB_CMD_REQUEST_SENSE 0x03
 
 struct cdb_request_sense {
@@ -55,9 +56,22 @@
     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 @@
 #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 @@
 }
 
 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 @@
 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 @@
 }
 
 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 @@
     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 @@
         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 @@
 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;