blob: 2128f52a592f58bde497fdc40360543f4a112e4b [file] [log] [blame]
Austin Schuh2ec71fd2022-12-30 14:48:59 -08001#include "aos/starter/irq_affinity_lib.h"
2
3#include <fcntl.h>
4
5#include "absl/strings/escaping.h"
6#include "absl/strings/match.h"
7#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07008
Austin Schuh2ec71fd2022-12-30 14:48:59 -08009#include "aos/scoped/scoped_fd.h"
10
11namespace aos {
12
13// Class to split strings by whitespace with absl::StrSplit.
14class ByWhitespace {
15 public:
16 // Returns the location of the next separator per the StrSplit API.
17 absl::string_view Find(absl::string_view text, size_t pos) const {
18 size_t count = 0;
19 const char *start = text.data() + text.size();
20 for (size_t i = pos; i < text.size(); ++i) {
21 if (text[i] == ' ') {
22 if (count == 0) {
23 start = text.data() + i;
24 }
25 ++count;
26 } else {
27 if (count > 0) {
28 break;
29 }
30 }
31 }
32 return std::string_view(start, count);
33 }
34};
35
36InterruptsStatus::InterruptsStatus() {
37 // 8k seems to be big enough to hold most things, so let's start there.
38 interrupts_content_.reserve(8192);
39}
40
41namespace {
42
43// Counts the number of "CPU" strings in the line without allocating.
44size_t CountCores(std::string_view line) {
45 size_t cpus_found = 0;
46 std::string_view::size_type start_pos = 0;
47 while (std::string_view::npos != (start_pos = line.find("CPU", start_pos))) {
48 start_pos += 3;
49 ++cpus_found;
50 }
51 return cpus_found;
52}
53
54} // namespace
55
56void InterruptsStatus::Update(std::string_view contents) {
57 size_t line_number = 0;
58 for (std::string_view line :
59 absl::StrSplit(contents, "\n", absl::SkipEmpty())) {
60 if (line_number == 0) {
61 // This is the CPUs line. Count our cores.
62 const size_t cpus_found = CountCores(line);
63
64 if (cpus_ == 0) {
65 // First time through.
66 cpus_ = cpus_found;
67 } else {
68 CHECK_EQ(cpus_found, cpus_) << ": Number of CPUs changed while running";
69 }
70 } else {
71 size_t element_number = 0;
72 InterruptState *state = nullptr;
73 bool new_element = false;
74 for (const std::string_view element :
75 absl::StrSplit(absl::StripAsciiWhitespace(line),
76 absl::MaxSplits(ByWhitespace(), cpus_ + 1))) {
77 if (element_number == 0) {
78 // Parse the interrupt number. This should either be in the form of:
79 // 23:
80 // or
81 // Err:
82 CHECK_EQ(element[element.size() - 1], ':')
83 << ": Missing trailing ':'";
84
85 int interrupt_number;
86 std::string_view interrupt_name =
87 element.substr(0, element.size() - 1);
88
89 // It's a named interrupt.
90 if (!absl::SimpleAtoi(interrupt_name, &interrupt_number)) {
91 interrupt_number = -1;
92 } else {
93 interrupt_name = "";
94 }
95
96 // Add a new element if we are too short, or if the interrupt changed.
97 if (states_.size() < line_number) {
98 new_element = true;
99 } else {
100 InterruptState *state = &states_[line_number - 1];
101 if (state->interrupt_number != interrupt_number ||
102 state->interrupt_name != interrupt_name) {
103 VLOG(1)
104 << "IRQ changed names... Blow away the end and try again.";
105 // This happens infrequently enough that it isn't worth trying to
106 // resize things. It may never happen while running. Nuke
107 // anything missing and retry.
108 states_.resize(line_number - 1);
109 new_element = true;
110 }
111 }
112
113 if (new_element) {
114 InterruptState new_state;
115 std::vector<unsigned int> irq_count(cpus_, 0);
116 states_.push_back(InterruptState{
117 .interrupt_number = interrupt_number,
118 .interrupt_name = interrupt_number == -1
119 ? std::string(interrupt_name)
120 : std::string(),
121 .count = std::move(irq_count),
122 .chip_name = std::string(),
123 .description = std::string(),
124 .hwirq = std::string(),
125 .actions = {},
126 });
127 }
128 state = &states_[line_number - 1];
129
130 } else if (element_number <= cpus_) {
131 // We are now parsing the count body. Keep updating the elements.
132 unsigned int interrupt_count;
133 CHECK(absl::SimpleAtoi(element, &interrupt_count))
134 << ": Failed to parse count " << interrupt_count;
135 state->count[element_number - 1] = interrupt_count;
136 } else if (element_number == cpus_ + 1) {
137 if (state->interrupt_number == -1) {
138 // Named interrupt, the rest of the string is the description.
139 if (new_element) {
140 state->description = std::string(element);
141 } else {
142 CHECK_EQ(state->description, element) << ": Description changed";
143 }
144 } else {
145 // Ok, the rest is now some properties of the interrupt.
146 size_t trailing_elements_count = 0;
147 for (std::string_view trailing_element :
148 absl::StrSplit(absl::StripAsciiWhitespace(element),
149 absl::MaxSplits(ByWhitespace(), 2))) {
150 if (trailing_elements_count == 0) {
151 // Chip name.
152 if (new_element) {
153 state->chip_name = std::string(trailing_element);
154 } else {
155 CHECK_EQ(state->chip_name, trailing_element)
156 << ": Chip changed names";
157 }
158 } else if (trailing_elements_count == 1) {
159 // Hardware IRQ
160 if (new_element) {
161 state->hwirq = std::string(trailing_element);
162 } else {
163 CHECK_EQ(state->hwirq, trailing_element)
164 << ": Hardware IRQ changed names";
165 }
166 } else {
167 // And then either "Level/Edge" and then the actions, or just
168 // the actions. Kernel has CONFIG_GENERIC_IRQ_SHOW_LEVEL
Philipp Schrader790cb542023-07-05 21:06:52 -0700169 // enabled if the string starts with either.. Strip it until
170 // someone finds a use.
Austin Schuh2ec71fd2022-12-30 14:48:59 -0800171 if (absl::StartsWith(trailing_element, "Level")) {
172 trailing_element =
173 absl::StripAsciiWhitespace(trailing_element.substr(5));
174 } else if (absl::StartsWith(trailing_element, "Edge")) {
175 trailing_element =
176 absl::StripAsciiWhitespace(trailing_element.substr(4));
177 }
178
179 // Split up the actions by ", " and stick them in.
180 if (new_element) {
181 state->actions = std::vector<std::string>(
182 absl::StrSplit(trailing_element, ", "));
183 } else {
184 size_t action_index = 0;
185 bool matches = true;
186 // Start by comparing. If we don't match, then set. This
187 // avoids an allocation if we can get away with it.
188 for (std::string_view action :
189 absl::StrSplit(trailing_element, ", ")) {
190 if (action_index >= state->actions.size()) {
191 matches = false;
192 break;
193 }
194 if (state->actions[action_index] != action) {
195 matches = false;
196 break;
197 }
198 ++action_index;
199 }
200 if (!matches) {
201 state->actions = std::vector<std::string>(
202 absl::StrSplit(trailing_element, ", "));
203 }
204 }
205 }
206 ++trailing_elements_count;
207 }
208 }
209 } else {
210 LOG(FATAL) << "Unexpected element, need to consume " << element;
211 }
212 ++element_number;
213 }
214
215 // Validate that everything makes sense and we have the elements expected.
216 if (state->interrupt_number != -1) {
217 CHECK_EQ(element_number, cpus_ + 2);
218 } else {
219 // Only these 3 interrupts are known to not be per core.
220 if (state->interrupt_name == "Err" || state->interrupt_name == "ERR" ||
221 state->interrupt_name == "MIS") {
222 if (new_element) {
223 CHECK_LE(element_number, cpus_ + 1);
224 state->count.resize(element_number - 1);
225 } else {
226 CHECK_EQ(state->count.size(), element_number - 1);
227 }
228 } else {
229 CHECK_EQ(element_number, cpus_ + 2);
230 }
231 }
232
233 if (VLOG_IS_ON(1)) {
234 if (state->interrupt_number == -1) {
235 LOG(INFO) << "IRQ: " << state->interrupt_name;
236 } else {
237 LOG(INFO) << "IRQ: " << state->interrupt_number;
238 }
239 for (unsigned int c : state->count) {
240 LOG(INFO) << " " << c;
241 }
242 if (!state->chip_name.empty()) {
243 LOG(INFO) << "chip_name \"" << state->chip_name << "\"";
244 }
245 if (!state->description.empty()) {
246 LOG(INFO) << "description \"" << state->description << "\"";
247 }
248 if (!state->hwirq.empty()) {
249 LOG(INFO) << "hwirq \"" << state->hwirq << "\"";
250 }
251 if (!state->actions.empty()) {
252 for (const std::string &action : state->actions) {
253 LOG(INFO) << " action \"" << action << "\"";
254 }
255 }
256 }
257 }
258
259 ++line_number;
260 }
261}
262
263void InterruptsStatus::Update() {
264 ScopedFD fd(open("/proc/interrupts", O_RDONLY));
265 size_t so_far = 0;
266 while (true) {
267 // Keep growing the size of interrupts_content_ until it holds the whole
268 // string.
269 size_t kStride = 8192;
270 if (interrupts_content_.capacity() < so_far + kStride) {
271 interrupts_content_.reserve(interrupts_content_.capacity() + kStride);
272 }
273
274 interrupts_content_.resize(interrupts_content_.capacity());
275
276 const ssize_t result = read(fd.get(), interrupts_content_.data() + so_far,
277 interrupts_content_.capacity() - so_far);
278 PCHECK(result >= 0) << ": reading from /proc/interrupts";
279 if (result == 0) {
280 break;
281 }
282 so_far += result;
283 }
284
285 interrupts_content_.resize(so_far);
286 Update(
287 std::string_view(interrupts_content_.data(), interrupts_content_.size()));
288}
289
290} // namespace aos