summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Huber <nico.huber@secunet.com>2023-05-19 20:19:56 +0000
committerFelix Held <felix-coreboot@felixheld.de>2023-06-12 17:00:28 +0000
commitedcde0ba7a152c07f869d02de4a3c33720e5da9b (patch)
treec918593d6b4fea0705b0eabc1bcdbe2a292e1fbc
parent4c0dc4ee918c3046719af868999785a381f06cc5 (diff)
libpayload/uhci: Re-write UHCI RH driver w/ generic_hub API
This is a complete rewrite of the UHCI root-hub driver, based on the xHCI one. We are doing things by the book as far as possible. One special case is uhci_rh_reset_port() which does the reset se- quencing that usually the hardware would do. This abandons some quirks of the old driver: * Ports are not disabled/re-enabled for every attachment anymore. * We solely rely on the Connect Status Change bit to track changes. * Further status changes are now deferred to the next polling round. The latter fixes endless loops in combination with commit 7faff543da (libpayload: usb: Detach unused USB devices). Change-Id: I5211728775eb94dfc23fa82ebf00fe5c99039709 Signed-off-by: Nico Huber <nico.huber@secunet.com> Reviewed-on: https://review.coreboot.org/c/coreboot/+/75504 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Julius Werner <jwerner@chromium.org> Reviewed-by: Paul Menzel <paulepanter@mailbox.org>
-rw-r--r--payloads/libpayload/drivers/usb/uhci_rh.c243
1 files changed, 91 insertions, 152 deletions
diff --git a/payloads/libpayload/drivers/usb/uhci_rh.c b/payloads/libpayload/drivers/usb/uhci_rh.c
index e7f0da7666..5f3d184cfd 100644
--- a/payloads/libpayload/drivers/usb/uhci_rh.c
+++ b/payloads/libpayload/drivers/usb/uhci_rh.c
@@ -1,6 +1,6 @@
/*
*
- * Copyright (C) 2008-2010 coresystems GmbH
+ * Copyright (C) 2013 secunet Security Networks AG
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -28,184 +28,123 @@
//#define USB_DEBUG
-#include <libpayload.h>
-#include "uhci.h"
+#include <stdint.h>
+#include <stdbool.h>
+#include <usb/usb.h>
+
+#include "generic_hub.h"
#include "uhci_private.h"
+#include "uhci.h"
-typedef struct {
- int port[2];
-} rh_inst_t;
+#define PORTSC(p) (PORTSC1 + ((p) - 1) * sizeof(uint16_t))
+#define PORTSC_CCS (1 << 0)
+#define PORTSC_CSC (1 << 1)
+#define PORTSC_PED (1 << 2)
+#define PORTSC_PEDC (1 << 3)
+#define PORTSC_LSDA (1 << 8)
+#define PORTSC_PR (1 << 9)
+#define PORTSC_SUSP (1 << 12)
+#define PORTSC_SC_BITS (PORTSC_CSC | PORTSC_PEDC)
-#define RH_INST(dev) ((rh_inst_t*)(dev)->data)
+static int
+uhci_rh_port_status_changed(usbdev_t *const dev, const int port)
+{
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port));
+ const bool changed = portsc & (PORTSC_CSC | PORTSC_PEDC);
+
+ /* always clear all the status change bits */
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc);
-static void
-uhci_rh_enable_port(usbdev_t *dev, int port)
+ return changed;
+}
+
+static int
+uhci_rh_port_connected(usbdev_t *const dev, const int port)
{
- u16 value;
- hci_t *controller = dev->controller;
- if (port == 1)
- port = PORTSC1;
- else if (port == 2)
- port = PORTSC2;
- else {
- usb_debug("Invalid port %d\n", port);
- return;
- }
-
- uhci_reg_write16(controller, port,
- uhci_reg_read16(controller, port) & ~(1 << 12)); /* wakeup */
-
- uhci_reg_write16(controller, port,
- uhci_reg_read16(controller, port) | 1 << 9); /* reset */
- mdelay(30); // >10ms
- uhci_reg_write16(controller, port,
- uhci_reg_read16(controller, port) & ~(1 << 9));
- mdelay(1); // >5.3us per spec, <3ms because some devices make trouble
-
- uhci_reg_write16(controller, port,
- uhci_reg_read16(controller, port) | 1 << 2); /* enable */
- /* wait for controller to enable port */
- /* TOTEST: how long to wait? 100ms for now */
- int timeout = 200; /* time out after 200 * 500us == 100ms */
- do {
- value = uhci_reg_read16(controller, port);
- udelay(500); timeout--;
- } while (((value & (1 << 2)) == 0) && (value & 0x01) && timeout);
- if (!timeout)
- usb_debug("Warning: uhci_rh: port enabling timed out.\n");
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port));
+ return !!(portsc & PORTSC_CCS);
}
-/* disable root hub */
-static void
-uhci_rh_disable_port(usbdev_t *dev, int port)
+static int
+uhci_rh_port_enabled(usbdev_t *const dev, const int port)
{
- hci_t *controller = dev->controller;
- if (port == 1)
- port = PORTSC1;
- else if (port == 2)
- port = PORTSC2;
- else {
- usb_debug("Invalid port %d\n", port);
- return;
- }
- uhci_reg_write16(controller, port,
- uhci_reg_read16(controller, port) & ~4);
- u16 value;
- /* wait for controller to disable port */
- /* TOTEST: how long to wait? 100ms for now */
- int timeout = 200; /* time out after 200 * 500us == 100ms */
- do {
- value = uhci_reg_read16(controller, port);
- udelay(500); timeout--;
- } while (((value & (1 << 2)) != 0) && timeout);
- if (!timeout)
- usb_debug("Warning: uhci_rh: port disabling timed out.\n");
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port));
+ return !(portsc & PORTSC_SUSP) && (portsc & PORTSC_PED);
}
-static void
-uhci_rh_scanport(usbdev_t *dev, int port)
+static usb_speed
+uhci_rh_port_speed(usbdev_t *const dev, const int port)
{
- int portsc, offset;
- if (port == 1) {
- portsc = PORTSC1;
- offset = 0;
- } else if (port == 2) {
- portsc = PORTSC2;
- offset = 1;
- } else {
- usb_debug("Invalid port %d\n", port);
- return;
- }
- int devno = RH_INST(dev)->port[offset];
- if ((devno != -1) && (dev->controller->devices[devno] != 0)) {
- usb_detach_device(dev->controller, devno);
- RH_INST(dev)->port[offset] = -1;
- }
- uhci_reg_write16(dev->controller, portsc,
- uhci_reg_read16(dev->controller, portsc) | (1 << 3) | (1 << 2)); // clear port state change, enable port
-
- mdelay(100); // wait for signal to stabilize
-
- if ((uhci_reg_read16(dev->controller, portsc) & 1) != 0) {
- // device attached
-
- uhci_rh_disable_port(dev, port);
- uhci_rh_enable_port(dev, port);
-
- usb_speed speed = ((uhci_reg_read16(dev->controller, portsc) >> 8) & 1);
-
- RH_INST(dev)->port[offset] = usb_attach_device(dev->controller, dev->address, portsc, speed);
- }
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port));
+ const bool low_speed = portsc & PORTSC_LSDA;
+
+ return low_speed ? LOW_SPEED : FULL_SPEED;
}
static int
-uhci_rh_report_port_changes(usbdev_t *dev)
+uhci_rh_enable_port(usbdev_t *const dev, const int port)
{
- u16 stored, real;
-
- stored = (RH_INST(dev)->port[0] == -1);
- real = ((uhci_reg_read16(dev->controller, PORTSC1) & 1) == 0);
- if (stored != real) {
- usb_debug("change on port 1\n");
- return 1;
- }
-
- stored = (RH_INST(dev)->port[1] == -1);
- real = ((uhci_reg_read16(dev->controller, PORTSC2) & 1) == 0);
- if (stored != real) {
- usb_debug("change on port 2\n");
- return 2;
- }
-
- // maybe detach+attach happened between two scans?
-
- if ((uhci_reg_read16(dev->controller, PORTSC1) & 2) > 0) {
- usb_debug("possibly re-attached on port 1\n");
- return 1;
- }
- if ((uhci_reg_read16(dev->controller, PORTSC2) & 2) > 0) {
- usb_debug("possibly re-attached on port 2\n");
- return 2;
- }
-
- // no change
- return -1;
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port)) & ~PORTSC_SUSP;
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc & ~(PORTSC_SC_BITS | PORTSC_SUSP));
+ uhci_reg_write16(dev->controller, PORTSC(port), (portsc & ~PORTSC_SC_BITS) | PORTSC_PED);
+ return 0;
}
-static void
-uhci_rh_destroy(usbdev_t *dev)
+static int
+uhci_rh_disable_port(usbdev_t *const dev, const int port)
{
- usb_detach_device(dev->controller, 1);
- usb_detach_device(dev->controller, 2);
- uhci_rh_disable_port(dev, 1);
- uhci_rh_disable_port(dev, 2);
- free(RH_INST(dev));
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port));
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc & ~(PORTSC_SC_BITS | PORTSC_PED));
+ return 0;
}
-static void
-uhci_rh_poll(usbdev_t *dev)
+static int
+uhci_rh_reset_port(usbdev_t *const dev, const int port)
{
- int port;
- while ((port = uhci_rh_report_port_changes(dev)) != -1)
- uhci_rh_scanport(dev, port);
+ const u16 portsc = uhci_reg_read16(dev->controller, PORTSC(port)) & ~PORTSC_PR;
+
+ /* Trigger port reset. */
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc | PORTSC_PR);
+
+ /* Wait for 15ms (usb20 spec 11.5.1.5: reset should take 10 to 20ms). */
+ mdelay(15);
+
+ /* Clear port reset. */
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc & ~PORTSC_PR);
+
+ udelay(10); /* Linux waits this long. */
+
+ /* Enable port. */
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc | PORTSC_PED);
+
+ /* Clear status-change bits one last time. */
+ uhci_reg_write16(dev->controller, PORTSC(port), portsc | PORTSC_PED | PORTSC_SC_BITS);
+
+ return 0;
}
+static const generic_hub_ops_t uhci_rh_ops = {
+ .hub_status_changed = NULL,
+ .port_status_changed = uhci_rh_port_status_changed,
+ .port_connected = uhci_rh_port_connected,
+ .port_in_reset = NULL,
+ .port_enabled = uhci_rh_port_enabled,
+ .port_speed = uhci_rh_port_speed,
+ .enable_port = uhci_rh_enable_port,
+ .disable_port = uhci_rh_disable_port,
+ .start_port_reset = NULL,
+ .reset_port = uhci_rh_reset_port,
+};
+
void
uhci_rh_init(usbdev_t *dev)
{
- dev->destroy = uhci_rh_destroy;
- dev->poll = uhci_rh_poll;
-
- uhci_rh_enable_port(dev, 1);
- uhci_rh_enable_port(dev, 2);
- dev->data = xmalloc(sizeof(rh_inst_t));
-
- RH_INST(dev)->port[0] = -1;
- RH_INST(dev)->port[1] = -1;
-
- /* we can set them here because a root hub _really_ shouldn't
- appear elsewhere */
+ /* we can set them here because a root hub _really_ shouldn't appear elsewhere */
dev->address = 0;
dev->hub = -1;
dev->port = -1;
+
+ generic_hub_init(dev, 2, &uhci_rh_ops);
+
+ usb_debug("UHCI: root hub init done\n");
}