// 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");