From 256651c94e720e7999251f556ac7c84eada35b2c Mon Sep 17 00:00:00 2001 From: Deewan Singh Date: Tue, 17 Feb 2026 13:29:21 +0530 Subject: [PATCH 1/3] ResourcesConfig: Add resources managed for RT benchmarking(cyclictest) Signed-off-by: Deewan Singh --- Configs/ResourcesConfig.yaml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Configs/ResourcesConfig.yaml b/Configs/ResourcesConfig.yaml index 8518909..0ed2d65 100644 --- a/Configs/ResourcesConfig.yaml +++ b/Configs/ResourcesConfig.yaml @@ -77,11 +77,11 @@ ResourceConfigs: Policy: higher_is_better ApplyType: "global" - # Reserve "80" for Cyclic Tests (RT Benchmarking) + # Reserve "80" for RT Benchmarking(cyclictest) - ResType: "0x80" ResID: "0x0000" - Name: "RES_CHANGE_GOV_PERF" - Path: "" + Name: "RES_TIMER_MIGRATION" + Path: "/proc/sys/kernel/timer_migration" Supported: true Permissions: third_party Modes: ["display_on", "doze"] @@ -90,8 +90,8 @@ ResourceConfigs: - ResType: "0x80" ResID: "0x0001" - Name: "RES_TIMER_MIGRATION" - Path: "/proc/sys/kernel/timer_migration" + Name: "RES_CPU_FREQ_GOV" + Path: "" Supported: true Permissions: third_party Modes: ["display_on", "doze"] @@ -100,6 +100,16 @@ ResourceConfigs: - ResType: "0x80" ResID: "0x0002" + Name: "RES_IRQ_AFFINITY" + Path: "" + Supported: true + Permissions: third_party + Modes: ["display_on", "doze"] + Policy: pass_through + ApplyType: "global" + + - ResType: "0x80" + ResID: "0x0003" Name: "RES_CPU_WQ_AFFINITY" Path: "" Supported: true From a34cc936234554dc0d4a9bf0f3b862df707ee041 Mon Sep 17 00:00:00 2001 From: Deewan Singh Date: Tue, 17 Feb 2026 13:31:46 +0530 Subject: [PATCH 2/3] SignalsConfig: Add resources and Timeout as INF(-1) under RT Trigger signal Signed-off-by: Deewan Singh --- Configs/SignalsConfig.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Configs/SignalsConfig.yaml b/Configs/SignalsConfig.yaml index a94e7a0..a20df1b 100644 --- a/Configs/SignalsConfig.yaml +++ b/Configs/SignalsConfig.yaml @@ -13,11 +13,10 @@ SignalConfigs: Name: RT_TRIGGER Enable: true Permissions: ["system", "third_party"] + Timeout: -1 Resources: + - {ResCode: "0x00030003", Values: [-1]} - {ResCode: "0x00800000", Values: [0]} - {ResCode: "0x00800001", Values: [0]} - {ResCode: "0x00800002", Values: [0]} - - {ResCode: "0x00030003", Values: [-1]} - - {ResCode: "0x00040003", ResInfo: "0x00000000", Values: [1]} - - {ResCode: "0x00040004", ResInfo: "0x00000100", Values: [1]} - - {ResCode: "0x00040005", ResInfo: "0x00000200", Values: [1]} + - {ResCode: "0x00800003", Values: [0]} From e0f6beea0196bc9d174b85ec8860cc4796fd492e Mon Sep 17 00:00:00 2001 From: Deewan Singh Date: Wed, 18 Feb 2026 13:03:32 +0530 Subject: [PATCH 3/3] PreemptRtExtn: Add and register applier and tear callback for irqAffinity and CPU WQ This adds the registration and defination for irq affinity & cpu workqueue applier and tear callbacks. Update the cpufreq governor callbacks. Add logging support. Signed-off-by: Deewan Singh --- Extensions/PreemptRtExtn.cpp | 518 ++++++++++++++++++++++++++++++++--- 1 file changed, 476 insertions(+), 42 deletions(-) diff --git a/Extensions/PreemptRtExtn.cpp b/Extensions/PreemptRtExtn.cpp index 167dc25..a1691ec 100644 --- a/Extensions/PreemptRtExtn.cpp +++ b/Extensions/PreemptRtExtn.cpp @@ -2,85 +2,519 @@ // SPDX-License-Identifier: BSD-3-Clause-Clear #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include -#include +#include +#include +#include #include #include -#include "Helpers.h" +#include +#include +#include #define POLICY_DIR_PATH "/sys/devices/system/cpu/cpufreq/" -#define WORKQUEUE_DIR_PATH "/sys/devices/virtual/workqueue/" +#define IRQ_DIR_PATH "/proc/irq/" +#define WQ_DIR_PATH "/sys/devices/virtual/workqueue/" + +// --------------------------- +// Conditional logging (URM_EXT_LOG_RT) +// --------------------------- +static bool gLogInit = false; +static bool gLogEnabled = false; + +static bool parseBoolEnv(const char* v) { + if (!v) return false; + return (!strcasecmp(v, "1") || !strcasecmp(v, "true") || + !strcasecmp(v, "on") || !strcasecmp(v, "yes") || + !strcasecmp(v, "y")); +} + +static inline bool isLogEnabled() { + if (!gLogInit) { + gLogEnabled = parseBoolEnv(std::getenv("URM_EXT_RT")); + gLogInit = true; + } + return gLogEnabled; +} + +static void logLine(const std::string& msg) { + if (!isLogEnabled()) return; + static bool opened = false; + if (!opened) { + openlog("URM-EXT-RT", LOG_PID | LOG_CONS, LOG_DAEMON); + opened = true; + } + + syslog(LOG_INFO, "%s", msg.c_str()); +} + +// --------------------------- +// Basic I/O helpers +// --------------------------- +static int writeAttr(const std::string& path, const std::string& value) { + int fd = ::open(path.c_str(), O_WRONLY | O_TRUNC); + if (fd < 0) { + if (isLogEnabled()) { + logLine("open fail: " + path + " errno=" + std::to_string(errno) + " (" + std::string(strerror(errno)) + ")"); + } + return errno; + } + std::string buf = value; + if (buf.empty() || buf.back() != '\n') buf.push_back('\n'); + ssize_t n = ::write(fd, buf.c_str(), buf.size()); + if (n < 0) { + if (isLogEnabled()) { + logLine("write fail: " + path + " errno=" + std::to_string(errno) + " (" + std::string(strerror(errno)) + ")"); + } + ::close(fd); + return errno; + } + ::close(fd); + if (isLogEnabled()) { + logLine("wrote: " + path + " <= \"" + value + "\""); + } + return 0; +} + +static bool readAttr(const std::string& path, std::string* out) { + std::ifstream ifs(path); + if (!ifs.is_open()) return false; + std::stringstream ss; ss << ifs.rdbuf(); + *out = ss.str(); + while (!out->empty() && (out->back() == '\n' || out->back() == '\r')) out->pop_back(); + return true; +} + +static bool isWritable(const std::string& path) { + return access(path.c_str(), W_OK) == 0; +} + +// --------------------------- +// Small string helpers +// --------------------------- +static std::string trim(const std::string& s) { + size_t b = 0, e = s.size(); + while (b < e && std::isspace(static_cast(s[b]))) ++b; + while (e > b && std::isspace(static_cast(s[e - 1]))) --e; + return s.substr(b, e - b); +} + +static std::string toLower(std::string s) { + for (auto &c : s) c = static_cast(std::tolower(static_cast(c))); + return s; +} + +// --------------------------- +// CPU list parsing & mask building +// --------------------------- +static std::vector parseCpuList(const std::string& cpuListCsv) { + std::vector cpus; + std::string token; + std::stringstream ss(cpuListCsv); + while (std::getline(ss, token, ',')) { + token = trim(token); + if (token.empty()) continue; + auto dash = token.find('-'); + if (dash != std::string::npos) { + std::string a = trim(token.substr(0, dash)); + std::string b = trim(token.substr(dash + 1)); + char* endp1 = nullptr; char* endp2 = nullptr; + long lo = std::strtol(a.c_str(), &endp1, 10); + long hi = std::strtol(b.c_str(), &endp2, 10); + if (*endp1 == '\0' && *endp2 == '\0' && lo >= 0 && hi >= lo) { + for (long v = lo; v <= hi; ++v) cpus.push_back(static_cast(v)); + } + } else { + char* endp = nullptr; + long v = std::strtol(token.c_str(), &endp, 10); + if (*endp == '\0' && v >= 0) { + cpus.push_back(static_cast(v)); + } + } + } + return cpus; +} + +static std::string cpuListToHexMask64(const std::vector& cpus) { + uint64_t mask = 0; + for (int cpu : cpus) { + if (cpu >= 0 && cpu < 64) { + mask |= (1ULL << static_cast(cpu)); + } + } + std::ostringstream oss; + oss << std::hex << std::nouppercase; + if (mask == 0) { + oss << "0"; + } else { + oss << mask; + } + return oss.str(); +} + +static std::string csvToHexMask(const std::string& csv) { + return cpuListToHexMask64(parseCpuList(csv)); +} + +// --------------------------- +// Hostname / Machine (exact, case-sensitive) +// --------------------------- +static std::string readFileIfExists(const std::string& path) { + std::string s; + if (readAttr(path, &s)) return s; + return {}; +} + +static std::string getHostname() { + char buf[256] = {0}; + if (gethostname(buf, sizeof(buf) - 1) == 0 && buf[0] != '\0') { + return std::string(buf); + } + std::string h = readFileIfExists("/etc/hostname"); + if (!h.empty()) return h; + struct utsname u{}; + if (uname(&u) == 0) return std::string(u.nodename); + return {}; +} + +// --------------------------- +// +// Key = exact hostname / machine; +// Value = { irqMaskSrc, irqIsHex, wqMaskSrc, wqIsHex } +// +// --------------------------- +struct MaskEntry { + const char* irqSrc; // CSV (e.g. "0-6") or HEX (e.g. "f7") + bool irqIsHex; + const char* wqSrc; // CSV or HEX + bool wqIsHex; +}; + +// Hostname-based policy +static const std::map kHostPolicyMap = { + // Hostnames that use IRQ: 0,1,2,4,5,6,7 and WQ: f7 + { "iq-8275-evk", { "0,1,2,4,5,6,7", false, "f7", true } }, + { "qcs8300-ride-sx", { "0,1,2,4,5,6,7", false, "f7", true } }, + + // Hostnames that use IRQ: 0-6 and WQ: 7f + { "iq-9075-qvk", { "0-6", false, "7f", true } }, + { "qcs9100-ride-sx", { "0-6", false, "7f", true } }, + { "qcm6490-idp", { "0-6", false, "7f", true } }, + { "rb3gen2-core-kit", { "0-6", false, "7f", true } }, +}; + +// Machine-based policy +static const std::map kMachinePolicyMap = { + { "QCS8275", { "0,1,2,4,5,6,7", false, "f7", true } }, + { "QCS8300", { "0,1,2,4,5,6,7", false, "f7", true } }, + + { "QCS9075", { "0-6", false, "7f", true } }, + { "QCM6490", { "0-6", false, "7f", true } }, + { "QCS9100", { "0-6", false, "7f", true } }, + { "QCS6490", { "0-6", false, "7f", true } }, +}; + +// Default masks when no map entry matches +static const MaskEntry kDefaultMask = { "0-6", false, "7f", true }; + +// --------------------------- +// Resolve final masks (hex strings) using the maps +// --------------------------- +struct ResolvedMasks { + std::string irqHex; + std::string wqHex; + std::string matchedKey; + std::string matchedScope; +}; + +static std::string strip0xLower(std::string s) { + s = trim(s); + if (s.size() >= 2 && s[0]=='0' && (s[1]=='x' || s[1]=='X')) s = s.substr(2); + return toLower(s); +} + +static ResolvedMasks resolveMasks() { + const std::string host = trim(getHostname()); + const std::string machine = trim(readFileIfExists("/sys/devices/soc0/machine")); + + // 1) Hostname exact match + { + auto it = kHostPolicyMap.find(host); + if (it != kHostPolicyMap.end()) { + const MaskEntry& e = it->second; + ResolvedMasks r; + r.irqHex = e.irqIsHex ? strip0xLower(e.irqSrc) : csvToHexMask(e.irqSrc); + r.wqHex = e.wqIsHex ? strip0xLower(e.wqSrc ) : csvToHexMask(e.wqSrc ); + r.matchedKey = host; + r.matchedScope = "host"; + if (isLogEnabled()) { + logLine("Resolved by host: '" + host + "' -> irq=" + r.irqHex + ", wq=" + r.wqHex); + } + return r; + } + } -static void getWqMask(std::string& wqMaskStr) { - std::string machineNamePath = "/sys/devices/soc0/machine"; - std::string machineName = ""; - fetchMachineName(machineName); + // 2) Machine exact match + { + auto it = kMachinePolicyMap.find(machine); + if (it != kMachinePolicyMap.end()) { + const MaskEntry& e = it->second; + ResolvedMasks r; + r.irqHex = e.irqIsHex ? strip0xLower(e.irqSrc) : csvToHexMask(e.irqSrc); + r.wqHex = e.wqIsHex ? strip0xLower(e.wqSrc ) : csvToHexMask(e.wqSrc ); + r.matchedKey = machine; + r.matchedScope = "machine"; + if (isLogEnabled()) { + logLine("Resolved by machine: '" + machine + "' -> irq=" + r.irqHex + ", wq=" + r.wqHex); + } + return r; + } + } - if(machineName == "qcs9100") { - wqMaskStr = "7F"; - } else if(machineName == "qcs8300") { - wqMaskStr = "F7"; - } else if(machineName == "qcm6490") { - wqMaskStr = "7F"; + // 3) Default + ResolvedMasks r; + r.irqHex = kDefaultMask.irqIsHex ? strip0xLower(kDefaultMask.irqSrc) : csvToHexMask(kDefaultMask.irqSrc); + r.wqHex = kDefaultMask.wqIsHex ? strip0xLower(kDefaultMask.wqSrc ) : csvToHexMask(kDefaultMask.wqSrc ); + r.matchedKey = ""; + r.matchedScope = "default"; + if (isLogEnabled()) { + logLine("Resolved by default -> irq=" + r.irqHex + ", wq=" + r.wqHex + + " (host='" + host + "', machine='" + machine + "')"); + } + return r; +} + +// --------------------------- +// PREEMPT_RT detection for cyclictest +// --------------------------- +static bool isPreemptRtActive() { + std::string rt; + if (readAttr("/sys/kernel/realtime", &rt)) { + rt = trim(rt); + if (isLogEnabled()) logLine(std::string("/sys/kernel/realtime = '") + rt + "'"); + if (rt == "1") return true; + if (rt == "0") return false; + } + struct utsname u{}; + if (uname(&u) == 0) { + std::string ver = toLower(u.version); + if (isLogEnabled()) logLine(std::string("uname -v: ") + u.version); + if (ver.find("preempt rt") != std::string::npos || ver.find("preempt_rt") != std::string::npos) { + return true; + } } + return false; } -static void governorApplierCallback(void* context) { +// --------------------------- +// cpufreq: apply/tear +// --------------------------- +static bool gCpufreqApplied = false; +static std::vector> gCpufreqGovBackup; + +static void cpufreqApplierCallback(void* /*context*/) { + if (isLogEnabled()) logLine("enter cpufreqApplierCallback"); + + if (gCpufreqApplied) return; + + gCpufreqGovBackup.clear(); + DIR* dir = opendir(POLICY_DIR_PATH); - if(dir == nullptr) { + if (!dir) { + if (isLogEnabled()) logLine(std::string("opendir fail: ") + POLICY_DIR_PATH + " errno=" + std::to_string(errno)); return; } struct dirent* entry; - while((entry = readdir(dir)) != nullptr) { - if(strncmp(entry->d_name, "policy", 6) == 0) { - std::string filePath = std::string(POLICY_DIR_PATH) + "/" + entry->d_name + "/governor"; - writeLineToFile(filePath, "performance"); + while ((entry = readdir(dir)) != nullptr) { + if (strncmp(entry->d_name, "policy", 6) != 0) continue; + + std::string base = std::string(POLICY_DIR_PATH) + entry->d_name; + std::string govFile = base + "/scaling_governor"; + if (!isWritable(govFile)) continue; + + std::string oldVal; + if (readAttr(govFile, &oldVal)) { + gCpufreqGovBackup.emplace_back(govFile, oldVal); + if (isLogEnabled()) logLine("[" + std::string(entry->d_name) + "] old governor: " + oldVal); + int rc = writeAttr(govFile, "performance"); + if (rc == 0 && isLogEnabled()) { + std::string now; + if (readAttr(govFile, &now)) logLine("verify " + govFile + " -> " + now); + } } } closedir(dir); + gCpufreqApplied = !gCpufreqGovBackup.empty(); } -static void workqueueApplierCallback(void* context) { - DIR* dir = opendir(WORKQUEUE_DIR_PATH); - if(dir == nullptr) { - return; +static void cpufreqTearCallback(void* /*context*/) { + if (!gCpufreqApplied) return; + if (isLogEnabled()) logLine("enter cpufreqTearCallback"); + + for (const auto& kv : gCpufreqGovBackup) { + const std::string& path = kv.first; + const std::string& oldVal = kv.second; + if (isWritable(path)) writeAttr(path, oldVal); } + gCpufreqGovBackup.clear(); + gCpufreqApplied = false; +} + +// --------------------------- +// IRQ affinity: apply/tear +// --------------------------- +static bool gIrqApplied = false; +static std::vector> gIrqAffBackup; + +static void irqAffinityApplierCallback(void* /*context*/) { + if (isLogEnabled()) logLine("enter irqAffinityApplierCallback"); + + if (gIrqApplied) return; + + gIrqAffBackup.clear(); + + const ResolvedMasks m = resolveMasks(); + const std::string hexMask = m.irqHex.empty() ? std::string("7f") : m.irqHex; + + DIR* dir = opendir(IRQ_DIR_PATH); + if (!dir) return; struct dirent* entry; - while((entry = readdir(dir)) != nullptr) { - std::string filePath = std::string(POLICY_DIR_PATH) + "/cpumask"; - std::string wqMask = ""; - getWqMask(wqMask); - writeLineToFile(filePath, wqMask); + while ((entry = readdir(dir)) != nullptr) { + // numeric directories only + bool numeric = true; + for (const char* p = entry->d_name; *p; ++p) { + if (!std::isdigit(static_cast(*p))) { numeric = false; break; } + } + if (!numeric) continue; + + std::string smpFile = std::string(IRQ_DIR_PATH) + entry->d_name + "/smp_affinity"; + if (!isWritable(smpFile)) continue; + + std::string oldVal; + if (readAttr(smpFile, &oldVal)) { + gIrqAffBackup.emplace_back(smpFile, oldVal); + writeAttr(smpFile, hexMask); + } } closedir(dir); + gIrqApplied = !gIrqAffBackup.empty(); } -static void governorTearCallback(void* context) { - // Reset to original if needed, else no_op - return; +static void irqAffinityTearCallback(void* /*context*/) { + if (!gIrqApplied) return; + if (isLogEnabled()) logLine("enter irqAffinityTearCallback"); + + for (const auto& kv : gIrqAffBackup) { + const std::string& path = kv.first; + const std::string& oldVal = kv.second; + if (isWritable(path)) writeAttr(path, oldVal); + } + gIrqAffBackup.clear(); + gIrqApplied = false; } -URM_REGISTER_RES_APPLIER_CB(0x00800000, governorApplierCallback) -URM_REGISTER_RES_APPLIER_CB(0x00800002, workqueueApplierCallback) -URM_REGISTER_RES_TEAR_CB(0x00800000, governorTearCallback) +// --------------------------- +// Workqueue cpumask: apply/tear +// --------------------------- +static bool gWqApplied = false; +static std::vector> gWqMaskBackup; -static void postProcessCallback(void* context) { - if(context == nullptr) { - return; +static void workqueueApplierCallback(void* /*context*/) { + if (isLogEnabled()) logLine("enter workqueueApplierCallback"); + + if (gWqApplied) return; + + gWqMaskBackup.clear(); + + const ResolvedMasks m = resolveMasks(); + const std::string hexMask = m.wqHex.empty() ? std::string("7f") : m.wqHex; + + DIR* dir = opendir(WQ_DIR_PATH); + if (!dir) return; + + struct dirent* entry; + while ((entry = readdir(dir)) != nullptr) { + if (entry->d_name[0] == '.') continue; // skip . and .. + std::string cpumaskFile = std::string(WQ_DIR_PATH) + entry->d_name + "/cpumask"; + if (!isWritable(cpumaskFile)) continue; + + std::string oldVal; + if (readAttr(cpumaskFile, &oldVal)) { + gWqMaskBackup.emplace_back(cpumaskFile, oldVal); + writeAttr(cpumaskFile, hexMask); + } } + closedir(dir); + gWqApplied = !gWqMaskBackup.empty(); +} - PostProcessCBData* cbData = static_cast(context); - if(cbData == nullptr) { - return; +static void workqueueTearCallback(void* /*context*/) { + if (!gWqApplied) return; + if (isLogEnabled()) logLine("enter workqueueTearCallback"); + + for (const auto& kv : gWqMaskBackup) { + const std::string& path = kv.first; + const std::string& oldVal = kv.second; + if (isWritable(path)) writeAttr(path, oldVal); } + gWqMaskBackup.clear(); + gWqApplied = false; +} + +// --------------------------- +// Post-process callback (only when PREEMPT_RT is active) +// --------------------------- +static void postProcessCallback(void* context) { + if (isLogEnabled()) logLine("enter postProcessCallback"); + if (context == nullptr) return; + + PostProcessCBData* cbData = static_cast(context); + if (cbData == nullptr) return; // Match to our usecase - cbData->mSigId = CONSTRUCT_SIG_CODE(0x80, 0x0001); + cbData->mSigId = CONSTRUCT_SIG_CODE(0x80, 0x0001); cbData->mSigType = DEFAULT_SIGNAL_TYPE; } -URM_REGISTER_POST_PROCESS_CB("cyclictest", postProcessCallback) +__attribute__((constructor)) +static void registerCyclictestIfRt() { + if (isPreemptRtActive()) { + if (isLogEnabled()) logLine("PREEMPT_RT active: registering post-process 'cyclictest'"); + URM_REGISTER_POST_PROCESS_CB("cyclictest", postProcessCallback) + } else { + if (isLogEnabled()) logLine("PREEMPT_RT not active: skipping post-process 'cyclictest'"); + } +} + +// --------------------------- +// URM registrations +// --------------------------- + +// IDs: +// 0x00800001 -> cpufreq +// 0x00800002 -> irqaffinity +// 0x00800003 -> workqueue + +URM_REGISTER_RES_APPLIER_CB(0x00800001, cpufreqApplierCallback) +URM_REGISTER_RES_TEAR_CB (0x00800001, cpufreqTearCallback) + +URM_REGISTER_RES_APPLIER_CB(0x00800002, irqAffinityApplierCallback) +URM_REGISTER_RES_TEAR_CB (0x00800002, irqAffinityTearCallback) + +URM_REGISTER_RES_APPLIER_CB(0x00800003, workqueueApplierCallback) +URM_REGISTER_RES_TEAR_CB (0x00800003, workqueueTearCallback)