You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
8.4 KiB
251 lines
8.4 KiB
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
* rebalance-interrupts:
|
|
*
|
|
* One-shot distribution of unassigned* IRQs to CPU cores.
|
|
* Useful for devices with the ARM-GIC-v3, as the Linux driver will take any
|
|
* interrupt assigned with an all-cores mask and always have it run on core 0.
|
|
*
|
|
* This should be run once, long enough after boot that all drivers have
|
|
* registered their interrupts.
|
|
*
|
|
* This program is configured to spread the load across all the cores in
|
|
* CPUFREQ policy 0. This is because other cores may be hotplugged in
|
|
* or out, and if hotplugged out the interrupts would be sent to core0 always.
|
|
*
|
|
* It might be wise to avoid core0 so that any later-added IRQs don't overcrowd
|
|
* core 0.
|
|
*
|
|
* Any program that has an actual IRQ related performance constraint should
|
|
* override any settings assigned by this and assign the IRQ to the same
|
|
* core as the code whose performance is impacted by the IRQ.
|
|
*
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
|
|
#include <iostream>
|
|
#include <list>
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
#define LOG_TAG "rebalance_interrupts"
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/format.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/strings.h>
|
|
|
|
|
|
#define POLICY0_CORES_PATH "/sys/devices/system/cpu/cpufreq/policy0/affected_cpus"
|
|
#define SYSFS_IRQDIR "/sys/kernel/irq"
|
|
#define PROC_IRQDIR "/proc/irq"
|
|
|
|
using android::base::ParseInt;
|
|
using android::base::ParseUint;
|
|
using android::base::ReadFileToString;
|
|
using android::base::Trim;
|
|
using android::base::WriteStringToFile;
|
|
using std::list;
|
|
using std::map;
|
|
using std::pair;
|
|
using std::string;
|
|
using std::vector;
|
|
|
|
// Return a vector of strings describing the affected CPUs for cpufreq
|
|
// Policy 0.
|
|
vector<int> Policy0AffectedCpus() {
|
|
string policy0_cores_unparsed;
|
|
if (!ReadFileToString(POLICY0_CORES_PATH, &policy0_cores_unparsed))
|
|
return vector<int>();
|
|
string policy0_trimmed = android::base::Trim(policy0_cores_unparsed);
|
|
vector<string> cpus_as_string = android::base::Split(policy0_trimmed, " ");
|
|
|
|
vector<int> cpus_as_int;
|
|
for (int i = 0; i < cpus_as_string.size(); ++i) {
|
|
int cpu;
|
|
if (!ParseInt(cpus_as_string[i].c_str(), &cpu))
|
|
return vector<int>();
|
|
cpus_as_int.push_back(cpu);
|
|
}
|
|
return cpus_as_int;
|
|
}
|
|
|
|
// Return a vector of strings describing the CPU masks for cpufreq Policy 0.
|
|
vector<string> Policy0CpuMasks() {
|
|
vector<int> cpus = Policy0AffectedCpus();
|
|
vector<string> cpu_masks;
|
|
for (int i = 0; i < cpus.size(); ++i)
|
|
cpu_masks.push_back(fmt::format("{0:02x}", 1 << cpus[i]));
|
|
return cpu_masks;
|
|
}
|
|
|
|
// Read the actions for the given irq# from sysfs, and add it to action_to_irq
|
|
bool AddEntryToIrqmap(const char* irq,
|
|
map<string, list<string>>& action_to_irqs) {
|
|
const string irq_base(SYSFS_IRQDIR "/");
|
|
string irq_actions_path = irq_base + irq + "/actions";
|
|
|
|
string irq_actions;
|
|
if (!ReadFileToString(irq_actions_path, &irq_actions))
|
|
return false;
|
|
|
|
irq_actions = Trim(irq_actions);
|
|
|
|
if (irq_actions == "(null)")
|
|
irq_actions = "";
|
|
|
|
action_to_irqs[irq_actions].push_back(irq);
|
|
|
|
return true;
|
|
}
|
|
|
|
// Get a mapping of driver "action" to IRQ#s for each IRQ# in
|
|
// SYSFS_IRQDIR.
|
|
bool GetIrqmap(map<string, list<string>>& action_to_irqs) {
|
|
bool some_success = false;
|
|
std::unique_ptr<DIR, decltype(&closedir)> irq_dir(opendir(SYSFS_IRQDIR), closedir);
|
|
if (!irq_dir) {
|
|
PLOG(ERROR) << "opening dir " SYSFS_IRQDIR;
|
|
return false;
|
|
}
|
|
|
|
struct dirent* entry;
|
|
while ((entry = readdir(irq_dir.get()))) {
|
|
|
|
// If the directory entry isn't a parsable number, skip it.
|
|
// . and .. get skipped here.
|
|
unsigned throwaway;
|
|
if (!ParseUint(entry->d_name, &throwaway))
|
|
continue;
|
|
|
|
some_success |= AddEntryToIrqmap(entry->d_name, action_to_irqs);
|
|
}
|
|
return some_success;
|
|
}
|
|
|
|
// Given a map of irq actions -> IRQs,
|
|
// find out which ones haven't been assigned and add those to
|
|
// rebalance_actions.
|
|
void FindUnassignedIrqs(const map<string, list<string>>& action_to_irqs,
|
|
list<pair<string, list<string>>>& rebalance_actions) {
|
|
for (const auto &action_to_irqs_entry: action_to_irqs) {
|
|
bool rebalance = true;
|
|
for (const auto& irq: action_to_irqs_entry.second) {
|
|
string smp_affinity;
|
|
string proc_path(PROC_IRQDIR "/");
|
|
proc_path += irq + "/smp_affinity";
|
|
ReadFileToString(proc_path, &smp_affinity);
|
|
smp_affinity = Trim(smp_affinity);
|
|
|
|
// Try to respect previoulsy set IRQ affinities.
|
|
// On ARM interrupt controllers under Linux, if an IRQ is assigned
|
|
// to more than one core it will only be assigned to the lowest core.
|
|
// Assume any IRQ which is set to more than one core in the lowest four
|
|
// CPUs hasn't been assigned and needs to be rebalanced.
|
|
if (smp_affinity.back() == '0' ||
|
|
smp_affinity.back() == '1' ||
|
|
smp_affinity.back() == '2' ||
|
|
smp_affinity.back() == '4' ||
|
|
smp_affinity.back() == '8') {
|
|
rebalance = false;
|
|
}
|
|
|
|
// Treat each unnamed action IRQ as independent.
|
|
if (action_to_irqs_entry.first.empty()) {
|
|
if (rebalance) {
|
|
pair<string, list<string>> empty_action_irq;
|
|
empty_action_irq.first = "";
|
|
empty_action_irq.second.push_back(irq);
|
|
rebalance_actions.push_back(empty_action_irq);
|
|
}
|
|
rebalance = true;
|
|
}
|
|
}
|
|
if (rebalance && !action_to_irqs_entry.first.empty()) {
|
|
rebalance_actions.push_back(std::make_pair(action_to_irqs_entry.first,
|
|
action_to_irqs_entry.second));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Read the file at `path`, Trim whitespace, see if it matches `expected_value`.
|
|
// Print the results to stdout.
|
|
void ReportIfAffinityUpdated(const std::string expected_value,
|
|
const std::string path) {
|
|
std::string readback, report;
|
|
ReadFileToString(path, &readback);
|
|
readback = Trim(readback);
|
|
if (readback != expected_value) {
|
|
report += "Unable to set ";
|
|
} else {
|
|
report += "Success setting ";
|
|
}
|
|
report += path;
|
|
report += ": found " + readback + " vs " + expected_value + "\n";
|
|
LOG(DEBUG) << report;
|
|
}
|
|
|
|
// Evenly distribute the IRQ actions across all the Policy0 CPUs.
|
|
// Assign all the IRQs of an action to a single CPU core.
|
|
bool RebalanceIrqs(const list<pair<string, list<string>>>& action_to_irqs) {
|
|
int mask_index = 0;
|
|
std::vector<std::string> affinity_masks = Policy0CpuMasks();
|
|
|
|
if (affinity_masks.empty()) {
|
|
LOG(ERROR) << "Unable to find Policy0 CPUs for IRQ assignment.";
|
|
return false;
|
|
}
|
|
|
|
for (const auto &action_to_irq: action_to_irqs) {
|
|
for (const auto& irq: action_to_irq.second) {
|
|
std::string affinity_path(PROC_IRQDIR "/");
|
|
affinity_path += irq + "/smp_affinity";
|
|
WriteStringToFile(affinity_masks[mask_index], affinity_path);
|
|
ReportIfAffinityUpdated(affinity_masks[mask_index], affinity_path);
|
|
}
|
|
mask_index = (mask_index + 1) % affinity_masks.size();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int main(int /* argc */, char* /* argv */[]) {
|
|
map<string, list<string>> irq_mapping;
|
|
list<pair<string, list<string>>> action_to_irqs;
|
|
|
|
// Find the mapping of "irq actions" to IRQs.
|
|
// Each IRQ has an assocatied irq_actions field, showing the actions
|
|
// associated with it. Multiple IRQs have the same actions.
|
|
// Generate the mapping of actions to IRQs with that action,
|
|
// as these IRQs should all be mapped to the same cores.
|
|
if (!GetIrqmap(irq_mapping)) {
|
|
LOG(ERROR) << "Unable to read IRQ mappings. Are you root?";
|
|
return 1;
|
|
}
|
|
|
|
// Some IRQs are already assigned to a subset of cores, usually for
|
|
// good reason (like some drivers have an IRQ per core, for per-core
|
|
// queues.) Find the set of IRQs that haven't been mapped to specific
|
|
// cores.
|
|
FindUnassignedIrqs(irq_mapping, action_to_irqs);
|
|
|
|
// Distribute the rebalancable IRQs across all cores.
|
|
return RebalanceIrqs(action_to_irqs) ? 0 : 1;
|
|
}
|
|
|