1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * LED Heartbeat Trigger |
4 | * |
5 | * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp> |
6 | * |
7 | * Based on Richard Purdie's ledtrig-timer.c and some arch's |
8 | * CONFIG_HEARTBEAT code. |
9 | */ |
10 | |
11 | #include <linux/module.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/init.h> |
14 | #include <linux/panic_notifier.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/timer.h> |
17 | #include <linux/sched.h> |
18 | #include <linux/sched/loadavg.h> |
19 | #include <linux/leds.h> |
20 | #include <linux/reboot.h> |
21 | #include "../leds.h" |
22 | |
23 | static int panic_heartbeats; |
24 | |
25 | struct heartbeat_trig_data { |
26 | struct led_classdev *led_cdev; |
27 | unsigned int phase; |
28 | unsigned int period; |
29 | struct timer_list timer; |
30 | unsigned int invert; |
31 | }; |
32 | |
33 | static void led_heartbeat_function(struct timer_list *t) |
34 | { |
35 | struct heartbeat_trig_data *heartbeat_data = |
36 | from_timer(heartbeat_data, t, timer); |
37 | struct led_classdev *led_cdev; |
38 | unsigned long brightness = LED_OFF; |
39 | unsigned long delay = 0; |
40 | |
41 | led_cdev = heartbeat_data->led_cdev; |
42 | |
43 | if (unlikely(panic_heartbeats)) { |
44 | led_set_brightness_nosleep(led_cdev, value: LED_OFF); |
45 | return; |
46 | } |
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 | /* acts like an actual heart beat -- ie thump-thump-pause... */ |
52 | switch (heartbeat_data->phase) { |
53 | case 0: |
54 | /* |
55 | * The hyperbolic function below modifies the |
56 | * heartbeat period length in dependency of the |
57 | * current (1min) load. It goes through the points |
58 | * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300. |
59 | */ |
60 | heartbeat_data->period = 300 + |
61 | (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT)); |
62 | heartbeat_data->period = |
63 | msecs_to_jiffies(m: heartbeat_data->period); |
64 | delay = msecs_to_jiffies(m: 70); |
65 | heartbeat_data->phase++; |
66 | if (!heartbeat_data->invert) |
67 | brightness = led_cdev->blink_brightness; |
68 | break; |
69 | case 1: |
70 | delay = heartbeat_data->period / 4 - msecs_to_jiffies(m: 70); |
71 | heartbeat_data->phase++; |
72 | if (heartbeat_data->invert) |
73 | brightness = led_cdev->blink_brightness; |
74 | break; |
75 | case 2: |
76 | delay = msecs_to_jiffies(m: 70); |
77 | heartbeat_data->phase++; |
78 | if (!heartbeat_data->invert) |
79 | brightness = led_cdev->blink_brightness; |
80 | break; |
81 | default: |
82 | delay = heartbeat_data->period - heartbeat_data->period / 4 - |
83 | msecs_to_jiffies(m: 70); |
84 | heartbeat_data->phase = 0; |
85 | if (heartbeat_data->invert) |
86 | brightness = led_cdev->blink_brightness; |
87 | break; |
88 | } |
89 | |
90 | led_set_brightness_nosleep(led_cdev, value: brightness); |
91 | mod_timer(timer: &heartbeat_data->timer, expires: jiffies + delay); |
92 | } |
93 | |
94 | static ssize_t led_invert_show(struct device *dev, |
95 | struct device_attribute *attr, char *buf) |
96 | { |
97 | struct heartbeat_trig_data *heartbeat_data = |
98 | led_trigger_get_drvdata(dev); |
99 | |
100 | return sprintf(buf, fmt: "%u\n" , heartbeat_data->invert); |
101 | } |
102 | |
103 | static ssize_t led_invert_store(struct device *dev, |
104 | struct device_attribute *attr, const char *buf, size_t size) |
105 | { |
106 | struct heartbeat_trig_data *heartbeat_data = |
107 | led_trigger_get_drvdata(dev); |
108 | unsigned long state; |
109 | int ret; |
110 | |
111 | ret = kstrtoul(s: buf, base: 0, res: &state); |
112 | if (ret) |
113 | return ret; |
114 | |
115 | heartbeat_data->invert = !!state; |
116 | |
117 | return size; |
118 | } |
119 | |
120 | static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); |
121 | |
122 | static struct attribute *heartbeat_trig_attrs[] = { |
123 | &dev_attr_invert.attr, |
124 | NULL |
125 | }; |
126 | ATTRIBUTE_GROUPS(heartbeat_trig); |
127 | |
128 | static int heartbeat_trig_activate(struct led_classdev *led_cdev) |
129 | { |
130 | struct heartbeat_trig_data *heartbeat_data; |
131 | |
132 | heartbeat_data = kzalloc(size: sizeof(*heartbeat_data), GFP_KERNEL); |
133 | if (!heartbeat_data) |
134 | return -ENOMEM; |
135 | |
136 | led_set_trigger_data(led_cdev, trigger_data: heartbeat_data); |
137 | heartbeat_data->led_cdev = led_cdev; |
138 | |
139 | timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0); |
140 | heartbeat_data->phase = 0; |
141 | if (!led_cdev->blink_brightness) |
142 | led_cdev->blink_brightness = led_cdev->max_brightness; |
143 | led_heartbeat_function(t: &heartbeat_data->timer); |
144 | set_bit(LED_BLINK_SW, addr: &led_cdev->work_flags); |
145 | |
146 | return 0; |
147 | } |
148 | |
149 | static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) |
150 | { |
151 | struct heartbeat_trig_data *heartbeat_data = |
152 | led_get_trigger_data(led_cdev); |
153 | |
154 | timer_shutdown_sync(timer: &heartbeat_data->timer); |
155 | kfree(objp: heartbeat_data); |
156 | clear_bit(LED_BLINK_SW, addr: &led_cdev->work_flags); |
157 | } |
158 | |
159 | static struct led_trigger heartbeat_led_trigger = { |
160 | .name = "heartbeat" , |
161 | .activate = heartbeat_trig_activate, |
162 | .deactivate = heartbeat_trig_deactivate, |
163 | .groups = heartbeat_trig_groups, |
164 | }; |
165 | |
166 | static int heartbeat_reboot_notifier(struct notifier_block *nb, |
167 | unsigned long code, void *unused) |
168 | { |
169 | led_trigger_unregister(trigger: &heartbeat_led_trigger); |
170 | return NOTIFY_DONE; |
171 | } |
172 | |
173 | static int heartbeat_panic_notifier(struct notifier_block *nb, |
174 | unsigned long code, void *unused) |
175 | { |
176 | panic_heartbeats = 1; |
177 | return NOTIFY_DONE; |
178 | } |
179 | |
180 | static struct notifier_block heartbeat_reboot_nb = { |
181 | .notifier_call = heartbeat_reboot_notifier, |
182 | }; |
183 | |
184 | static struct notifier_block heartbeat_panic_nb = { |
185 | .notifier_call = heartbeat_panic_notifier, |
186 | }; |
187 | |
188 | static int __init heartbeat_trig_init(void) |
189 | { |
190 | int rc = led_trigger_register(trigger: &heartbeat_led_trigger); |
191 | |
192 | if (!rc) { |
193 | atomic_notifier_chain_register(nh: &panic_notifier_list, |
194 | nb: &heartbeat_panic_nb); |
195 | register_reboot_notifier(&heartbeat_reboot_nb); |
196 | } |
197 | return rc; |
198 | } |
199 | |
200 | static void __exit heartbeat_trig_exit(void) |
201 | { |
202 | unregister_reboot_notifier(&heartbeat_reboot_nb); |
203 | atomic_notifier_chain_unregister(nh: &panic_notifier_list, |
204 | nb: &heartbeat_panic_nb); |
205 | led_trigger_unregister(trigger: &heartbeat_led_trigger); |
206 | } |
207 | |
208 | module_init(heartbeat_trig_init); |
209 | module_exit(heartbeat_trig_exit); |
210 | |
211 | MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>" ); |
212 | MODULE_DESCRIPTION("Heartbeat LED trigger" ); |
213 | MODULE_LICENSE("GPL v2" ); |
214 | |