| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * cpuidle driver for haltpoll governor. |
| * |
| * Copyright 2019 Red Hat, Inc. and/or its affiliates. |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2. See |
| * the COPYING file in the top-level directory. |
| * |
| * Authors: Marcelo Tosatti <mtosatti@redhat.com> |
| */ |
| |
| #include <linux/init.h> |
| #include <linux/cpu.h> |
| #include <linux/cpuidle.h> |
| #include <linux/module.h> |
| #include <linux/sched/idle.h> |
| #include <linux/kvm_para.h> |
| #include <linux/cpuidle_haltpoll.h> |
| |
| static bool force __read_mostly; |
| module_param(force, bool, 0444); |
| MODULE_PARM_DESC(force, "Load unconditionally"); |
| |
| static struct cpuidle_device __percpu *haltpoll_cpuidle_devices; |
| static enum cpuhp_state haltpoll_hp_state; |
| |
| static int default_enter_idle(struct cpuidle_device *dev, |
| struct cpuidle_driver *drv, int index) |
| { |
| if (current_clr_polling_and_test()) { |
| local_irq_enable(); |
| return index; |
| } |
| default_idle(); |
| return index; |
| } |
| |
| static struct cpuidle_driver haltpoll_driver = { |
| .name = "haltpoll", |
| .governor = "haltpoll", |
| .states = { |
| { /* entry 0 is for polling */ }, |
| { |
| .enter = default_enter_idle, |
| .exit_latency = 1, |
| .target_residency = 1, |
| .power_usage = -1, |
| .name = "haltpoll idle", |
| .desc = "default architecture idle", |
| }, |
| }, |
| .safe_state_index = 0, |
| .state_count = 2, |
| }; |
| |
| static int haltpoll_cpu_online(unsigned int cpu) |
| { |
| struct cpuidle_device *dev; |
| |
| dev = per_cpu_ptr(haltpoll_cpuidle_devices, cpu); |
| if (!dev->registered) { |
| dev->cpu = cpu; |
| if (cpuidle_register_device(dev)) { |
| pr_notice("cpuidle_register_device %d failed!\n", cpu); |
| return -EIO; |
| } |
| arch_haltpoll_enable(cpu); |
| } |
| |
| return 0; |
| } |
| |
| static int haltpoll_cpu_offline(unsigned int cpu) |
| { |
| struct cpuidle_device *dev; |
| |
| dev = per_cpu_ptr(haltpoll_cpuidle_devices, cpu); |
| if (dev->registered) { |
| arch_haltpoll_disable(cpu); |
| cpuidle_unregister_device(dev); |
| } |
| |
| return 0; |
| } |
| |
| static void haltpoll_uninit(void) |
| { |
| if (haltpoll_hp_state) |
| cpuhp_remove_state(haltpoll_hp_state); |
| cpuidle_unregister_driver(&haltpoll_driver); |
| |
| free_percpu(haltpoll_cpuidle_devices); |
| haltpoll_cpuidle_devices = NULL; |
| } |
| |
| static bool haltpoll_want(void) |
| { |
| return kvm_para_has_hint(KVM_HINTS_REALTIME) || force; |
| } |
| |
| static int __init haltpoll_init(void) |
| { |
| int ret; |
| struct cpuidle_driver *drv = &haltpoll_driver; |
| |
| /* Do not load haltpoll if idle= is passed */ |
| if (boot_option_idle_override != IDLE_NO_OVERRIDE) |
| return -ENODEV; |
| |
| if (!kvm_para_available() || !haltpoll_want()) |
| return -ENODEV; |
| |
| cpuidle_poll_state_init(drv); |
| |
| ret = cpuidle_register_driver(drv); |
| if (ret < 0) |
| return ret; |
| |
| haltpoll_cpuidle_devices = alloc_percpu(struct cpuidle_device); |
| if (haltpoll_cpuidle_devices == NULL) { |
| cpuidle_unregister_driver(drv); |
| return -ENOMEM; |
| } |
| |
| ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "cpuidle/haltpoll:online", |
| haltpoll_cpu_online, haltpoll_cpu_offline); |
| if (ret < 0) { |
| haltpoll_uninit(); |
| } else { |
| haltpoll_hp_state = ret; |
| ret = 0; |
| } |
| |
| return ret; |
| } |
| |
| static void __exit haltpoll_exit(void) |
| { |
| haltpoll_uninit(); |
| } |
| |
| module_init(haltpoll_init); |
| module_exit(haltpoll_exit); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Marcelo Tosatti <mtosatti@redhat.com>"); |