Files
x1e-nixos/kernel/modules/cpu-parking/cpu_parking.c
T
2026-04-10 15:45:53 +04:00

216 lines
5.2 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* CPU core parking for Snapdragon X Elite.
*/
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/tick.h>
#include <linux/workqueue.h>
#include <linux/bitops.h>
#define DRIVER_NAME "cpu_parking"
#define MAX_CPUS 16
static int set_enabled(const char *val, const struct kernel_param *kp);
static const struct kernel_param_ops enabled_ops = {
.set = set_enabled,
.get = param_get_bool,
};
static bool enabled = true;
module_param_cb(enabled, &enabled_ops, &enabled, 0644);
MODULE_PARM_DESC(enabled, "Enable/disable parking (default: 1)");
static unsigned int sample_interval_ms = 1000;
module_param(sample_interval_ms, uint, 0644);
MODULE_PARM_DESC(sample_interval_ms, "Polling interval in ms (default: 1000)");
static unsigned int busy_up_pct = 75;
module_param(busy_up_pct, uint, 0644);
MODULE_PARM_DESC(busy_up_pct, "Avg util%% above which to unpark a core (default: 75)");
static unsigned int busy_down_pct = 30;
module_param(busy_down_pct, uint, 0644);
MODULE_PARM_DESC(busy_down_pct, "Avg util%% below which to park a core (default: 30)");
static unsigned int min_online;
module_param(min_online, uint, 0644);
MODULE_PARM_DESC(min_online, "Minimum P-cores to keep online (default: 0)");
static unsigned int parkable_cpus = 0xFF0;
module_param(parkable_cpus, uint, 0644);
MODULE_PARM_DESC(parkable_cpus, "Bitmask of parkable CPUs (default: 0xFF0 = CPUs 4-11)");
static unsigned int parked_mask;
module_param_named(parked_cpus, parked_mask, uint, 0444);
MODULE_PARM_DESC(parked_cpus, "Current parked CPU bitmask (read-only)");
static unsigned int last_avg_util;
module_param(last_avg_util, uint, 0444);
MODULE_PARM_DESC(last_avg_util, "Last average utilization %% (read-only)");
static u64 prev_idle[MAX_CPUS];
static u64 prev_wall[MAX_CPUS];
static struct delayed_work parking_work;
static void snapshot_cpu(unsigned int cpu)
{
u64 wall;
if (cpu >= MAX_CPUS)
return;
prev_idle[cpu] = get_cpu_idle_time_us(cpu, &wall);
prev_wall[cpu] = wall;
}
static void snapshot_all_online(void)
{
unsigned int cpu;
for_each_online_cpu(cpu)
snapshot_cpu(cpu);
}
static void park_cpu(unsigned int cpu)
{
if (!cpu_online(cpu))
return;
if (remove_cpu(cpu)) {
pr_warn(DRIVER_NAME ": failed to park cpu%u\n", cpu);
return;
}
parked_mask |= BIT(cpu);
pr_info(DRIVER_NAME ": parked cpu%u (parked=0x%03x)\n", cpu, parked_mask);
}
static void unpark_cpu(unsigned int cpu)
{
if (cpu_online(cpu))
return;
if (add_cpu(cpu)) {
pr_warn(DRIVER_NAME ": failed to unpark cpu%u\n", cpu);
return;
}
parked_mask &= ~BIT(cpu);
snapshot_cpu(cpu);
pr_info(DRIVER_NAME ": unparked cpu%u (parked=0x%03x)\n", cpu, parked_mask);
}
static void unpark_all(void)
{
int cpu;
for_each_possible_cpu(cpu) {
if (parked_mask & BIT(cpu))
unpark_cpu(cpu);
}
}
static void parking_work_fn(struct work_struct *work)
{
unsigned int cpu, nr_sampled = 0, total_util = 0;
unsigned int online_parkable = 0, parked_parkable;
u64 idle, wall, d_idle, d_wall;
int target;
if (!enabled) {
unpark_all();
return;
}
for_each_online_cpu(cpu) {
if (cpu >= MAX_CPUS)
continue;
idle = get_cpu_idle_time_us(cpu, &wall);
if (idle == (u64)-1)
continue;
d_idle = idle - prev_idle[cpu];
d_wall = wall - prev_wall[cpu];
prev_idle[cpu] = idle;
prev_wall[cpu] = wall;
if (d_wall > 0 && d_idle <= d_wall)
total_util += (unsigned int)((d_wall - d_idle) * 100 / d_wall);
nr_sampled++;
if (parkable_cpus & BIT(cpu))
online_parkable++;
}
if (nr_sampled == 0)
goto resched;
last_avg_util = total_util / nr_sampled;
parked_parkable = hweight32(parked_mask & parkable_cpus);
if (last_avg_util > busy_up_pct && parked_parkable > 0) {
for (cpu = 0; cpu < MAX_CPUS; cpu++) {
if ((parked_mask & BIT(cpu)) &&
(parkable_cpus & BIT(cpu))) {
unpark_cpu(cpu);
break;
}
}
} else if (last_avg_util < busy_down_pct &&
online_parkable > min_online) {
for (target = MAX_CPUS - 1; target >= 0; target--) {
if ((parkable_cpus & BIT(target)) &&
cpu_online(target) &&
!(parked_mask & BIT(target))) {
park_cpu(target);
break;
}
}
}
resched:
schedule_delayed_work(&parking_work,
msecs_to_jiffies(sample_interval_ms));
}
static int set_enabled(const char *val, const struct kernel_param *kp)
{
bool was_enabled = enabled;
int ret;
ret = param_set_bool(val, kp);
if (ret)
return ret;
if (!was_enabled && enabled) {
snapshot_all_online();
schedule_delayed_work(&parking_work,
msecs_to_jiffies(sample_interval_ms));
pr_info(DRIVER_NAME ": re-enabled\n");
}
return 0;
}
static int __init cpu_parking_init(void)
{
snapshot_all_online();
INIT_DELAYED_WORK(&parking_work, parking_work_fn);
schedule_delayed_work(&parking_work,
msecs_to_jiffies(sample_interval_ms));
pr_info(DRIVER_NAME ": loaded (parkable=0x%03x interval=%ums up=%u%% down=%u%%)\n",
parkable_cpus, sample_interval_ms, busy_up_pct, busy_down_pct);
return 0;
}
static void __exit cpu_parking_exit(void)
{
cancel_delayed_work_sync(&parking_work);
unpark_all();
pr_info(DRIVER_NAME ": unloaded, all cores online\n");
}
module_init(cpu_parking_init);
module_exit(cpu_parking_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("CPU core parking for Snapdragon X Elite (x1e80100)");