| /* | 
 |  * Windfarm PowerMac thermal control. Core | 
 |  * | 
 |  * (c) Copyright 2005 Benjamin Herrenschmidt, IBM Corp. | 
 |  *                    <benh@kernel.crashing.org> | 
 |  * | 
 |  * Released under the term of the GNU GPL v2. | 
 |  * | 
 |  * 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/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 <asm/prom.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; | 
 | 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); | 
 | } | 
 |  | 
 | int wf_critical_overtemp(void) | 
 | { | 
 | 	static char * critical_overtemp_path = "/sbin/critical_overtemp"; | 
 | 	char *argv[] = { 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); | 
 | } | 
 | EXPORT_SYMBOL_GPL(wf_critical_overtemp); | 
 |  | 
 | 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); | 
 | 	s32 val = 0; | 
 | 	int err; | 
 |  | 
 | 	err = ctrl->ops->get_value(ctrl, &val); | 
 | 	if (err < 0) | 
 | 		return err; | 
 | 	return sprintf(buf, "%d\n", val); | 
 | } | 
 |  | 
 | /* 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); | 
 |  | 
 | 	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); | 
 |  | 
 | struct wf_control * wf_find_control(const char *name) | 
 | { | 
 | 	struct wf_control *ct; | 
 |  | 
 | 	mutex_lock(&wf_lock); | 
 | 	list_for_each_entry(ct, &wf_controls, link) { | 
 | 		if (!strcmp(ct->name, name)) { | 
 | 			if (wf_get_control(ct)) | 
 | 				ct = NULL; | 
 | 			mutex_unlock(&wf_lock); | 
 | 			return ct; | 
 | 		} | 
 | 	} | 
 | 	mutex_unlock(&wf_lock); | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(wf_find_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); | 
 |  | 
 | 	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); | 
 |  | 
 | struct wf_sensor * wf_find_sensor(const char *name) | 
 | { | 
 | 	struct wf_sensor *sr; | 
 |  | 
 | 	mutex_lock(&wf_lock); | 
 | 	list_for_each_entry(sr, &wf_sensors, link) { | 
 | 		if (!strcmp(sr->name, name)) { | 
 | 			if (wf_get_sensor(sr)) | 
 | 				sr = NULL; | 
 | 			mutex_unlock(&wf_lock); | 
 | 			return sr; | 
 | 		} | 
 | 	} | 
 | 	mutex_unlock(&wf_lock); | 
 | 	return NULL; | 
 | } | 
 | EXPORT_SYMBOL_GPL(wf_find_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); | 
 |  | 
 | int wf_is_overtemp(void) | 
 | { | 
 | 	return (wf_overtemp != 0); | 
 | } | 
 | EXPORT_SYMBOL_GPL(wf_is_overtemp); | 
 |  | 
 | static int __init windfarm_core_init(void) | 
 | { | 
 | 	DBG("wf: core loaded\n"); | 
 |  | 
 | 	/* Don't register on old machines that use therm_pm72 for now */ | 
 | 	if (machine_is_compatible("PowerMac7,2") || | 
 | 	    machine_is_compatible("PowerMac7,3") || | 
 | 	    machine_is_compatible("RackMac3,1")) | 
 | 		return -ENODEV; | 
 | 	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"); | 
 |  |