Add code to parse /proc/interrupts (and tests)
This sets us up to configure the interrupts automatically
Change-Id: Ibefb3e1b02badc3f14abf14e49333c9a8ed987f6
Signed-off-by: Austin Schuh <austin.linux@gmail.com>
diff --git a/aos/starter/irq_affinity_lib.cc b/aos/starter/irq_affinity_lib.cc
new file mode 100644
index 0000000..ccc4853
--- /dev/null
+++ b/aos/starter/irq_affinity_lib.cc
@@ -0,0 +1,288 @@
+#include "aos/starter/irq_affinity_lib.h"
+
+#include <fcntl.h>
+
+#include "absl/strings/escaping.h"
+#include "absl/strings/match.h"
+#include "absl/strings/str_split.h"
+#include "aos/scoped/scoped_fd.h"
+
+namespace aos {
+
+// Class to split strings by whitespace with absl::StrSplit.
+class ByWhitespace {
+ public:
+ // Returns the location of the next separator per the StrSplit API.
+ absl::string_view Find(absl::string_view text, size_t pos) const {
+ size_t count = 0;
+ const char *start = text.data() + text.size();
+ for (size_t i = pos; i < text.size(); ++i) {
+ if (text[i] == ' ') {
+ if (count == 0) {
+ start = text.data() + i;
+ }
+ ++count;
+ } else {
+ if (count > 0) {
+ break;
+ }
+ }
+ }
+ return std::string_view(start, count);
+ }
+};
+
+InterruptsStatus::InterruptsStatus() {
+ // 8k seems to be big enough to hold most things, so let's start there.
+ interrupts_content_.reserve(8192);
+}
+
+namespace {
+
+// Counts the number of "CPU" strings in the line without allocating.
+size_t CountCores(std::string_view line) {
+ size_t cpus_found = 0;
+ std::string_view::size_type start_pos = 0;
+ while (std::string_view::npos != (start_pos = line.find("CPU", start_pos))) {
+ start_pos += 3;
+ ++cpus_found;
+ }
+ return cpus_found;
+}
+
+} // namespace
+
+void InterruptsStatus::Update(std::string_view contents) {
+ size_t line_number = 0;
+ for (std::string_view line :
+ absl::StrSplit(contents, "\n", absl::SkipEmpty())) {
+ if (line_number == 0) {
+ // This is the CPUs line. Count our cores.
+ const size_t cpus_found = CountCores(line);
+
+ if (cpus_ == 0) {
+ // First time through.
+ cpus_ = cpus_found;
+ } else {
+ CHECK_EQ(cpus_found, cpus_) << ": Number of CPUs changed while running";
+ }
+ } else {
+ size_t element_number = 0;
+ InterruptState *state = nullptr;
+ bool new_element = false;
+ for (const std::string_view element :
+ absl::StrSplit(absl::StripAsciiWhitespace(line),
+ absl::MaxSplits(ByWhitespace(), cpus_ + 1))) {
+ if (element_number == 0) {
+ // Parse the interrupt number. This should either be in the form of:
+ // 23:
+ // or
+ // Err:
+ CHECK_EQ(element[element.size() - 1], ':')
+ << ": Missing trailing ':'";
+
+ int interrupt_number;
+ std::string_view interrupt_name =
+ element.substr(0, element.size() - 1);
+
+ // It's a named interrupt.
+ if (!absl::SimpleAtoi(interrupt_name, &interrupt_number)) {
+ interrupt_number = -1;
+ } else {
+ interrupt_name = "";
+ }
+
+ // Add a new element if we are too short, or if the interrupt changed.
+ if (states_.size() < line_number) {
+ new_element = true;
+ } else {
+ InterruptState *state = &states_[line_number - 1];
+ if (state->interrupt_number != interrupt_number ||
+ state->interrupt_name != interrupt_name) {
+ VLOG(1)
+ << "IRQ changed names... Blow away the end and try again.";
+ // This happens infrequently enough that it isn't worth trying to
+ // resize things. It may never happen while running. Nuke
+ // anything missing and retry.
+ states_.resize(line_number - 1);
+ new_element = true;
+ }
+ }
+
+ if (new_element) {
+ InterruptState new_state;
+ std::vector<unsigned int> irq_count(cpus_, 0);
+ states_.push_back(InterruptState{
+ .interrupt_number = interrupt_number,
+ .interrupt_name = interrupt_number == -1
+ ? std::string(interrupt_name)
+ : std::string(),
+ .count = std::move(irq_count),
+ .chip_name = std::string(),
+ .description = std::string(),
+ .hwirq = std::string(),
+ .actions = {},
+ });
+ }
+ state = &states_[line_number - 1];
+
+ } else if (element_number <= cpus_) {
+ // We are now parsing the count body. Keep updating the elements.
+ unsigned int interrupt_count;
+ CHECK(absl::SimpleAtoi(element, &interrupt_count))
+ << ": Failed to parse count " << interrupt_count;
+ state->count[element_number - 1] = interrupt_count;
+ } else if (element_number == cpus_ + 1) {
+ if (state->interrupt_number == -1) {
+ // Named interrupt, the rest of the string is the description.
+ if (new_element) {
+ state->description = std::string(element);
+ } else {
+ CHECK_EQ(state->description, element) << ": Description changed";
+ }
+ } else {
+ // Ok, the rest is now some properties of the interrupt.
+ size_t trailing_elements_count = 0;
+ for (std::string_view trailing_element :
+ absl::StrSplit(absl::StripAsciiWhitespace(element),
+ absl::MaxSplits(ByWhitespace(), 2))) {
+ if (trailing_elements_count == 0) {
+ // Chip name.
+ if (new_element) {
+ state->chip_name = std::string(trailing_element);
+ } else {
+ CHECK_EQ(state->chip_name, trailing_element)
+ << ": Chip changed names";
+ }
+ } else if (trailing_elements_count == 1) {
+ // Hardware IRQ
+ if (new_element) {
+ state->hwirq = std::string(trailing_element);
+ } else {
+ CHECK_EQ(state->hwirq, trailing_element)
+ << ": Hardware IRQ changed names";
+ }
+ } else {
+ // And then either "Level/Edge" and then the actions, or just
+ // the actions. Kernel has CONFIG_GENERIC_IRQ_SHOW_LEVEL
+ // enabled if the string starts with either.. Strip it until someone finds a use.
+ if (absl::StartsWith(trailing_element, "Level")) {
+ trailing_element =
+ absl::StripAsciiWhitespace(trailing_element.substr(5));
+ } else if (absl::StartsWith(trailing_element, "Edge")) {
+ trailing_element =
+ absl::StripAsciiWhitespace(trailing_element.substr(4));
+ }
+
+ // Split up the actions by ", " and stick them in.
+ if (new_element) {
+ state->actions = std::vector<std::string>(
+ absl::StrSplit(trailing_element, ", "));
+ } else {
+ size_t action_index = 0;
+ bool matches = true;
+ // Start by comparing. If we don't match, then set. This
+ // avoids an allocation if we can get away with it.
+ for (std::string_view action :
+ absl::StrSplit(trailing_element, ", ")) {
+ if (action_index >= state->actions.size()) {
+ matches = false;
+ break;
+ }
+ if (state->actions[action_index] != action) {
+ matches = false;
+ break;
+ }
+ ++action_index;
+ }
+ if (!matches) {
+ state->actions = std::vector<std::string>(
+ absl::StrSplit(trailing_element, ", "));
+ }
+ }
+ }
+ ++trailing_elements_count;
+ }
+ }
+ } else {
+ LOG(FATAL) << "Unexpected element, need to consume " << element;
+ }
+ ++element_number;
+ }
+
+ // Validate that everything makes sense and we have the elements expected.
+ if (state->interrupt_number != -1) {
+ CHECK_EQ(element_number, cpus_ + 2);
+ } else {
+ // Only these 3 interrupts are known to not be per core.
+ if (state->interrupt_name == "Err" || state->interrupt_name == "ERR" ||
+ state->interrupt_name == "MIS") {
+ if (new_element) {
+ CHECK_LE(element_number, cpus_ + 1);
+ state->count.resize(element_number - 1);
+ } else {
+ CHECK_EQ(state->count.size(), element_number - 1);
+ }
+ } else {
+ CHECK_EQ(element_number, cpus_ + 2);
+ }
+ }
+
+ if (VLOG_IS_ON(1)) {
+ if (state->interrupt_number == -1) {
+ LOG(INFO) << "IRQ: " << state->interrupt_name;
+ } else {
+ LOG(INFO) << "IRQ: " << state->interrupt_number;
+ }
+ for (unsigned int c : state->count) {
+ LOG(INFO) << " " << c;
+ }
+ if (!state->chip_name.empty()) {
+ LOG(INFO) << "chip_name \"" << state->chip_name << "\"";
+ }
+ if (!state->description.empty()) {
+ LOG(INFO) << "description \"" << state->description << "\"";
+ }
+ if (!state->hwirq.empty()) {
+ LOG(INFO) << "hwirq \"" << state->hwirq << "\"";
+ }
+ if (!state->actions.empty()) {
+ for (const std::string &action : state->actions) {
+ LOG(INFO) << " action \"" << action << "\"";
+ }
+ }
+ }
+ }
+
+ ++line_number;
+ }
+}
+
+void InterruptsStatus::Update() {
+ ScopedFD fd(open("/proc/interrupts", O_RDONLY));
+ size_t so_far = 0;
+ while (true) {
+ // Keep growing the size of interrupts_content_ until it holds the whole
+ // string.
+ size_t kStride = 8192;
+ if (interrupts_content_.capacity() < so_far + kStride) {
+ interrupts_content_.reserve(interrupts_content_.capacity() + kStride);
+ }
+
+ interrupts_content_.resize(interrupts_content_.capacity());
+
+ const ssize_t result = read(fd.get(), interrupts_content_.data() + so_far,
+ interrupts_content_.capacity() - so_far);
+ PCHECK(result >= 0) << ": reading from /proc/interrupts";
+ if (result == 0) {
+ break;
+ }
+ so_far += result;
+ }
+
+ interrupts_content_.resize(so_far);
+ Update(
+ std::string_view(interrupts_content_.data(), interrupts_content_.size()));
+}
+
+} // namespace aos