1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Activity LED trigger |
4 | * |
5 | * Copyright (C) 2017 Willy Tarreau <w@1wt.eu> |
6 | * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c. |
7 | */ |
8 | |
9 | #include <linux/init.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/kernel_stat.h> |
12 | #include <linux/leds.h> |
13 | #include <linux/module.h> |
14 | #include <linux/panic_notifier.h> |
15 | #include <linux/reboot.h> |
16 | #include <linux/sched.h> |
17 | #include <linux/slab.h> |
18 | #include <linux/timer.h> |
19 | #include "../leds.h" |
20 | |
21 | static int panic_detected; |
22 | |
23 | struct activity_data { |
24 | struct timer_list timer; |
25 | struct led_classdev *led_cdev; |
26 | u64 last_used; |
27 | u64 last_boot; |
28 | int time_left; |
29 | int state; |
30 | int invert; |
31 | }; |
32 | |
33 | static void led_activity_function(struct timer_list *t) |
34 | { |
35 | struct activity_data *activity_data = from_timer(activity_data, t, |
36 | timer); |
37 | struct led_classdev *led_cdev = activity_data->led_cdev; |
38 | unsigned int target; |
39 | unsigned int usage; |
40 | int delay; |
41 | u64 curr_used; |
42 | u64 curr_boot; |
43 | s32 diff_used; |
44 | s32 diff_boot; |
45 | int cpus; |
46 | int i; |
47 | |
48 | if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, addr: &led_cdev->work_flags)) |
49 | led_cdev->blink_brightness = led_cdev->new_blink_brightness; |
50 | |
51 | if (unlikely(panic_detected)) { |
52 | /* full brightness in case of panic */ |
53 | led_set_brightness_nosleep(led_cdev, value: led_cdev->blink_brightness); |
54 | return; |
55 | } |
56 | |
57 | cpus = 0; |
58 | curr_used = 0; |
59 | |
60 | for_each_possible_cpu(i) { |
61 | struct kernel_cpustat kcpustat; |
62 | |
63 | kcpustat_cpu_fetch(dst: &kcpustat, cpu: i); |
64 | |
65 | curr_used += kcpustat.cpustat[CPUTIME_USER] |
66 | + kcpustat.cpustat[CPUTIME_NICE] |
67 | + kcpustat.cpustat[CPUTIME_SYSTEM] |
68 | + kcpustat.cpustat[CPUTIME_SOFTIRQ] |
69 | + kcpustat.cpustat[CPUTIME_IRQ]; |
70 | cpus++; |
71 | } |
72 | |
73 | /* We come here every 100ms in the worst case, so that's 100M ns of |
74 | * cumulated time. By dividing by 2^16, we get the time resolution |
75 | * down to 16us, ensuring we won't overflow 32-bit computations below |
76 | * even up to 3k CPUs, while keeping divides cheap on smaller systems. |
77 | */ |
78 | curr_boot = ktime_get_boottime_ns() * cpus; |
79 | diff_boot = (curr_boot - activity_data->last_boot) >> 16; |
80 | diff_used = (curr_used - activity_data->last_used) >> 16; |
81 | activity_data->last_boot = curr_boot; |
82 | activity_data->last_used = curr_used; |
83 | |
84 | if (diff_boot <= 0 || diff_used < 0) |
85 | usage = 0; |
86 | else if (diff_used >= diff_boot) |
87 | usage = 100; |
88 | else |
89 | usage = 100 * diff_used / diff_boot; |
90 | |
91 | /* |
92 | * Now we know the total boot_time multiplied by the number of CPUs, and |
93 | * the total idle+wait time for all CPUs. We'll compare how they evolved |
94 | * since last call. The % of overall CPU usage is : |
95 | * |
96 | * 1 - delta_idle / delta_boot |
97 | * |
98 | * What we want is that when the CPU usage is zero, the LED must blink |
99 | * slowly with very faint flashes that are detectable but not disturbing |
100 | * (typically 10ms every second, or 10ms ON, 990ms OFF). Then we want |
101 | * blinking frequency to increase up to the point where the load is |
102 | * enough to saturate one core in multi-core systems or 50% in single |
103 | * core systems. At this point it should reach 10 Hz with a 10/90 duty |
104 | * cycle (10ms ON, 90ms OFF). After this point, the blinking frequency |
105 | * remains stable (10 Hz) and only the duty cycle increases to report |
106 | * the activity, up to the point where we have 90ms ON, 10ms OFF when |
107 | * all cores are saturated. It's important that the LED never stays in |
108 | * a steady state so that it's easy to distinguish an idle or saturated |
109 | * machine from a hung one. |
110 | * |
111 | * This gives us : |
112 | * - a target CPU usage of min(50%, 100%/#CPU) for a 10% duty cycle |
113 | * (10ms ON, 90ms OFF) |
114 | * - below target : |
115 | * ON_ms = 10 |
116 | * OFF_ms = 90 + (1 - usage/target) * 900 |
117 | * - above target : |
118 | * ON_ms = 10 + (usage-target)/(100%-target) * 80 |
119 | * OFF_ms = 90 - (usage-target)/(100%-target) * 80 |
120 | * |
121 | * In order to keep a good responsiveness, we cap the sleep time to |
122 | * 100 ms and keep track of the sleep time left. This allows us to |
123 | * quickly change it if needed. |
124 | */ |
125 | |
126 | activity_data->time_left -= 100; |
127 | if (activity_data->time_left <= 0) { |
128 | activity_data->time_left = 0; |
129 | activity_data->state = !activity_data->state; |
130 | led_set_brightness_nosleep(led_cdev, |
131 | value: (activity_data->state ^ activity_data->invert) ? |
132 | led_cdev->blink_brightness : LED_OFF); |
133 | } |
134 | |
135 | target = (cpus > 1) ? (100 / cpus) : 50; |
136 | |
137 | if (usage < target) |
138 | delay = activity_data->state ? |
139 | 10 : /* ON */ |
140 | 990 - 900 * usage / target; /* OFF */ |
141 | else |
142 | delay = activity_data->state ? |
143 | 10 + 80 * (usage - target) / (100 - target) : /* ON */ |
144 | 90 - 80 * (usage - target) / (100 - target); /* OFF */ |
145 | |
146 | |
147 | if (!activity_data->time_left || delay <= activity_data->time_left) |
148 | activity_data->time_left = delay; |
149 | |
150 | delay = min_t(int, activity_data->time_left, 100); |
151 | mod_timer(timer: &activity_data->timer, expires: jiffies + msecs_to_jiffies(m: delay)); |
152 | } |
153 | |
154 | static ssize_t led_invert_show(struct device *dev, |
155 | struct device_attribute *attr, char *buf) |
156 | { |
157 | struct activity_data *activity_data = led_trigger_get_drvdata(dev); |
158 | |
159 | return sprintf(buf, fmt: "%u\n" , activity_data->invert); |
160 | } |
161 | |
162 | static ssize_t led_invert_store(struct device *dev, |
163 | struct device_attribute *attr, |
164 | const char *buf, size_t size) |
165 | { |
166 | struct activity_data *activity_data = led_trigger_get_drvdata(dev); |
167 | unsigned long state; |
168 | int ret; |
169 | |
170 | ret = kstrtoul(s: buf, base: 0, res: &state); |
171 | if (ret) |
172 | return ret; |
173 | |
174 | activity_data->invert = !!state; |
175 | |
176 | return size; |
177 | } |
178 | |
179 | static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); |
180 | |
181 | static struct attribute *activity_led_attrs[] = { |
182 | &dev_attr_invert.attr, |
183 | NULL |
184 | }; |
185 | ATTRIBUTE_GROUPS(activity_led); |
186 | |
187 | static int activity_activate(struct led_classdev *led_cdev) |
188 | { |
189 | struct activity_data *activity_data; |
190 | |
191 | activity_data = kzalloc(size: sizeof(*activity_data), GFP_KERNEL); |
192 | if (!activity_data) |
193 | return -ENOMEM; |
194 | |
195 | led_set_trigger_data(led_cdev, trigger_data: activity_data); |
196 | |
197 | activity_data->led_cdev = led_cdev; |
198 | timer_setup(&activity_data->timer, led_activity_function, 0); |
199 | if (!led_cdev->blink_brightness) |
200 | led_cdev->blink_brightness = led_cdev->max_brightness; |
201 | led_activity_function(t: &activity_data->timer); |
202 | set_bit(LED_BLINK_SW, addr: &led_cdev->work_flags); |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static void activity_deactivate(struct led_classdev *led_cdev) |
208 | { |
209 | struct activity_data *activity_data = led_get_trigger_data(led_cdev); |
210 | |
211 | timer_shutdown_sync(timer: &activity_data->timer); |
212 | kfree(objp: activity_data); |
213 | clear_bit(LED_BLINK_SW, addr: &led_cdev->work_flags); |
214 | } |
215 | |
216 | static struct led_trigger activity_led_trigger = { |
217 | .name = "activity" , |
218 | .activate = activity_activate, |
219 | .deactivate = activity_deactivate, |
220 | .groups = activity_led_groups, |
221 | }; |
222 | |
223 | static int activity_reboot_notifier(struct notifier_block *nb, |
224 | unsigned long code, void *unused) |
225 | { |
226 | led_trigger_unregister(trigger: &activity_led_trigger); |
227 | return NOTIFY_DONE; |
228 | } |
229 | |
230 | static int activity_panic_notifier(struct notifier_block *nb, |
231 | unsigned long code, void *unused) |
232 | { |
233 | panic_detected = 1; |
234 | return NOTIFY_DONE; |
235 | } |
236 | |
237 | static struct notifier_block activity_reboot_nb = { |
238 | .notifier_call = activity_reboot_notifier, |
239 | }; |
240 | |
241 | static struct notifier_block activity_panic_nb = { |
242 | .notifier_call = activity_panic_notifier, |
243 | }; |
244 | |
245 | static int __init activity_init(void) |
246 | { |
247 | int rc = led_trigger_register(trigger: &activity_led_trigger); |
248 | |
249 | if (!rc) { |
250 | atomic_notifier_chain_register(nh: &panic_notifier_list, |
251 | nb: &activity_panic_nb); |
252 | register_reboot_notifier(&activity_reboot_nb); |
253 | } |
254 | return rc; |
255 | } |
256 | |
257 | static void __exit activity_exit(void) |
258 | { |
259 | unregister_reboot_notifier(&activity_reboot_nb); |
260 | atomic_notifier_chain_unregister(nh: &panic_notifier_list, |
261 | nb: &activity_panic_nb); |
262 | led_trigger_unregister(trigger: &activity_led_trigger); |
263 | } |
264 | |
265 | module_init(activity_init); |
266 | module_exit(activity_exit); |
267 | |
268 | MODULE_AUTHOR("Willy Tarreau <w@1wt.eu>" ); |
269 | MODULE_DESCRIPTION("Activity LED trigger" ); |
270 | MODULE_LICENSE("GPL v2" ); |
271 | |