| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Windfarm PowerMac thermal control. Core |
| * |
| * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. |
| * <benh@kernel.crashing.org> |
| * |
| * This core code tracks the list of sensors & controls, register |
| * clients, and holds the kernel thread used for control. |
| * |
| * TODO: |
| * |
| * Add some information about sensor/control type and data format to |
| * sensors/controls, and have the sysfs attribute stuff be moved |
| * generically here instead of hard coded in the platform specific |
| * driver as it us currently |
| * |
| * This however requires solving some annoying lifetime issues with |
| * sysfs which doesn't seem to have lifetime rules for struct attribute, |
| * I may have to create full features kobjects for every sensor/control |
| * instead which is a bit of an overkill imho |
| */ |
| |
| #include <linux/types.h> |
| #include <linux/errno.h> |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/init.h> |
| #include <linux/spinlock.h> |
| #include <linux/kthread.h> |
| #include <linux/jiffies.h> |
| #include <linux/reboot.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/mutex.h> |
| #include <linux/freezer.h> |
| |
| #include "windfarm.h" |
| |
| #define VERSION "0.2" |
| |
| #undef DEBUG |
| |
| #ifdef DEBUG |
| #define DBG(args...) printk(args) |
| #else |
| #define DBG(args...) do { } while(0) |
| #endif |
| |
| static LIST_HEAD(wf_controls); |
| static LIST_HEAD(wf_sensors); |
| static DEFINE_MUTEX(wf_lock); |
| static BLOCKING_NOTIFIER_HEAD(wf_client_list); |
| static int wf_client_count; |
| static unsigned int wf_overtemp; |
| static unsigned int wf_overtemp_counter; |
| static struct task_struct *wf_thread; |
| |
| static struct platform_device wf_platform_device = { |
| .name = "windfarm", |
| }; |
| |
| /* |
| * Utilities & tick thread |
| */ |
| |
| static inline void wf_notify(int event, void *param) |
| { |
| blocking_notifier_call_chain(&wf_client_list, event, param); |
| } |
| |
| static int wf_critical_overtemp(void) |
| { |
| static char const critical_overtemp_path[] = "/sbin/critical_overtemp"; |
| char *argv[] = { (char *)critical_overtemp_path, NULL }; |
| static char *envp[] = { "HOME=/", |
| "TERM=linux", |
| "PATH=/sbin:/usr/sbin:/bin:/usr/bin", |
| NULL }; |
| |
| return call_usermodehelper(critical_overtemp_path, |
| argv, envp, UMH_WAIT_EXEC); |
| } |
| |
| static int wf_thread_func(void *data) |
| { |
| unsigned long next, delay; |
| |
| next = jiffies; |
| |
| DBG("wf: thread started\n"); |
| |
| set_freezable(); |
| while (!kthread_should_stop()) { |
| try_to_freeze(); |
| |
| if (time_after_eq(jiffies, next)) { |
| wf_notify(WF_EVENT_TICK, NULL); |
| if (wf_overtemp) { |
| wf_overtemp_counter++; |
| /* 10 seconds overtemp, notify userland */ |
| if (wf_overtemp_counter > 10) |
| wf_critical_overtemp(); |
| /* 30 seconds, shutdown */ |
| if (wf_overtemp_counter > 30) { |
| printk(KERN_ERR "windfarm: Overtemp " |
| "for more than 30" |
| " seconds, shutting down\n"); |
| machine_power_off(); |
| } |
| } |
| next += HZ; |
| } |
| |
| delay = next - jiffies; |
| if (delay <= HZ) |
| schedule_timeout_interruptible(delay); |
| } |
| |
| DBG("wf: thread stopped\n"); |
| |
| return 0; |
| } |
| |
| static void wf_start_thread(void) |
| { |
| wf_thread = kthread_run(wf_thread_func, NULL, "kwindfarm"); |
| if (IS_ERR(wf_thread)) { |
| printk(KERN_ERR "windfarm: failed to create thread,err %ld\n", |
| PTR_ERR(wf_thread)); |
| wf_thread = NULL; |
| } |
| } |
| |
| |
| static void wf_stop_thread(void) |
| { |
| if (wf_thread) |
| kthread_stop(wf_thread); |
| wf_thread = NULL; |
| } |
| |
| /* |
| * Controls |
| */ |
| |
| static void wf_control_release(struct kref *kref) |
| { |
| struct wf_control *ct = container_of(kref, struct wf_control, ref); |
| |
| DBG("wf: Deleting control %s\n", ct->name); |
| |
| if (ct->ops && ct->ops->release) |
| ct->ops->release(ct); |
| else |
| kfree(ct); |
| } |
| |
| static ssize_t wf_show_control(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct wf_control *ctrl = container_of(attr, struct wf_control, attr); |
| const char *typestr; |
| s32 val = 0; |
| int err; |
| |
| err = ctrl->ops->get_value(ctrl, &val); |
| if (err < 0) { |
| if (err == -EFAULT) |
| return sprintf(buf, "<HW FAULT>\n"); |
| return err; |
| } |
| switch(ctrl->type) { |
| case WF_CONTROL_RPM_FAN: |
| typestr = " RPM"; |
| break; |
| case WF_CONTROL_PWM_FAN: |
| typestr = " %"; |
| break; |
| default: |
| typestr = ""; |
| } |
| return sprintf(buf, "%d%s\n", val, typestr); |
| } |
| |
| /* This is really only for debugging... */ |
| static ssize_t wf_store_control(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct wf_control *ctrl = container_of(attr, struct wf_control, attr); |
| int val; |
| int err; |
| char *endp; |
| |
| val = simple_strtoul(buf, &endp, 0); |
| while (endp < buf + count && (*endp == ' ' || *endp == '\n')) |
| ++endp; |
| if (endp - buf < count) |
| return -EINVAL; |
| err = ctrl->ops->set_value(ctrl, val); |
| if (err < 0) |
| return err; |
| return count; |
| } |
| |
| int wf_register_control(struct wf_control *new_ct) |
| { |
| struct wf_control *ct; |
| |
| mutex_lock(&wf_lock); |
| list_for_each_entry(ct, &wf_controls, link) { |
| if (!strcmp(ct->name, new_ct->name)) { |
| printk(KERN_WARNING "windfarm: trying to register" |
| " duplicate control %s\n", ct->name); |
| mutex_unlock(&wf_lock); |
| return -EEXIST; |
| } |
| } |
| kref_init(&new_ct->ref); |
| list_add(&new_ct->link, &wf_controls); |
| |
| sysfs_attr_init(&new_ct->attr.attr); |
| new_ct->attr.attr.name = new_ct->name; |
| new_ct->attr.attr.mode = 0644; |
| new_ct->attr.show = wf_show_control; |
| new_ct->attr.store = wf_store_control; |
| if (device_create_file(&wf_platform_device.dev, &new_ct->attr)) |
| printk(KERN_WARNING "windfarm: device_create_file failed" |
| " for %s\n", new_ct->name); |
| /* the subsystem still does useful work without the file */ |
| |
| DBG("wf: Registered control %s\n", new_ct->name); |
| |
| wf_notify(WF_EVENT_NEW_CONTROL, new_ct); |
| mutex_unlock(&wf_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wf_register_control); |
| |
| void wf_unregister_control(struct wf_control *ct) |
| { |
| mutex_lock(&wf_lock); |
| list_del(&ct->link); |
| mutex_unlock(&wf_lock); |
| |
| DBG("wf: Unregistered control %s\n", ct->name); |
| |
| kref_put(&ct->ref, wf_control_release); |
| } |
| EXPORT_SYMBOL_GPL(wf_unregister_control); |
| |
| int wf_get_control(struct wf_control *ct) |
| { |
| if (!try_module_get(ct->ops->owner)) |
| return -ENODEV; |
| kref_get(&ct->ref); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wf_get_control); |
| |
| void wf_put_control(struct wf_control *ct) |
| { |
| struct module *mod = ct->ops->owner; |
| kref_put(&ct->ref, wf_control_release); |
| module_put(mod); |
| } |
| EXPORT_SYMBOL_GPL(wf_put_control); |
| |
| |
| /* |
| * Sensors |
| */ |
| |
| |
| static void wf_sensor_release(struct kref *kref) |
| { |
| struct wf_sensor *sr = container_of(kref, struct wf_sensor, ref); |
| |
| DBG("wf: Deleting sensor %s\n", sr->name); |
| |
| if (sr->ops && sr->ops->release) |
| sr->ops->release(sr); |
| else |
| kfree(sr); |
| } |
| |
| static ssize_t wf_show_sensor(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct wf_sensor *sens = container_of(attr, struct wf_sensor, attr); |
| s32 val = 0; |
| int err; |
| |
| err = sens->ops->get_value(sens, &val); |
| if (err < 0) |
| return err; |
| return sprintf(buf, "%d.%03d\n", FIX32TOPRINT(val)); |
| } |
| |
| int wf_register_sensor(struct wf_sensor *new_sr) |
| { |
| struct wf_sensor *sr; |
| |
| mutex_lock(&wf_lock); |
| list_for_each_entry(sr, &wf_sensors, link) { |
| if (!strcmp(sr->name, new_sr->name)) { |
| printk(KERN_WARNING "windfarm: trying to register" |
| " duplicate sensor %s\n", sr->name); |
| mutex_unlock(&wf_lock); |
| return -EEXIST; |
| } |
| } |
| kref_init(&new_sr->ref); |
| list_add(&new_sr->link, &wf_sensors); |
| |
| sysfs_attr_init(&new_sr->attr.attr); |
| new_sr->attr.attr.name = new_sr->name; |
| new_sr->attr.attr.mode = 0444; |
| new_sr->attr.show = wf_show_sensor; |
| new_sr->attr.store = NULL; |
| if (device_create_file(&wf_platform_device.dev, &new_sr->attr)) |
| printk(KERN_WARNING "windfarm: device_create_file failed" |
| " for %s\n", new_sr->name); |
| /* the subsystem still does useful work without the file */ |
| |
| DBG("wf: Registered sensor %s\n", new_sr->name); |
| |
| wf_notify(WF_EVENT_NEW_SENSOR, new_sr); |
| mutex_unlock(&wf_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wf_register_sensor); |
| |
| void wf_unregister_sensor(struct wf_sensor *sr) |
| { |
| mutex_lock(&wf_lock); |
| list_del(&sr->link); |
| mutex_unlock(&wf_lock); |
| |
| DBG("wf: Unregistered sensor %s\n", sr->name); |
| |
| wf_put_sensor(sr); |
| } |
| EXPORT_SYMBOL_GPL(wf_unregister_sensor); |
| |
| int wf_get_sensor(struct wf_sensor *sr) |
| { |
| if (!try_module_get(sr->ops->owner)) |
| return -ENODEV; |
| kref_get(&sr->ref); |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wf_get_sensor); |
| |
| void wf_put_sensor(struct wf_sensor *sr) |
| { |
| struct module *mod = sr->ops->owner; |
| kref_put(&sr->ref, wf_sensor_release); |
| module_put(mod); |
| } |
| EXPORT_SYMBOL_GPL(wf_put_sensor); |
| |
| |
| /* |
| * Client & notification |
| */ |
| |
| int wf_register_client(struct notifier_block *nb) |
| { |
| int rc; |
| struct wf_control *ct; |
| struct wf_sensor *sr; |
| |
| mutex_lock(&wf_lock); |
| rc = blocking_notifier_chain_register(&wf_client_list, nb); |
| if (rc != 0) |
| goto bail; |
| wf_client_count++; |
| list_for_each_entry(ct, &wf_controls, link) |
| wf_notify(WF_EVENT_NEW_CONTROL, ct); |
| list_for_each_entry(sr, &wf_sensors, link) |
| wf_notify(WF_EVENT_NEW_SENSOR, sr); |
| if (wf_client_count == 1) |
| wf_start_thread(); |
| bail: |
| mutex_unlock(&wf_lock); |
| return rc; |
| } |
| EXPORT_SYMBOL_GPL(wf_register_client); |
| |
| int wf_unregister_client(struct notifier_block *nb) |
| { |
| mutex_lock(&wf_lock); |
| blocking_notifier_chain_unregister(&wf_client_list, nb); |
| wf_client_count--; |
| if (wf_client_count == 0) |
| wf_stop_thread(); |
| mutex_unlock(&wf_lock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL_GPL(wf_unregister_client); |
| |
| void wf_set_overtemp(void) |
| { |
| mutex_lock(&wf_lock); |
| wf_overtemp++; |
| if (wf_overtemp == 1) { |
| printk(KERN_WARNING "windfarm: Overtemp condition detected !\n"); |
| wf_overtemp_counter = 0; |
| wf_notify(WF_EVENT_OVERTEMP, NULL); |
| } |
| mutex_unlock(&wf_lock); |
| } |
| EXPORT_SYMBOL_GPL(wf_set_overtemp); |
| |
| void wf_clear_overtemp(void) |
| { |
| mutex_lock(&wf_lock); |
| WARN_ON(wf_overtemp == 0); |
| if (wf_overtemp == 0) { |
| mutex_unlock(&wf_lock); |
| return; |
| } |
| wf_overtemp--; |
| if (wf_overtemp == 0) { |
| printk(KERN_WARNING "windfarm: Overtemp condition cleared !\n"); |
| wf_notify(WF_EVENT_NORMALTEMP, NULL); |
| } |
| mutex_unlock(&wf_lock); |
| } |
| EXPORT_SYMBOL_GPL(wf_clear_overtemp); |
| |
| static int __init windfarm_core_init(void) |
| { |
| DBG("wf: core loaded\n"); |
| |
| platform_device_register(&wf_platform_device); |
| return 0; |
| } |
| |
| static void __exit windfarm_core_exit(void) |
| { |
| BUG_ON(wf_client_count != 0); |
| |
| DBG("wf: core unloaded\n"); |
| |
| platform_device_unregister(&wf_platform_device); |
| } |
| |
| |
| module_init(windfarm_core_init); |
| module_exit(windfarm_core_exit); |
| |
| MODULE_AUTHOR("Benjamin Herrenschmidt <benh@kernel.crashing.org>"); |
| MODULE_DESCRIPTION("Core component of PowerMac thermal control"); |
| MODULE_LICENSE("GPL"); |
| |