summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKevin O'Connor <kevin@koconnor.net>2014-12-29 13:06:23 -0500
committerKevin O'Connor <kevin@koconnor.net>2015-01-01 12:56:35 -0500
commit746d0a75ad90316280fc98468945bf0706c7386f (patch)
tree017e0a3ed4c2fc9b91f933edd05747fb77193443
parent45e71721da6e37598593cf04f5a840380455d0a6 (diff)
downloadqemu-seabios-746d0a75ad90316280fc98468945bf0706c7386f.tar.gz
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>
-rw-r--r--src/hw/usb-ohci.c88
-rw-r--r--src/hw/usb-ohci.h1
-rw-r--r--src/hw/usb.c5
3 files changed, 76 insertions, 18 deletions
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 usb_ohci_s {
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 @@ check_ohci_ports(struct usb_ohci_s *cntl)
// 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 @@ ohci_free_pipes(struct usb_ohci_s *cntl)
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 @@ start_ohci(struct usb_ohci_s *cntl, struct ohci_hcca *hcca)
// 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 @@ ohci_desc2pipe(struct ohci_pipe *pipe, struct usbdevice_s *usbdev
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 @@ ohci_realloc_pipe(struct usbdevice_s *usbdev, struct usb_pipe *upipe
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 @@ ohci_realloc_pipe(struct usbdevice_s *usbdev, struct usb_pipe *upipe
}
// 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_realloc_pipe(struct usbdevice_s *usbdev, struct usb_pipe *upipe
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 @@ wait_ed(struct ohci_ed *ed, int timeout)
{
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 @@ ohci_send_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
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 @@ ohci_send_control(struct usb_pipe *p, int dir, const void *cmd, int cmdsize
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 @@ struct ohci_regs {
#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 @@ usb_send_bulk(struct usb_pipe *pipe_fl, int dir, void *data, int datasize)
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 @@ usb_poll_intr(struct usb_pipe *pipe_fl, void *data)
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);
}