1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Windfarm PowerMac thermal control. Core |
4 | * |
5 | * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. |
6 | * <benh@kernel.crashing.org> |
7 | * |
8 | * This core code tracks the list of sensors & controls, register |
9 | * clients, and holds the kernel thread used for control. |
10 | * |
11 | * TODO: |
12 | * |
13 | * Add some information about sensor/control type and data format to |
14 | * sensors/controls, and have the sysfs attribute stuff be moved |
15 | * generically here instead of hard coded in the platform specific |
16 | * driver as it us currently |
17 | * |
18 | * This however requires solving some annoying lifetime issues with |
19 | * sysfs which doesn't seem to have lifetime rules for struct attribute, |
20 | * I may have to create full features kobjects for every sensor/control |
21 | * instead which is a bit of an overkill imho |
22 | */ |
23 | |
24 | #include <linux/types.h> |
25 | #include <linux/errno.h> |
26 | #include <linux/kernel.h> |
27 | #include <linux/slab.h> |
28 | #include <linux/init.h> |
29 | #include <linux/spinlock.h> |
30 | #include <linux/kthread.h> |
31 | #include <linux/jiffies.h> |
32 | #include <linux/reboot.h> |
33 | #include <linux/device.h> |
34 | #include <linux/platform_device.h> |
35 | #include <linux/mutex.h> |
36 | #include <linux/freezer.h> |
37 | |
38 | #include "windfarm.h" |
39 | |
40 | #define VERSION "0.2" |
41 | |
42 | #undef DEBUG |
43 | |
44 | #ifdef DEBUG |
45 | #define DBG(args...) printk(args) |
46 | #else |
47 | #define DBG(args...) do { } while(0) |
48 | #endif |
49 | |
50 | static LIST_HEAD(wf_controls); |
51 | static LIST_HEAD(wf_sensors); |
52 | static DEFINE_MUTEX(wf_lock); |
53 | static BLOCKING_NOTIFIER_HEAD(wf_client_list); |
54 | static int wf_client_count; |
55 | static unsigned int wf_overtemp; |
56 | static unsigned int wf_overtemp_counter; |
57 | static struct task_struct *wf_thread; |
58 | |
59 | static struct platform_device wf_platform_device = { |
60 | .name = "windfarm" , |
61 | }; |
62 | |
63 | /* |
64 | * Utilities & tick thread |
65 | */ |
66 | |
67 | static inline void wf_notify(int event, void *param) |
68 | { |
69 | blocking_notifier_call_chain(nh: &wf_client_list, val: event, v: param); |
70 | } |
71 | |
72 | static int wf_critical_overtemp(void) |
73 | { |
74 | static char const critical_overtemp_path[] = "/sbin/critical_overtemp" ; |
75 | char *argv[] = { (char *)critical_overtemp_path, NULL }; |
76 | static char *envp[] = { "HOME=/" , |
77 | "TERM=linux" , |
78 | "PATH=/sbin:/usr/sbin:/bin:/usr/bin" , |
79 | NULL }; |
80 | |
81 | return call_usermodehelper(path: critical_overtemp_path, |
82 | argv, envp, UMH_WAIT_EXEC); |
83 | } |
84 | |
85 | static int wf_thread_func(void *data) |
86 | { |
87 | unsigned long next, delay; |
88 | |
89 | next = jiffies; |
90 | |
91 | DBG("wf: thread started\n" ); |
92 | |
93 | set_freezable(); |
94 | while (!kthread_should_stop()) { |
95 | try_to_freeze(); |
96 | |
97 | if (time_after_eq(jiffies, next)) { |
98 | wf_notify(WF_EVENT_TICK, NULL); |
99 | if (wf_overtemp) { |
100 | wf_overtemp_counter++; |
101 | /* 10 seconds overtemp, notify userland */ |
102 | if (wf_overtemp_counter > 10) |
103 | wf_critical_overtemp(); |
104 | /* 30 seconds, shutdown */ |
105 | if (wf_overtemp_counter > 30) { |
106 | printk(KERN_ERR "windfarm: Overtemp " |
107 | "for more than 30" |
108 | " seconds, shutting down\n" ); |
109 | machine_power_off(); |
110 | } |
111 | } |
112 | next += HZ; |
113 | } |
114 | |
115 | delay = next - jiffies; |
116 | if (delay <= HZ) |
117 | schedule_timeout_interruptible(timeout: delay); |
118 | } |
119 | |
120 | DBG("wf: thread stopped\n" ); |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static void wf_start_thread(void) |
126 | { |
127 | wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm" ); |
128 | if (IS_ERR(ptr: wf_thread)) { |
129 | printk(KERN_ERR "windfarm: failed to create thread,err %ld\n" , |
130 | PTR_ERR(wf_thread)); |
131 | wf_thread = NULL; |
132 | } |
133 | } |
134 | |
135 | |
136 | static void wf_stop_thread(void) |
137 | { |
138 | if (wf_thread) |
139 | kthread_stop(k: wf_thread); |
140 | wf_thread = NULL; |
141 | } |
142 | |
143 | /* |
144 | * Controls |
145 | */ |
146 | |
147 | static void wf_control_release(struct kref *kref) |
148 | { |
149 | struct wf_control *ct = container_of(kref, struct wf_control, ref); |
150 | |
151 | DBG("wf: Deleting control %s\n" , ct->name); |
152 | |
153 | if (ct->ops && ct->ops->release) |
154 | ct->ops->release(ct); |
155 | else |
156 | kfree(objp: ct); |
157 | } |
158 | |
159 | static ssize_t wf_show_control(struct device *dev, |
160 | struct device_attribute *attr, char *buf) |
161 | { |
162 | struct wf_control *ctrl = container_of(attr, struct wf_control, attr); |
163 | const char *typestr; |
164 | s32 val = 0; |
165 | int err; |
166 | |
167 | err = ctrl->ops->get_value(ctrl, &val); |
168 | if (err < 0) { |
169 | if (err == -EFAULT) |
170 | return sprintf(buf, fmt: "<HW FAULT>\n" ); |
171 | return err; |
172 | } |
173 | switch(ctrl->type) { |
174 | case WF_CONTROL_RPM_FAN: |
175 | typestr = " RPM" ; |
176 | break; |
177 | case WF_CONTROL_PWM_FAN: |
178 | typestr = " %" ; |
179 | break; |
180 | default: |
181 | typestr = "" ; |
182 | } |
183 | return sprintf(buf, fmt: "%d%s\n" , val, typestr); |
184 | } |
185 | |
186 | /* This is really only for debugging... */ |
187 | static ssize_t wf_store_control(struct device *dev, |
188 | struct device_attribute *attr, |
189 | const char *buf, size_t count) |
190 | { |
191 | struct wf_control *ctrl = container_of(attr, struct wf_control, attr); |
192 | int val; |
193 | int err; |
194 | char *endp; |
195 | |
196 | val = simple_strtoul(buf, &endp, 0); |
197 | while (endp < buf + count && (*endp == ' ' || *endp == '\n')) |
198 | ++endp; |
199 | if (endp - buf < count) |
200 | return -EINVAL; |
201 | err = ctrl->ops->set_value(ctrl, val); |
202 | if (err < 0) |
203 | return err; |
204 | return count; |
205 | } |
206 | |
207 | int wf_register_control(struct wf_control *new_ct) |
208 | { |
209 | struct wf_control *ct; |
210 | |
211 | mutex_lock(&wf_lock); |
212 | list_for_each_entry(ct, &wf_controls, link) { |
213 | if (!strcmp(ct->name, new_ct->name)) { |
214 | printk(KERN_WARNING "windfarm: trying to register" |
215 | " duplicate control %s\n" , ct->name); |
216 | mutex_unlock(lock: &wf_lock); |
217 | return -EEXIST; |
218 | } |
219 | } |
220 | kref_init(kref: &new_ct->ref); |
221 | list_add(new: &new_ct->link, head: &wf_controls); |
222 | |
223 | sysfs_attr_init(&new_ct->attr.attr); |
224 | new_ct->attr.attr.name = new_ct->name; |
225 | new_ct->attr.attr.mode = 0644; |
226 | new_ct->attr.show = wf_show_control; |
227 | new_ct->attr.store = wf_store_control; |
228 | if (device_create_file(device: &wf_platform_device.dev, entry: &new_ct->attr)) |
229 | printk(KERN_WARNING "windfarm: device_create_file failed" |
230 | " for %s\n" , new_ct->name); |
231 | /* the subsystem still does useful work without the file */ |
232 | |
233 | DBG("wf: Registered control %s\n" , new_ct->name); |
234 | |
235 | wf_notify(WF_EVENT_NEW_CONTROL, param: new_ct); |
236 | mutex_unlock(lock: &wf_lock); |
237 | |
238 | return 0; |
239 | } |
240 | EXPORT_SYMBOL_GPL(wf_register_control); |
241 | |
242 | void wf_unregister_control(struct wf_control *ct) |
243 | { |
244 | mutex_lock(&wf_lock); |
245 | list_del(entry: &ct->link); |
246 | mutex_unlock(lock: &wf_lock); |
247 | |
248 | DBG("wf: Unregistered control %s\n" , ct->name); |
249 | |
250 | kref_put(kref: &ct->ref, release: wf_control_release); |
251 | } |
252 | EXPORT_SYMBOL_GPL(wf_unregister_control); |
253 | |
254 | int wf_get_control(struct wf_control *ct) |
255 | { |
256 | if (!try_module_get(module: ct->ops->owner)) |
257 | return -ENODEV; |
258 | kref_get(kref: &ct->ref); |
259 | return 0; |
260 | } |
261 | EXPORT_SYMBOL_GPL(wf_get_control); |
262 | |
263 | void wf_put_control(struct wf_control *ct) |
264 | { |
265 | struct module *mod = ct->ops->owner; |
266 | kref_put(kref: &ct->ref, release: wf_control_release); |
267 | module_put(module: mod); |
268 | } |
269 | EXPORT_SYMBOL_GPL(wf_put_control); |
270 | |
271 | |
272 | /* |
273 | * Sensors |
274 | */ |
275 | |
276 | |
277 | static void wf_sensor_release(struct kref *kref) |
278 | { |
279 | struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); |
280 | |
281 | DBG("wf: Deleting sensor %s\n" , sr->name); |
282 | |
283 | if (sr->ops && sr->ops->release) |
284 | sr->ops->release(sr); |
285 | else |
286 | kfree(objp: sr); |
287 | } |
288 | |
289 | static ssize_t wf_show_sensor(struct device *dev, |
290 | struct device_attribute *attr, char *buf) |
291 | { |
292 | struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr); |
293 | s32 val = 0; |
294 | int err; |
295 | |
296 | err = sens->ops->get_value(sens, &val); |
297 | if (err < 0) |
298 | return err; |
299 | return sprintf(buf, fmt: "%d.%03d\n" , FIX32TOPRINT(val)); |
300 | } |
301 | |
302 | int wf_register_sensor(struct wf_sensor *new_sr) |
303 | { |
304 | struct wf_sensor *sr; |
305 | |
306 | mutex_lock(&wf_lock); |
307 | list_for_each_entry(sr, &wf_sensors, link) { |
308 | if (!strcmp(sr->name, new_sr->name)) { |
309 | printk(KERN_WARNING "windfarm: trying to register" |
310 | " duplicate sensor %s\n" , sr->name); |
311 | mutex_unlock(lock: &wf_lock); |
312 | return -EEXIST; |
313 | } |
314 | } |
315 | kref_init(kref: &new_sr->ref); |
316 | list_add(new: &new_sr->link, head: &wf_sensors); |
317 | |
318 | sysfs_attr_init(&new_sr->attr.attr); |
319 | new_sr->attr.attr.name = new_sr->name; |
320 | new_sr->attr.attr.mode = 0444; |
321 | new_sr->attr.show = wf_show_sensor; |
322 | new_sr->attr.store = NULL; |
323 | if (device_create_file(device: &wf_platform_device.dev, entry: &new_sr->attr)) |
324 | printk(KERN_WARNING "windfarm: device_create_file failed" |
325 | " for %s\n" , new_sr->name); |
326 | /* the subsystem still does useful work without the file */ |
327 | |
328 | DBG("wf: Registered sensor %s\n" , new_sr->name); |
329 | |
330 | wf_notify(WF_EVENT_NEW_SENSOR, param: new_sr); |
331 | mutex_unlock(lock: &wf_lock); |
332 | |
333 | return 0; |
334 | } |
335 | EXPORT_SYMBOL_GPL(wf_register_sensor); |
336 | |
337 | void wf_unregister_sensor(struct wf_sensor *sr) |
338 | { |
339 | mutex_lock(&wf_lock); |
340 | list_del(entry: &sr->link); |
341 | mutex_unlock(lock: &wf_lock); |
342 | |
343 | DBG("wf: Unregistered sensor %s\n" , sr->name); |
344 | |
345 | wf_put_sensor(sr); |
346 | } |
347 | EXPORT_SYMBOL_GPL(wf_unregister_sensor); |
348 | |
349 | int wf_get_sensor(struct wf_sensor *sr) |
350 | { |
351 | if (!try_module_get(module: sr->ops->owner)) |
352 | return -ENODEV; |
353 | kref_get(kref: &sr->ref); |
354 | return 0; |
355 | } |
356 | EXPORT_SYMBOL_GPL(wf_get_sensor); |
357 | |
358 | void wf_put_sensor(struct wf_sensor *sr) |
359 | { |
360 | struct module *mod = sr->ops->owner; |
361 | kref_put(kref: &sr->ref, release: wf_sensor_release); |
362 | module_put(module: mod); |
363 | } |
364 | EXPORT_SYMBOL_GPL(wf_put_sensor); |
365 | |
366 | |
367 | /* |
368 | * Client & notification |
369 | */ |
370 | |
371 | int wf_register_client(struct notifier_block *nb) |
372 | { |
373 | int rc; |
374 | struct wf_control *ct; |
375 | struct wf_sensor *sr; |
376 | |
377 | mutex_lock(&wf_lock); |
378 | rc = blocking_notifier_chain_register(nh: &wf_client_list, nb); |
379 | if (rc != 0) |
380 | goto bail; |
381 | wf_client_count++; |
382 | list_for_each_entry(ct, &wf_controls, link) |
383 | wf_notify(WF_EVENT_NEW_CONTROL, param: ct); |
384 | list_for_each_entry(sr, &wf_sensors, link) |
385 | wf_notify(WF_EVENT_NEW_SENSOR, param: sr); |
386 | if (wf_client_count == 1) |
387 | wf_start_thread(); |
388 | bail: |
389 | mutex_unlock(lock: &wf_lock); |
390 | return rc; |
391 | } |
392 | EXPORT_SYMBOL_GPL(wf_register_client); |
393 | |
394 | int wf_unregister_client(struct notifier_block *nb) |
395 | { |
396 | mutex_lock(&wf_lock); |
397 | blocking_notifier_chain_unregister(nh: &wf_client_list, nb); |
398 | wf_client_count--; |
399 | if (wf_client_count == 0) |
400 | wf_stop_thread(); |
401 | mutex_unlock(lock: &wf_lock); |
402 | |
403 | return 0; |
404 | } |
405 | EXPORT_SYMBOL_GPL(wf_unregister_client); |
406 | |
407 | void wf_set_overtemp(void) |
408 | { |
409 | mutex_lock(&wf_lock); |
410 | wf_overtemp++; |
411 | if (wf_overtemp == 1) { |
412 | printk(KERN_WARNING "windfarm: Overtemp condition detected !\n" ); |
413 | wf_overtemp_counter = 0; |
414 | wf_notify(WF_EVENT_OVERTEMP, NULL); |
415 | } |
416 | mutex_unlock(lock: &wf_lock); |
417 | } |
418 | EXPORT_SYMBOL_GPL(wf_set_overtemp); |
419 | |
420 | void wf_clear_overtemp(void) |
421 | { |
422 | mutex_lock(&wf_lock); |
423 | WARN_ON(wf_overtemp == 0); |
424 | if (wf_overtemp == 0) { |
425 | mutex_unlock(lock: &wf_lock); |
426 | return; |
427 | } |
428 | wf_overtemp--; |
429 | if (wf_overtemp == 0) { |
430 | printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n" ); |
431 | wf_notify(WF_EVENT_NORMALTEMP, NULL); |
432 | } |
433 | mutex_unlock(lock: &wf_lock); |
434 | } |
435 | EXPORT_SYMBOL_GPL(wf_clear_overtemp); |
436 | |
437 | static int __init windfarm_core_init(void) |
438 | { |
439 | DBG("wf: core loaded\n" ); |
440 | |
441 | platform_device_register(&wf_platform_device); |
442 | return 0; |
443 | } |
444 | |
445 | static void __exit windfarm_core_exit(void) |
446 | { |
447 | BUG_ON(wf_client_count != 0); |
448 | |
449 | DBG("wf: core unloaded\n" ); |
450 | |
451 | platform_device_unregister(&wf_platform_device); |
452 | } |
453 | |
454 | |
455 | module_init(windfarm_core_init); |
456 | module_exit(windfarm_core_exit); |
457 | |
458 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>" ); |
459 | MODULE_DESCRIPTION("Core component of PowerMac thermal control" ); |
460 | MODULE_LICENSE("GPL" ); |
461 | |
462 | |