From 2b470623642407eae6a35a89dad145adb004b3ef Mon Sep 17 00:00:00 2001 From: Caveh Jalali Date: Wed, 17 Feb 2021 03:17:55 -0800 Subject: common: Add new shared PD interrupt handler This adds a new TCPC interrupt handler task that knows how to deal with TCPCs that share a single interrupt line. ec.tasklist for this handler takes a port mask instead of a port number as a task startup argument. The main quirk of shared interrupts is that interrupts must be serviced on multiple devcies in order to clear the interrupt into the EC. BRANCH=none BUG=b:173575131 TEST=buildall passes Change-Id: I6f9f0ce471092741274d8a1542510d92100f6698 Signed-off-by: Caveh Jalali Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2700314 --- common/usbc_intr_task.c | 173 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 43 deletions(-) diff --git a/common/usbc_intr_task.c b/common/usbc_intr_task.c index cb59882d7a..7470696c18 100644 --- a/common/usbc_intr_task.c +++ b/common/usbc_intr_task.c @@ -38,6 +38,38 @@ void schedule_deferred_pd_interrupt(const int port) task_set_event(pd_int_task_id[port], PD_PROCESS_INTERRUPT); } +static struct { + int count; + timestamp_t time; +} storm_tracker[CONFIG_USB_PD_PORT_MAX_COUNT]; + +static void service_one_port(int port) +{ + timestamp_t now; + + tcpc_alert(port); + + now = get_time(); + if (timestamp_expired(storm_tracker[port].time, + &now)) { + /* Reset timer into future */ + storm_tracker[port].time.val = now.val + ALERT_STORM_INTERVAL; + + /* + * Start at 1 since we are processing an interrupt right + * now + */ + storm_tracker[port].count = 1; + } else if (++storm_tracker[port].count > ALERT_STORM_MAX_COUNT) { + CPRINTS("C%d: Interrupt storm detected." + " Disabling port temporarily", + port); + + pd_set_suspend(port, 1); + pd_deferred_resume(port); + } +} + /* * Main task entry point that handles PD interrupts for a single port * @@ -48,10 +80,6 @@ void pd_interrupt_handler_task(void *p) { const int port = (int) ((intptr_t) p); const int port_mask = (PD_STATUS_TCPC_ALERT_0 << port); - struct { - int count; - timestamp_t time; - } storm_tracker[CONFIG_USB_PD_PORT_MAX_COUNT] = {}; ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_MAX_COUNT); @@ -66,46 +94,105 @@ void pd_interrupt_handler_task(void *p) while (1) { const int evt = task_wait_event(-1); - if (evt & PD_PROCESS_INTERRUPT) { - /* - * While the interrupt signal is asserted; we have more - * work to do. This effectively makes the interrupt a - * level-interrupt instead of an edge-interrupt without - * having to enable/disable a real level-interrupt in - * multiple locations. - * - * Also, if the port is disabled do not process - * interrupts. Upon existing suspend, we schedule a - * PD_PROCESS_INTERRUPT to check if we missed anything. - */ - while ((tcpc_get_alert_status() & port_mask) && - pd_is_port_enabled(port)) { - timestamp_t now; - - tcpc_alert(port); - - now = get_time(); - if (timestamp_expired(storm_tracker[port].time, - &now)) { - /* Reset timer into future */ - storm_tracker[port].time.val = - now.val + ALERT_STORM_INTERVAL; - - /* - * Start at 1 since we are processing an - * interrupt right now - */ - storm_tracker[port].count = 1; - } else if (++storm_tracker[port].count > - ALERT_STORM_MAX_COUNT) { - CPRINTS("C%d: Interrupt storm detected." - " Disabling port temporarily", - port); - - pd_set_suspend(port, 1); - pd_deferred_resume(port); + if ((evt & PD_PROCESS_INTERRUPT) == 0) + continue; + /* + * While the interrupt signal is asserted; we have more + * work to do. This effectively makes the interrupt a + * level-interrupt instead of an edge-interrupt without + * having to enable/disable a real level-interrupt in + * multiple locations. + * + * Also, if the port is disabled do not process + * interrupts. Upon existing suspend, we schedule a + * PD_PROCESS_INTERRUPT to check if we missed anything. + */ + while ((tcpc_get_alert_status() & port_mask) && + pd_is_port_enabled(port)) { + + service_one_port(port); + } + } +} + +/* + * This code assumes port alert masks are adjacent to each other. + */ +BUILD_ASSERT(PD_STATUS_TCPC_ALERT_3 == (PD_STATUS_TCPC_ALERT_0 << 3)); + +/* + * Shared TCPC interrupt handler. The function argument in ec.tasklist + * is the mask of ports to handle. For example: + * + * BIT(USBC_PORT_C2) | BIT(USBC_PORT_C0) + * + * Note that this bitmask is 0-based while PD_STATUS_TCPC_ALERT_ + * is not. + */ + +void pd_shared_alert_task(void *p) +{ + const int sources_mask = (int) ((intptr_t) p); + int want_alerts = 0; + int port; + int port_mask; + + CPRINTS("%s: port mask 0x%02x", __func__, sources_mask); + + for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; ++port) { + if ((sources_mask & BIT(port)) == 0) + continue; + if (!board_is_usb_pd_port_present(port)) + continue; + + port_mask = PD_STATUS_TCPC_ALERT_0 << port; + want_alerts |= port_mask; + pd_int_task_id[port] = task_get_current(); + } + + if (want_alerts == 0) { + /* + * None of the configured alert sources are available. + */ + return; + } + + while (1) { + const int evt = task_wait_event(-1); + int have_alerts; + + if ((evt & PD_PROCESS_INTERRUPT) == 0) + continue; + + /* + * While the interrupt signal is asserted; we have more + * work to do. This effectively makes the interrupt a + * level-interrupt instead of an edge-interrupt without + * having to enable/disable a real level-interrupt in + * multiple locations. + * + * Also, if the port is disabled do not process + * interrupts. Upon existing suspend, we schedule a + * PD_PROCESS_INTERRUPT to check if we missed anything. + */ + do { + have_alerts = tcpc_get_alert_status(); + have_alerts &= want_alerts; + + for (port = 0; port < CONFIG_USB_PD_PORT_MAX_COUNT; + ++port) { + port_mask = PD_STATUS_TCPC_ALERT_0 << port; + if ((have_alerts & port_mask) == 0) { + /* skip quiet port */ + continue; + } + if (!pd_is_port_enabled(port)) { + /* filter out disabled port */ + have_alerts &= ~port_mask; + continue; } + service_one_port(port); } - } + } while (have_alerts != 0); } } -- cgit v1.2.1