summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Huber <nico.huber@secunet.com>2012-06-14 13:27:39 +0200
committerNico Huber <nico.huber@secunet.com>2012-06-21 11:53:25 +0200
commit3ca35cae354305003bcc5d14549a247247726e61 (patch)
treef63a68222c85d7cf15fcf85e24fecf8909e2db7e
parent0d120f8ee25cfeb0c87024aff8c1f81d0b64bbb4 (diff)
downloadcoreboot-3ca35cae354305003bcc5d14549a247247726e61.tar.xz
libpayload: Add dummy queue heads to EHCI interrupt frame list
This introduces a dummy queue head in the interrupt frame list of the EHCI host controller. It's a workaround for broken controllers which follow pointers from this list even if the terminate bit is set. Fortunately, they do honor the bit in queue heads and having an empty QH in the list doesn't violate the standard. The linux kernel has a similar workaround for AMD SB700, SB800, and Hudson-2/3 platforms. We observed this bug with an AMD SB600. Change-Id: Ibbb66dea5fddc89c7995a24d746bedf6bfa887be Signed-off-by: Nico Huber <nico.huber@secunet.com> Reviewed-on: http://review.coreboot.org/1124 Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org> Tested-by: build bot (Jenkins)
-rw-r--r--payloads/libpayload/drivers/usb/ehci.c24
-rw-r--r--payloads/libpayload/drivers/usb/ehci_private.h1
2 files changed, 22 insertions, 3 deletions
diff --git a/payloads/libpayload/drivers/usb/ehci.c b/payloads/libpayload/drivers/usb/ehci.c
index aaadf2dd4e..8d84f8dd60 100644
--- a/payloads/libpayload/drivers/usb/ehci.c
+++ b/payloads/libpayload/drivers/usb/ehci.c
@@ -83,6 +83,9 @@ static void ehci_shutdown (hci_t *controller)
/* Free periodic frame list */
free(phys_to_virt(EHCI_INST(controller)->operation->periodiclistbase));
+ /* Free dummy QH */
+ free(EHCI_INST(controller)->dummy_qh);
+
EHCI_INST(controller)->operation->configflag = 0;
}
@@ -521,9 +524,11 @@ static void *ehci_create_intr_queue(
int nothing_placed = 1;
u32 *const ps = (u32 *)phys_to_virt(EHCI_INST(ep->dev->controller)
->operation->periodiclistbase);
+ const u32 dummy_ptr = virt_to_phys(EHCI_INST(
+ ep->dev->controller)->dummy_qh) | PS_TYPE_QH;
for (i = 0; i < 1024; i += reqtiming) {
/* advance to the next free position */
- while ((i < 1024) && !(ps[i] & PS_TERMINATE)) ++i;
+ while ((i < 1024) && (ps[i] != dummy_ptr)) ++i;
if (i < 1024) {
ps[i] = virt_to_phys(&intrq->qh) | PS_TYPE_QH;
nothing_placed = 0;
@@ -547,9 +552,11 @@ static void ehci_destroy_intr_queue(endpoint_t *const ep, void *const queue)
int i;
u32 *const ps = (u32 *)phys_to_virt(EHCI_INST(
ep->dev->controller)->operation->periodiclistbase);
+ const u32 dummy_ptr = virt_to_phys(EHCI_INST(
+ ep->dev->controller)->dummy_qh) | PS_TYPE_QH;
for (i = 0; i < 1024; ++i) {
if ((ps[i] & PS_PTR_MASK) == virt_to_phys(&intrq->qh))
- ps[i] = PS_TERMINATE;
+ ps[i] = dummy_ptr;
}
/* wait 1ms for frame to end */
@@ -661,8 +668,19 @@ ehci_init (pcidev_t addr)
u32 *const periodic_list = (u32 *)memalign(4096, 1024 * sizeof(u32));
if (!periodic_list)
fatal("Not enough memory creating EHCI periodic frame list.\n");
+
+ /*
+ * Insert dummy QH in periodic frame list
+ * This helps with broken host controllers
+ * and doesn't violate the standard.
+ */
+ EHCI_INST(controller)->dummy_qh = (ehci_qh_t *)memalign(32, sizeof(ehci_qh_t));
+ memset(EHCI_INST(controller)->dummy_qh, 0,
+ sizeof(*EHCI_INST(controller)->dummy_qh));
+ EHCI_INST(controller)->dummy_qh->horiz_link_ptr = QH_TERMINATE;
for (i = 0; i < 1024; ++i)
- periodic_list[i] = PS_TERMINATE;
+ periodic_list[i] = virt_to_phys(EHCI_INST(controller)->dummy_qh)
+ | PS_TYPE_QH;
/* Make sure periodic schedule is disabled */
ehci_set_periodic_schedule(EHCI_INST(controller), 0);
diff --git a/payloads/libpayload/drivers/usb/ehci_private.h b/payloads/libpayload/drivers/usb/ehci_private.h
index a97336b492..3276e23edc 100644
--- a/payloads/libpayload/drivers/usb/ehci_private.h
+++ b/payloads/libpayload/drivers/usb/ehci_private.h
@@ -132,6 +132,7 @@ typedef volatile struct {
typedef struct ehci {
hc_cap_t *capabilities;
hc_op_t *operation;
+ ehci_qh_t *dummy_qh;
} ehci_t;
#define PS_TERMINATE 1