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