diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2005-11-07 00:08:17 -0500 |
---|---|---|
committer | Paul Mackerras <paulus@samba.org> | 2005-11-07 19:17:56 -0500 |
commit | 75722d3992f57375c0cc029dcceb2334a45ceff1 (patch) | |
tree | d3f63b3ea80790c2f29ea435781c1331f17d269e /drivers/macintosh/windfarm_core.c | |
parent | 7d49697ef92bd2cf84ab53bd4cea82fefb197fb9 (diff) |
[PATCH] ppc64: Thermal control for SMU based machines
This adds a new thermal control framework for PowerMac, along with the
implementation for PowerMac8,1, PowerMac8,2 (iMac G5 rev 1 and 2), and
PowerMac9,1 (latest single CPU desktop). In the future, I expect to move
the older G5 thermal control to the new framework as well.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'drivers/macintosh/windfarm_core.c')
-rw-r--r-- | drivers/macintosh/windfarm_core.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/drivers/macintosh/windfarm_core.c b/drivers/macintosh/windfarm_core.c new file mode 100644 index 000000000000..6c2a471ea6c0 --- /dev/null +++ b/drivers/macintosh/windfarm_core.c | |||
@@ -0,0 +1,426 @@ | |||
1 | /* | ||
2 | * Windfarm PowerMac thermal control. Core | ||
3 | * | ||
4 | * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. | ||
5 | * <benh@kernel.crashing.org> | ||
6 | * | ||
7 | * Released under the term of the GNU GPL v2. | ||
8 | * | ||
9 | * This core code tracks the list of sensors & controls, register | ||
10 | * clients, and holds the kernel thread used for control. | ||
11 | * | ||
12 | * TODO: | ||
13 | * | ||
14 | * Add some information about sensor/control type and data format to | ||
15 | * sensors/controls, and have the sysfs attribute stuff be moved | ||
16 | * generically here instead of hard coded in the platform specific | ||
17 | * driver as it us currently | ||
18 | * | ||
19 | * This however requires solving some annoying lifetime issues with | ||
20 | * sysfs which doesn't seem to have lifetime rules for struct attribute, | ||
21 | * I may have to create full features kobjects for every sensor/control | ||
22 | * instead which is a bit of an overkill imho | ||
23 | */ | ||
24 | |||
25 | #include <linux/types.h> | ||
26 | #include <linux/errno.h> | ||
27 | #include <linux/kernel.h> | ||
28 | #include <linux/init.h> | ||
29 | #include <linux/spinlock.h> | ||
30 | #include <linux/smp_lock.h> | ||
31 | #include <linux/kthread.h> | ||
32 | #include <linux/jiffies.h> | ||
33 | #include <linux/reboot.h> | ||
34 | #include <linux/device.h> | ||
35 | #include <linux/platform_device.h> | ||
36 | |||
37 | #include "windfarm.h" | ||
38 | |||
39 | #define VERSION "0.2" | ||
40 | |||
41 | #undef DEBUG | ||
42 | |||
43 | #ifdef DEBUG | ||
44 | #define DBG(args...) printk(args) | ||
45 | #else | ||
46 | #define DBG(args...) do { } while(0) | ||
47 | #endif | ||
48 | |||
49 | static LIST_HEAD(wf_controls); | ||
50 | static LIST_HEAD(wf_sensors); | ||
51 | static DECLARE_MUTEX(wf_lock); | ||
52 | static struct notifier_block *wf_client_list; | ||
53 | static int wf_client_count; | ||
54 | static unsigned int wf_overtemp; | ||
55 | static unsigned int wf_overtemp_counter; | ||
56 | struct task_struct *wf_thread; | ||
57 | |||
58 | /* | ||
59 | * Utilities & tick thread | ||
60 | */ | ||
61 | |||
62 | static inline void wf_notify(int event, void *param) | ||
63 | { | ||
64 | notifier_call_chain(&wf_client_list, event, param); | ||
65 | } | ||
66 | |||
67 | int wf_critical_overtemp(void) | ||
68 | { | ||
69 | static char * critical_overtemp_path = "/sbin/critical_overtemp"; | ||
70 | char *argv[] = { critical_overtemp_path, NULL }; | ||
71 | static char *envp[] = { "HOME=/", | ||
72 | "TERM=linux", | ||
73 | "PATH=/sbin:/usr/sbin:/bin:/usr/bin", | ||
74 | NULL }; | ||
75 | |||
76 | return call_usermodehelper(critical_overtemp_path, argv, envp, 0); | ||
77 | } | ||
78 | EXPORT_SYMBOL_GPL(wf_critical_overtemp); | ||
79 | |||
80 | static int wf_thread_func(void *data) | ||
81 | { | ||
82 | unsigned long next, delay; | ||
83 | |||
84 | next = jiffies; | ||
85 | |||
86 | DBG("wf: thread started\n"); | ||
87 | |||
88 | while(!kthread_should_stop()) { | ||
89 | try_to_freeze(); | ||
90 | |||
91 | if (time_after_eq(jiffies, next)) { | ||
92 | wf_notify(WF_EVENT_TICK, NULL); | ||
93 | if (wf_overtemp) { | ||
94 | wf_overtemp_counter++; | ||
95 | /* 10 seconds overtemp, notify userland */ | ||
96 | if (wf_overtemp_counter > 10) | ||
97 | wf_critical_overtemp(); | ||
98 | /* 30 seconds, shutdown */ | ||
99 | if (wf_overtemp_counter > 30) { | ||
100 | printk(KERN_ERR "windfarm: Overtemp " | ||
101 | "for more than 30" | ||
102 | " seconds, shutting down\n"); | ||
103 | machine_power_off(); | ||
104 | } | ||
105 | } | ||
106 | next += HZ; | ||
107 | } | ||
108 | |||
109 | delay = next - jiffies; | ||
110 | if (delay <= HZ) | ||
111 | schedule_timeout_interruptible(delay); | ||
112 | |||
113 | /* there should be no signal, but oh well */ | ||
114 | if (signal_pending(current)) { | ||
115 | printk(KERN_WARNING "windfarm: thread got sigl !\n"); | ||
116 | break; | ||
117 | } | ||
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(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(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(ct); | ||
157 | } | ||
158 | |||
159 | int wf_register_control(struct wf_control *new_ct) | ||
160 | { | ||
161 | struct wf_control *ct; | ||
162 | |||
163 | down(&wf_lock); | ||
164 | list_for_each_entry(ct, &wf_controls, link) { | ||
165 | if (!strcmp(ct->name, new_ct->name)) { | ||
166 | printk(KERN_WARNING "windfarm: trying to register" | ||
167 | " duplicate control %s\n", ct->name); | ||
168 | up(&wf_lock); | ||
169 | return -EEXIST; | ||
170 | } | ||
171 | } | ||
172 | kref_init(&new_ct->ref); | ||
173 | list_add(&new_ct->link, &wf_controls); | ||
174 | |||
175 | DBG("wf: Registered control %s\n", new_ct->name); | ||
176 | |||
177 | wf_notify(WF_EVENT_NEW_CONTROL, new_ct); | ||
178 | up(&wf_lock); | ||
179 | |||
180 | return 0; | ||
181 | } | ||
182 | EXPORT_SYMBOL_GPL(wf_register_control); | ||
183 | |||
184 | void wf_unregister_control(struct wf_control *ct) | ||
185 | { | ||
186 | down(&wf_lock); | ||
187 | list_del(&ct->link); | ||
188 | up(&wf_lock); | ||
189 | |||
190 | DBG("wf: Unregistered control %s\n", ct->name); | ||
191 | |||
192 | kref_put(&ct->ref, wf_control_release); | ||
193 | } | ||
194 | EXPORT_SYMBOL_GPL(wf_unregister_control); | ||
195 | |||
196 | struct wf_control * wf_find_control(const char *name) | ||
197 | { | ||
198 | struct wf_control *ct; | ||
199 | |||
200 | down(&wf_lock); | ||
201 | list_for_each_entry(ct, &wf_controls, link) { | ||
202 | if (!strcmp(ct->name, name)) { | ||
203 | if (wf_get_control(ct)) | ||
204 | ct = NULL; | ||
205 | up(&wf_lock); | ||
206 | return ct; | ||
207 | } | ||
208 | } | ||
209 | up(&wf_lock); | ||
210 | return NULL; | ||
211 | } | ||
212 | EXPORT_SYMBOL_GPL(wf_find_control); | ||
213 | |||
214 | int wf_get_control(struct wf_control *ct) | ||
215 | { | ||
216 | if (!try_module_get(ct->ops->owner)) | ||
217 | return -ENODEV; | ||
218 | kref_get(&ct->ref); | ||
219 | return 0; | ||
220 | } | ||
221 | EXPORT_SYMBOL_GPL(wf_get_control); | ||
222 | |||
223 | void wf_put_control(struct wf_control *ct) | ||
224 | { | ||
225 | struct module *mod = ct->ops->owner; | ||
226 | kref_put(&ct->ref, wf_control_release); | ||
227 | module_put(mod); | ||
228 | } | ||
229 | EXPORT_SYMBOL_GPL(wf_put_control); | ||
230 | |||
231 | |||
232 | /* | ||
233 | * Sensors | ||
234 | */ | ||
235 | |||
236 | |||
237 | static void wf_sensor_release(struct kref *kref) | ||
238 | { | ||
239 | struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); | ||
240 | |||
241 | DBG("wf: Deleting sensor %s\n", sr->name); | ||
242 | |||
243 | if (sr->ops && sr->ops->release) | ||
244 | sr->ops->release(sr); | ||
245 | else | ||
246 | kfree(sr); | ||
247 | } | ||
248 | |||
249 | int wf_register_sensor(struct wf_sensor *new_sr) | ||
250 | { | ||
251 | struct wf_sensor *sr; | ||
252 | |||
253 | down(&wf_lock); | ||
254 | list_for_each_entry(sr, &wf_sensors, link) { | ||
255 | if (!strcmp(sr->name, new_sr->name)) { | ||
256 | printk(KERN_WARNING "windfarm: trying to register" | ||
257 | " duplicate sensor %s\n", sr->name); | ||
258 | up(&wf_lock); | ||
259 | return -EEXIST; | ||
260 | } | ||
261 | } | ||
262 | kref_init(&new_sr->ref); | ||
263 | list_add(&new_sr->link, &wf_sensors); | ||
264 | |||
265 | DBG("wf: Registered sensor %s\n", new_sr->name); | ||
266 | |||
267 | wf_notify(WF_EVENT_NEW_SENSOR, new_sr); | ||
268 | up(&wf_lock); | ||
269 | |||
270 | return 0; | ||
271 | } | ||
272 | EXPORT_SYMBOL_GPL(wf_register_sensor); | ||
273 | |||
274 | void wf_unregister_sensor(struct wf_sensor *sr) | ||
275 | { | ||
276 | down(&wf_lock); | ||
277 | list_del(&sr->link); | ||
278 | up(&wf_lock); | ||
279 | |||
280 | DBG("wf: Unregistered sensor %s\n", sr->name); | ||
281 | |||
282 | wf_put_sensor(sr); | ||
283 | } | ||
284 | EXPORT_SYMBOL_GPL(wf_unregister_sensor); | ||
285 | |||
286 | struct wf_sensor * wf_find_sensor(const char *name) | ||
287 | { | ||
288 | struct wf_sensor *sr; | ||
289 | |||
290 | down(&wf_lock); | ||
291 | list_for_each_entry(sr, &wf_sensors, link) { | ||
292 | if (!strcmp(sr->name, name)) { | ||
293 | if (wf_get_sensor(sr)) | ||
294 | sr = NULL; | ||
295 | up(&wf_lock); | ||
296 | return sr; | ||
297 | } | ||
298 | } | ||
299 | up(&wf_lock); | ||
300 | return NULL; | ||
301 | } | ||
302 | EXPORT_SYMBOL_GPL(wf_find_sensor); | ||
303 | |||
304 | int wf_get_sensor(struct wf_sensor *sr) | ||
305 | { | ||
306 | if (!try_module_get(sr->ops->owner)) | ||
307 | return -ENODEV; | ||
308 | kref_get(&sr->ref); | ||
309 | return 0; | ||
310 | } | ||
311 | EXPORT_SYMBOL_GPL(wf_get_sensor); | ||
312 | |||
313 | void wf_put_sensor(struct wf_sensor *sr) | ||
314 | { | ||
315 | struct module *mod = sr->ops->owner; | ||
316 | kref_put(&sr->ref, wf_sensor_release); | ||
317 | module_put(mod); | ||
318 | } | ||
319 | EXPORT_SYMBOL_GPL(wf_put_sensor); | ||
320 | |||
321 | |||
322 | /* | ||
323 | * Client & notification | ||
324 | */ | ||
325 | |||
326 | int wf_register_client(struct notifier_block *nb) | ||
327 | { | ||
328 | int rc; | ||
329 | struct wf_control *ct; | ||
330 | struct wf_sensor *sr; | ||
331 | |||
332 | down(&wf_lock); | ||
333 | rc = notifier_chain_register(&wf_client_list, nb); | ||
334 | if (rc != 0) | ||
335 | goto bail; | ||
336 | wf_client_count++; | ||
337 | list_for_each_entry(ct, &wf_controls, link) | ||
338 | wf_notify(WF_EVENT_NEW_CONTROL, ct); | ||
339 | list_for_each_entry(sr, &wf_sensors, link) | ||
340 | wf_notify(WF_EVENT_NEW_SENSOR, sr); | ||
341 | if (wf_client_count == 1) | ||
342 | wf_start_thread(); | ||
343 | bail: | ||
344 | up(&wf_lock); | ||
345 | return rc; | ||
346 | } | ||
347 | EXPORT_SYMBOL_GPL(wf_register_client); | ||
348 | |||
349 | int wf_unregister_client(struct notifier_block *nb) | ||
350 | { | ||
351 | down(&wf_lock); | ||
352 | notifier_chain_unregister(&wf_client_list, nb); | ||
353 | wf_client_count++; | ||
354 | if (wf_client_count == 0) | ||
355 | wf_stop_thread(); | ||
356 | up(&wf_lock); | ||
357 | |||
358 | return 0; | ||
359 | } | ||
360 | EXPORT_SYMBOL_GPL(wf_unregister_client); | ||
361 | |||
362 | void wf_set_overtemp(void) | ||
363 | { | ||
364 | down(&wf_lock); | ||
365 | wf_overtemp++; | ||
366 | if (wf_overtemp == 1) { | ||
367 | printk(KERN_WARNING "windfarm: Overtemp condition detected !\n"); | ||
368 | wf_overtemp_counter = 0; | ||
369 | wf_notify(WF_EVENT_OVERTEMP, NULL); | ||
370 | } | ||
371 | up(&wf_lock); | ||
372 | } | ||
373 | EXPORT_SYMBOL_GPL(wf_set_overtemp); | ||
374 | |||
375 | void wf_clear_overtemp(void) | ||
376 | { | ||
377 | down(&wf_lock); | ||
378 | WARN_ON(wf_overtemp == 0); | ||
379 | if (wf_overtemp == 0) { | ||
380 | up(&wf_lock); | ||
381 | return; | ||
382 | } | ||
383 | wf_overtemp--; | ||
384 | if (wf_overtemp == 0) { | ||
385 | printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n"); | ||
386 | wf_notify(WF_EVENT_NORMALTEMP, NULL); | ||
387 | } | ||
388 | up(&wf_lock); | ||
389 | } | ||
390 | EXPORT_SYMBOL_GPL(wf_clear_overtemp); | ||
391 | |||
392 | int wf_is_overtemp(void) | ||
393 | { | ||
394 | return (wf_overtemp != 0); | ||
395 | } | ||
396 | EXPORT_SYMBOL_GPL(wf_is_overtemp); | ||
397 | |||
398 | static struct platform_device wf_platform_device = { | ||
399 | .name = "windfarm", | ||
400 | }; | ||
401 | |||
402 | static int __init windfarm_core_init(void) | ||
403 | { | ||
404 | DBG("wf: core loaded\n"); | ||
405 | |||
406 | platform_device_register(&wf_platform_device); | ||
407 | return 0; | ||
408 | } | ||
409 | |||
410 | static void __exit windfarm_core_exit(void) | ||
411 | { | ||
412 | BUG_ON(wf_client_count != 0); | ||
413 | |||
414 | DBG("wf: core unloaded\n"); | ||
415 | |||
416 | platform_device_unregister(&wf_platform_device); | ||
417 | } | ||
418 | |||
419 | |||
420 | module_init(windfarm_core_init); | ||
421 | module_exit(windfarm_core_exit); | ||
422 | |||
423 | MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); | ||
424 | MODULE_DESCRIPTION("Core component of PowerMac thermal control"); | ||
425 | MODULE_LICENSE("GPL"); | ||
426 | |||