/* Copyright 2020 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ /* High-priority interrupt tasks implementations */ #include #include "builtin/assert.h" #include "common.h" #include "compile_time_macros.h" #include "console.h" #include "ec_commands.h" #include "task.h" #include "tcpm/tcpm.h" #include "timer.h" #include "usb_pd.h" #include "usb_pd_tcpm.h" #define CPRINTF(format, args...) cprintf(CC_USBPD, format, ##args) #define CPRINTS(format, args...) cprints(CC_USBPD, format, ##args) /* Events for pd_interrupt_handler_task */ #define PD_PROCESS_INTERRUPT BIT(0) /* * Theoretically, we may need to support up to 480 USB-PD packets per second for * intensive operations such as FW update over PD. This value has tested well * preventing watchdog resets with a single bad port partner plugged in. */ #define ALERT_STORM_MAX_COUNT 480 #define ALERT_STORM_INTERVAL SECOND static uint8_t pd_int_task_id[CONFIG_USB_PD_PORT_MAX_COUNT]; void schedule_deferred_pd_interrupt(const int port) { /* * Don't set event to idle task if task id is 0. This happens when * not all the port have pd int task, the pd_int_task_id of port * that doesn't have pd int task is 0. */ if (pd_int_task_id[port] != 0) 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); } } __overridable void board_process_pd_alert(int port) { } /* * Main task entry point that handles PD interrupts for a single port. These * interrupts usually come from a TCPC, but may also come from PD-related chips * sharing the TCPC interrupt line. * * @param p The PD port number for which to handle interrupts (pointer is * reinterpreted as an integer directly). */ void pd_interrupt_handler_task(void *p) { const int port = (int)((intptr_t)p); const int port_mask = (PD_STATUS_TCPC_ALERT_0 << port); ASSERT(port >= 0 && port < CONFIG_USB_PD_PORT_MAX_COUNT); /* * If port does not exist, return */ if (port >= board_get_usb_pd_port_count()) return; pd_int_task_id[port] = task_get_current(); while (1) { const int evt = task_wait_event(-1); 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); } board_process_pd_alert(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); } }