// SPDX-License-Identifier: GPL-2.0-only /* * CPU core parking for Snapdragon X Elite. */ #include #include #include #include #include #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)");