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