1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
|
/* 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 "console.h"
#include "task.h"
#include "timer.h"
#include "usb_mux.h"
#include "usb_pd.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, 0);
}
/*
* Main task entry point that handles PD interrupts for a single port
*
* @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);
struct {
int count;
timestamp_t time;
} storm_tracker[CONFIG_USB_PD_PORT_MAX_COUNT] = {};
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) {
/*
* 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);
}
}
}
}
}
|