usb: Add support for OHCI bulk transfers

Support bulk transfers (usb drives) on OHCI USB controllers.  Now that
there is support for 32bit only drive controllers, it is not that hard
to support disks on OHCI controllers, and it may be useful for some
old hardware.

Signed-off-by: Kevin O'Connor <kevin@koconnor.net>
diff --git a/src/hw/usb-ohci.c b/src/hw/usb-ohci.c
index 4789768..ee31b83 100644
--- a/src/hw/usb-ohci.c
+++ b/src/hw/usb-ohci.c
@@ -7,6 +7,7 @@
 #include "biosvar.h" // GET_LOWFLAT
 #include "config.h" // CONFIG_*
 #include "malloc.h" // free
+#include "memmap.h" // PAGE_SIZE
 #include "output.h" // dprintf
 #include "pci.h" // pci_bdf_to_bus
 #include "pci_ids.h" // PCI_CLASS_SERIAL_USB_OHCI
@@ -27,6 +28,7 @@
 struct ohci_pipe {
     struct ohci_ed ed;
     struct usb_pipe pipe;
+    struct ohci_regs *regs;
     void *data;
     int count;
     struct ohci_td *tds;
@@ -118,10 +120,10 @@
 
 // Wait for next USB frame to start - for ensuring safe memory release.
 static void
-ohci_waittick(struct usb_ohci_s *cntl)
+ohci_waittick(struct ohci_regs *regs)
 {
     barrier();
-    struct ohci_hcca *hcca = (void*)cntl->regs->hcca;
+    struct ohci_hcca *hcca = (void*)regs->hcca;
     u32 startframe = hcca->frame_no;
     u32 end = timer_calc(1000 * 5);
     for (;;) {
@@ -143,7 +145,7 @@
     u32 creg = readl(&cntl->regs->control);
     if (creg & (OHCI_CTRL_CLE|OHCI_CTRL_BLE)) {
         writel(&cntl->regs->control, creg & ~(OHCI_CTRL_CLE|OHCI_CTRL_BLE));
-        ohci_waittick(cntl);
+        ohci_waittick(cntl->regs);
     }
 
     u32 *pos = &cntl->regs->ed_controlhead;
@@ -209,7 +211,7 @@
 
     // Go into operational state
     writel(&cntl->regs->control
-           , (OHCI_CTRL_CBSR | OHCI_CTRL_CLE | OHCI_CTRL_PLE
+           , (OHCI_CTRL_CBSR | OHCI_CTRL_CLE | OHCI_CTRL_BLE | OHCI_CTRL_PLE
               | OHCI_USB_OPER | oldrwc));
     readl(&cntl->regs->control); // flush writes
 
@@ -320,6 +322,9 @@
     pipe->ed.hwINFO = (ED_SKIP | usbdev->devaddr | (pipe->pipe.ep << 7)
                        | (epdesc->wMaxPacketSize << 16)
                        | (usbdev->speed ? ED_LOWSPEED : 0));
+    struct usb_ohci_s *cntl = container_of(
+        usbdev->hub->cntl, struct usb_ohci_s, usb);
+    pipe->regs = cntl->regs;
 }
 
 static struct usb_pipe *
@@ -398,10 +403,6 @@
     u8 eptype = epdesc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
     if (eptype == USB_ENDPOINT_XFER_INT)
         return ohci_alloc_intr_pipe(usbdev, epdesc);
-    if (eptype != USB_ENDPOINT_XFER_CONTROL) {
-        dprintf(1, "OHCI Bulk transfers not supported.\n");
-        return NULL;
-    }
     struct usb_ohci_s *cntl = container_of(
         usbdev->hub->cntl, struct usb_ohci_s, usb);
     dprintf(7, "ohci_alloc_async_pipe %p\n", &cntl->usb);
@@ -415,7 +416,11 @@
     }
 
     // Allocate a new queue head.
-    struct ohci_pipe *pipe = malloc_tmphigh(sizeof(*pipe));
+    struct ohci_pipe *pipe;
+    if (eptype == USB_ENDPOINT_XFER_CONTROL)
+        pipe = malloc_tmphigh(sizeof(*pipe));
+    else
+        pipe = malloc_low(sizeof(*pipe));
     if (!pipe) {
         warn_noalloc();
         return NULL;
@@ -424,9 +429,12 @@
     ohci_desc2pipe(pipe, usbdev, epdesc);
 
     // Add queue head to controller list.
-    pipe->ed.hwNextED = cntl->regs->ed_controlhead;
+    u32 *head = &cntl->regs->ed_controlhead;
+    if (eptype != USB_ENDPOINT_XFER_CONTROL)
+        head = &cntl->regs->ed_bulkhead;
+    pipe->ed.hwNextED = *head;
     barrier();
-    cntl->regs->ed_controlhead = (u32)&pipe->ed;
+    *head = (u32)&pipe->ed;
     return &pipe->pipe;
 }
 
@@ -435,10 +443,12 @@
 {
     u32 end = timer_calc(timeout);
     for (;;) {
-        if (ed->hwHeadP == ed->hwTailP)
+        if ((ed->hwHeadP & ~(ED_C|ED_H)) == ed->hwTailP)
             return 0;
         if (timer_check(end)) {
             warn_timeout();
+            dprintf(1, "ohci ed info=%x tail=%x head=%x next=%x\n"
+                    , ed->hwINFO, ed->hwTailP, ed->hwHeadP, ed->hwNextED);
             return -1;
         }
         yield();
@@ -458,8 +468,6 @@
         return -1;
     }
     struct ohci_pipe *pipe = container_of(p, struct ohci_pipe, pipe);
-    struct usb_ohci_s *cntl = container_of(
-        pipe->pipe.cntl, struct usb_ohci_s, usb);
 
     // Setup transfer descriptors
     struct ohci_td *tds = malloc_tmphigh(sizeof(*tds) * 3);
@@ -491,20 +499,66 @@
     pipe->ed.hwTailP = (u32)td;
     barrier();
     pipe->ed.hwINFO &= ~ED_SKIP;
-    writel(&cntl->regs->cmdstatus, OHCI_CLF);
+    writel(&pipe->regs->cmdstatus, OHCI_CLF);
 
     int ret = wait_ed(&pipe->ed, usb_xfer_time(p, datasize));
     pipe->ed.hwINFO |= ED_SKIP;
     if (ret)
-        ohci_waittick(cntl);
+        ohci_waittick(pipe->regs);
     free(tds);
     return ret;
 }
 
+#define STACKOTDS 16
+#define OHCI_TD_ALIGN 16
+
 int
 ohci_send_bulk(struct usb_pipe *p, int dir, void *data, int datasize)
 {
-    return -1;
+    ASSERT32FLAT();
+    if (! CONFIG_USB_OHCI)
+        return -1;
+    dprintf(7, "ohci_send_bulk %p\n", p);
+    struct ohci_pipe *pipe = container_of(p, struct ohci_pipe, pipe);
+
+    // Allocate 16 tds on stack (with required alignment)
+    u8 tdsbuf[sizeof(struct ohci_td) * STACKOTDS + OHCI_TD_ALIGN - 1];
+    struct ohci_td *tds = (void*)ALIGN((u32)tdsbuf, OHCI_TD_ALIGN), *td = tds;
+    memset(tds, 0, sizeof(*tds) * STACKOTDS);
+
+    // Setup transfer descriptors
+    u16 maxpacket = pipe->pipe.maxpacket;
+    u32 dest = (u32)data, dataend = dest + datasize;
+    while (dest < dataend) {
+        // Send data pids
+        if (td >= &tds[STACKOTDS]) {
+            warn_noalloc();
+            return -1;
+        }
+        int maxtransfer = 2*PAGE_SIZE - (dest & (PAGE_SIZE-1));
+        int transfer = dataend - dest;
+        if (transfer > maxtransfer)
+            transfer = ALIGN_DOWN(maxtransfer, maxpacket);
+        td->hwINFO = (dir ? TD_DP_IN : TD_DP_OUT) | TD_CC;
+        td->hwCBP = dest;
+        td->hwNextTD = (u32)&td[1];
+        td->hwBE = dest + transfer - 1;
+        td++;
+        dest += transfer;
+    }
+
+    // Transfer data
+    pipe->ed.hwHeadP = (u32)tds | (pipe->ed.hwHeadP & ED_C);
+    pipe->ed.hwTailP = (u32)td;
+    barrier();
+    pipe->ed.hwINFO &= ~ED_SKIP;
+    writel(&pipe->regs->cmdstatus, OHCI_BLF);
+
+    int ret = wait_ed(&pipe->ed, usb_xfer_time(p, datasize));
+    pipe->ed.hwINFO |= ED_SKIP;
+    if (ret)
+        ohci_waittick(pipe->regs);
+    return ret;
 }
 
 int
diff --git a/src/hw/usb-ohci.h b/src/hw/usb-ohci.h
index 5699523..db935ca 100644
--- a/src/hw/usb-ohci.h
+++ b/src/hw/usb-ohci.h
@@ -107,6 +107,7 @@
 
 #define OHCI_HCR        (1 << 0)
 #define OHCI_CLF        (1 << 1)
+#define OHCI_BLF        (1 << 2)
 
 #define OHCI_INTR_MIE   (1 << 31)
 
diff --git a/src/hw/usb.c b/src/hw/usb.c
index bb646a7..75412f9 100644
--- a/src/hw/usb.c
+++ b/src/hw/usb.c
@@ -71,6 +71,8 @@
     case USB_TYPE_UHCI:
         return uhci_send_bulk(pipe_fl, dir, data, datasize);
     case USB_TYPE_OHCI:
+        if (MODESEGMENT)
+            return -1;
         return ohci_send_bulk(pipe_fl, dir, data, datasize);
     case USB_TYPE_EHCI:
         return ehci_send_bulk(pipe_fl, dir, data, datasize);
@@ -102,7 +104,8 @@
 
 int usb_32bit_pipe(struct usb_pipe *pipe_fl)
 {
-    return CONFIG_USB_XHCI && GET_LOWFLAT(pipe_fl->type) == USB_TYPE_XHCI;
+    return (CONFIG_USB_XHCI && GET_LOWFLAT(pipe_fl->type) == USB_TYPE_XHCI)
+        || (CONFIG_USB_OHCI && GET_LOWFLAT(pipe_fl->type) == USB_TYPE_OHCI);
 }