| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * s390 generic CPU idle driver. | |
| * | |
| * Copyright IBM Corp. 2026 | |
| */ | |
| #define pr_fmt(fmt) "CPUidle s390: " fmt | |
| #include <linux/init.h> | |
| #include <linux/cpuidle.h> | |
| #include <linux/cpu.h> | |
| #include <linux/sched/clock.h> | |
| static __cpuidle int s390_enter_idle(struct cpuidle_device *dev, | |
| struct cpuidle_driver *drv, | |
| int index) | |
| { | |
| arch_cpu_idle(); | |
| return index; | |
| } | |
| static struct cpuidle_driver s390_cpuidle_driver = { | |
| .cpumask = (struct cpumask *)cpu_present_mask, | |
| .name = "s390-idle", | |
| .states = { | |
| { /* entry 0 is for polling */}, | |
| { | |
| .enter = s390_enter_idle, | |
| .name = "IDLE", | |
| .desc = "ENABLED WAIT", | |
| }, | |
| }, | |
| .safe_state_index = 0, | |
| .state_count = 2, | |
| }; | |
| static int s390_cpuidle_cpu_online(unsigned int cpu) | |
| { | |
| struct cpuidle_device *dev = &per_cpu(cpuidle_dev, cpu); | |
| int rc; | |
| if (dev->registered) { | |
| cpuidle_pause_and_lock(); | |
| rc = cpuidle_enable_device(dev); | |
| cpuidle_resume_and_unlock(); | |
| if (rc) | |
| pr_err("Failed to enable cpuidle device on cpu %u\n", cpu); | |
| } else { | |
| dev->cpu = cpu; | |
| rc = cpuidle_register_device(dev); | |
| if (rc) | |
| pr_err("Failed to register cpuidle driver on cpu %u\n", cpu); | |
| } | |
| return rc; | |
| } | |
| static int s390_cpuidle_cpu_dead(unsigned int cpu) | |
| { | |
| struct cpuidle_device *dev = &per_cpu(cpuidle_dev, cpu); | |
| if (!dev->registered) | |
| return 0; | |
| cpuidle_pause_and_lock(); | |
| cpuidle_disable_device(dev); | |
| cpuidle_resume_and_unlock(); | |
| return 0; | |
| } | |
| /* | |
| * The target_residency and exit_latency values are benchmark-derived estimates | |
| * that remain non-deterministic due to s390's virtualized architecture. | |
| * | |
| * Configuration strategy: | |
| * - Poll idle state: Values derived from the next enabled idle state (EW) | |
| * - Enabled Wait state: Values selected based on idle behavior and empirical | |
| * measurement data | |
| * | |
| * Goal is to improve responsiveness for workloads with frequent sleep/wakeup | |
| * cycles while minimizing any side effects. | |
| */ | |
| static void __init s390_cpuidle_ew_tune(void) | |
| { | |
| struct cpuidle_state *state = &s390_cpuidle_driver.states[1]; | |
| if (machine_is_lpar()) { | |
| state->target_residency = 5; | |
| state->exit_latency = 5; | |
| } else { | |
| state->target_residency = 1; | |
| state->exit_latency = 1; | |
| } | |
| } | |
| static int __init s390_cpuidle_init(void) | |
| { | |
| int rc; | |
| s390_cpuidle_ew_tune(); | |
| cpuidle_poll_state_init(&s390_cpuidle_driver); | |
| rc = cpuidle_register(&s390_cpuidle_driver, NULL); | |
| if (rc) | |
| return rc; | |
| rc = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN, | |
| "cpuidle/s390:online", | |
| s390_cpuidle_cpu_online, | |
| s390_cpuidle_cpu_dead); | |
| if (rc < 0) { | |
| cpuidle_unregister(&s390_cpuidle_driver); | |
| pr_err("Failed to allocate hotplug state: cpuidle/s390:online\n"); | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| device_initcall(s390_cpuidle_init); |