From afe86c0b74cc7f5dcaefc34155daf299e8377353 Mon Sep 17 00:00:00 2001 From: Nico Huber Date: Mon, 21 May 2012 14:46:26 +0200 Subject: [PATCH] libpayload: Add timeouts in the OHCI USB driver We should always have some timeout when we wait for the hardware. This adds missing timeouts and a more standard compliant port reset to the OHCI driver. Change-Id: I2cfcb1039fd12f291e88dcb8b74d41cb5bb2315e Signed-off-by: Nico Huber Reviewed-on: http://review.coreboot.org/1076 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer --- payloads/libpayload/drivers/usb/ohci.c | 22 +++++++++-- payloads/libpayload/drivers/usb/ohci_rh.c | 48 ++++++++++++++++++----- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/payloads/libpayload/drivers/usb/ohci.c b/payloads/libpayload/drivers/usb/ohci.c index 73120a85da..4b3aa39c6c 100644 --- a/payloads/libpayload/drivers/usb/ohci.c +++ b/payloads/libpayload/drivers/usb/ohci.c @@ -195,14 +195,23 @@ dump_td(td_t *cur, int level) } static int -wait_for_ed(usbdev_t *dev, ed_t *head) +wait_for_ed(usbdev_t *dev, ed_t *head, int pages) { td_t *cur; /* wait for results */ + /* TODO: how long to wait? + * give 50ms per page plus another 100ms for now + * this should even work with low-speed + */ + int timeout = pages*50 + 100; while (((head->head_pointer & ~3) != head->tail_pointer) && !(head->head_pointer & 1) && - ((((td_t*)phys_to_virt(head->head_pointer & ~3))->config & TD_CC_MASK) >= TD_CC_NOACCESS)) { + ((((td_t*)phys_to_virt(head->head_pointer & ~3))->config + & TD_CC_MASK) >= TD_CC_NOACCESS) && + timeout--) { + /* don't log every ms */ + if (!(timeout % 100)) debug("intst: %x; ctrl: %x; cmdst: %x; head: %x -> %x, tail: %x, condition: %x\n", OHCI_INST(dev->controller)->opreg->HcInterruptStatus, OHCI_INST(dev->controller)->opreg->HcControl, @@ -213,6 +222,9 @@ wait_for_ed(usbdev_t *dev, ed_t *head) (((td_t*)phys_to_virt(head->head_pointer & ~3))->config & TD_CC_MASK) >> TD_CC_SHIFT); mdelay(1); } + if (timeout < 0) + printf("Error: ohci: endpoint " + "descriptor processing timed out.\n"); #if 0 /* XXX: The following debugging code may follow invalid lists and * cause a reboot. @@ -341,7 +353,8 @@ ohci_control (usbdev_t *dev, direction_t dir, int drlen, void *devreq, int dalen OHCI_INST(dev->controller)->opreg->HcControl |= ControlListEnable; OHCI_INST(dev->controller)->opreg->HcCommandStatus = ControlListFilled; - int failure = wait_for_ed(dev, head); + int failure = wait_for_ed(dev, head, + (dalen==0)?0:(last_page - first_page + 1)); OHCI_INST(dev->controller)->opreg->HcControl &= ~ControlListEnable; /* free memory */ @@ -430,7 +443,8 @@ ohci_bulk (endpoint_t *ep, int dalen, u8 *data, int finalize) OHCI_INST(ep->dev->controller)->opreg->HcControl |= BulkListEnable; OHCI_INST(ep->dev->controller)->opreg->HcCommandStatus = BulkListFilled; - int failure = wait_for_ed(ep->dev, head); + int failure = wait_for_ed(ep->dev, head, + (dalen==0)?0:(last_page - first_page + 1)); OHCI_INST(ep->dev->controller)->opreg->HcControl &= ~BulkListEnable; ep->toggle = head->head_pointer & ED_TOGGLE; diff --git a/payloads/libpayload/drivers/usb/ohci_rh.c b/payloads/libpayload/drivers/usb/ohci_rh.c index 5cf7ee8376..ac92f45a46 100644 --- a/payloads/libpayload/drivers/usb/ohci_rh.c +++ b/payloads/libpayload/drivers/usb/ohci_rh.c @@ -43,15 +43,40 @@ typedef struct { static void ohci_rh_enable_port (usbdev_t *dev, int port) { - if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] & CurrentConnectStatus)) - return; + /* Reset RH port should hold 50ms with pulses of at least 10ms and + * gaps of at most 3ms (usb20 spec 7.1.7.5). + * After reset, the port will be enabled automatically (ohci spec + * 7.4.4). + */ + int delay = 100; /* 100 * 500us == 50ms */ + while (delay > 0) { + if (!(OHCI_INST(dev->controller)->opreg->HcRhPortStatus[port] + & CurrentConnectStatus)) + return; - OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = SetPortEnable; // enable port - mdelay(10); - while (!(OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus)) mdelay(1); - OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = SetPortReset; // reset port - while (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortResetStatus) mdelay(1); - OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = PortResetStatusChange; + /* start reset */ + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = + SetPortReset; + int timeout = 200; /* timeout after 200 * 500us == 100ms */ + while ((OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] + & PortResetStatus) + && timeout--) { + udelay(500); delay--; + } + if (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] + & PortResetStatus) { + debug("Warning: root-hub port reset timed out.\n"); + break; + } + if ((200-timeout) < 20) + debug("Warning: port reset too short: %dms; " + "should be at least 10ms.\n", + (200-timeout)/2); + /* clear reset status change */ + OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = + PortResetStatusChange; + debug ("rh port reset finished after %dms.\n", (200-timeout)/2); + } } /* disable root hub */ @@ -59,7 +84,12 @@ static void ohci_rh_disable_port (usbdev_t *dev, int port) { OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] = ClearPortEnable; // disable port - while (OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] & PortEnableStatus) mdelay(1); + int timeout = 50; /* timeout after 50 * 100us == 5ms */ + while ((OHCI_INST (dev->controller)->opreg->HcRhPortStatus[port] + & PortEnableStatus) + && timeout--) { + udelay(100); + } } static void