282 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			9.4 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * 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;
 | |
| }
 | |
| 
 | |
| void ChownIrqAffinity() {
 | |
|     std::unique_ptr<DIR, decltype(&closedir)> irq_dir(opendir(PROC_IRQDIR), closedir);
 | |
|     if (!irq_dir) {
 | |
|         PLOG(ERROR) << "opening dir " PROC_IRQDIR;
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     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;
 | |
| 
 | |
|         string affinity_path(PROC_IRQDIR "/");
 | |
|         affinity_path += entry->d_name;
 | |
|         affinity_path += "/smp_affinity";
 | |
|         chown(affinity_path.c_str(), 1000, 1000);
 | |
| 
 | |
|         string affinity_list_path(PROC_IRQDIR "/");
 | |
|         affinity_list_path += entry->d_name;
 | |
|         affinity_list_path += "/smp_affinity_list";
 | |
|         chown(affinity_list_path.c_str(), 1000, 1000);
 | |
|     }
 | |
| }
 | |
| 
 | |
| 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;
 | |
|   }
 | |
| 
 | |
|   // Change ownership of smp_affinity and smp_affinity_list handles
 | |
|   // from root to system.
 | |
|   ChownIrqAffinity();
 | |
| 
 | |
|   // 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;
 | |
| }
 | |
| 
 |