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