1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * cpuidle driver for haltpoll governor. |
4 | * |
5 | * Copyright 2019 Red Hat, Inc. and/or its affiliates. |
6 | * |
7 | * This work is licensed under the terms of the GNU GPL, version 2. See |
8 | * the COPYING file in the top-level directory. |
9 | * |
10 | * Authors: Marcelo Tosatti <mtosatti@redhat.com> |
11 | */ |
12 | |
13 | #include <linux/init.h> |
14 | #include <linux/cpu.h> |
15 | #include <linux/cpuidle.h> |
16 | #include <linux/module.h> |
17 | #include <linux/sched/idle.h> |
18 | #include <linux/kvm_para.h> |
19 | #include <linux/cpuidle_haltpoll.h> |
20 | |
21 | static bool force __read_mostly; |
22 | module_param(force, bool, 0444); |
23 | MODULE_PARM_DESC(force, "Load unconditionally" ); |
24 | |
25 | static struct cpuidle_device __percpu *haltpoll_cpuidle_devices; |
26 | static enum cpuhp_state haltpoll_hp_state; |
27 | |
28 | static int default_enter_idle(struct cpuidle_device *dev, |
29 | struct cpuidle_driver *drv, int index) |
30 | { |
31 | if (current_clr_polling_and_test()) { |
32 | local_irq_enable(); |
33 | return index; |
34 | } |
35 | arch_cpu_idle(); |
36 | return index; |
37 | } |
38 | |
39 | static struct cpuidle_driver haltpoll_driver = { |
40 | .name = "haltpoll" , |
41 | .governor = "haltpoll" , |
42 | .states = { |
43 | { /* entry 0 is for polling */ }, |
44 | { |
45 | .enter = default_enter_idle, |
46 | .exit_latency = 1, |
47 | .target_residency = 1, |
48 | .power_usage = -1, |
49 | .name = "haltpoll idle" , |
50 | .desc = "default architecture idle" , |
51 | }, |
52 | }, |
53 | .safe_state_index = 0, |
54 | .state_count = 2, |
55 | }; |
56 | |
57 | static int haltpoll_cpu_online(unsigned int cpu) |
58 | { |
59 | struct cpuidle_device *dev; |
60 | |
61 | dev = per_cpu_ptr(haltpoll_cpuidle_devices, cpu); |
62 | if (!dev->registered) { |
63 | dev->cpu = cpu; |
64 | if (cpuidle_register_device(dev)) { |
65 | pr_notice("cpuidle_register_device %d failed!\n" , cpu); |
66 | return -EIO; |
67 | } |
68 | arch_haltpoll_enable(cpu); |
69 | } |
70 | |
71 | return 0; |
72 | } |
73 | |
74 | static int haltpoll_cpu_offline(unsigned int cpu) |
75 | { |
76 | struct cpuidle_device *dev; |
77 | |
78 | dev = per_cpu_ptr(haltpoll_cpuidle_devices, cpu); |
79 | if (dev->registered) { |
80 | arch_haltpoll_disable(cpu); |
81 | cpuidle_unregister_device(dev); |
82 | } |
83 | |
84 | return 0; |
85 | } |
86 | |
87 | static void haltpoll_uninit(void) |
88 | { |
89 | if (haltpoll_hp_state) |
90 | cpuhp_remove_state(state: haltpoll_hp_state); |
91 | cpuidle_unregister_driver(drv: &haltpoll_driver); |
92 | |
93 | free_percpu(pdata: haltpoll_cpuidle_devices); |
94 | haltpoll_cpuidle_devices = NULL; |
95 | } |
96 | |
97 | static bool haltpoll_want(void) |
98 | { |
99 | return kvm_para_has_hint(KVM_HINTS_REALTIME) || force; |
100 | } |
101 | |
102 | static int __init haltpoll_init(void) |
103 | { |
104 | int ret; |
105 | struct cpuidle_driver *drv = &haltpoll_driver; |
106 | |
107 | /* Do not load haltpoll if idle= is passed */ |
108 | if (boot_option_idle_override != IDLE_NO_OVERRIDE) |
109 | return -ENODEV; |
110 | |
111 | if (!kvm_para_available() || !haltpoll_want()) |
112 | return -ENODEV; |
113 | |
114 | cpuidle_poll_state_init(drv); |
115 | |
116 | ret = cpuidle_register_driver(drv); |
117 | if (ret < 0) |
118 | return ret; |
119 | |
120 | haltpoll_cpuidle_devices = alloc_percpu(struct cpuidle_device); |
121 | if (haltpoll_cpuidle_devices == NULL) { |
122 | cpuidle_unregister_driver(drv); |
123 | return -ENOMEM; |
124 | } |
125 | |
126 | ret = cpuhp_setup_state(state: CPUHP_AP_ONLINE_DYN, name: "cpuidle/haltpoll:online" , |
127 | startup: haltpoll_cpu_online, teardown: haltpoll_cpu_offline); |
128 | if (ret < 0) { |
129 | haltpoll_uninit(); |
130 | } else { |
131 | haltpoll_hp_state = ret; |
132 | ret = 0; |
133 | } |
134 | |
135 | return ret; |
136 | } |
137 | |
138 | static void __exit haltpoll_exit(void) |
139 | { |
140 | haltpoll_uninit(); |
141 | } |
142 | |
143 | module_init(haltpoll_init); |
144 | module_exit(haltpoll_exit); |
145 | MODULE_LICENSE("GPL" ); |
146 | MODULE_AUTHOR("Marcelo Tosatti <mtosatti@redhat.com>" ); |
147 | |