On Thu, 7 Feb 2008 04:34:15 -0500 Len Brown wrote: > From: Zhang Rui <[EMAIL PROTECTED]> > > The Generic Thermal sysfs driver for thermal management. > > Signed-off-by: Zhang Rui <[EMAIL PROTECTED]> > Signed-off-by: Thomas Sujith <[EMAIL PROTECTED]> > Signed-off-by: Len Brown <[EMAIL PROTECTED]> > --- > Documentation/thermal/sysfs-api.txt | 246 ++++++++++++ > drivers/Kconfig | 2 + > drivers/Makefile | 1 + > drivers/thermal/Kconfig | 15 + > drivers/thermal/Makefile | 5 + > drivers/thermal/thermal.c | 714 > +++++++++++++++++++++++++++++++++++ > include/linux/thermal.h | 90 +++++ > 7 files changed, 1073 insertions(+), 0 deletions(-) > create mode 100644 Documentation/thermal/sysfs-api.txt > create mode 100644 drivers/thermal/Kconfig > create mode 100644 drivers/thermal/Makefile > create mode 100644 drivers/thermal/thermal.c > create mode 100644 include/linux/thermal.h > > diff --git a/Documentation/thermal/sysfs-api.txt > b/Documentation/thermal/sysfs-api.txt > new file mode 100644 > index 0000000..5776e09 > --- /dev/null > +++ b/Documentation/thermal/sysfs-api.txt
Nice doc. Thanks. > @@ -0,0 +1,246 @@ > +Generic Thermal Sysfs driver How To > +========================= > + > +Written by Sujith Thomas <[EMAIL PROTECTED]>, Zhang Rui <[EMAIL PROTECTED]> > + > +Updated: 2 January 2008 > + > +Copyright (c) 2008 Intel Corporation > + > + > +0. Introduction > + > +The generic thermal sysfs provides a set of interfaces for thermal zone > devices (sensors) > +and thermal cooling devices (fan, processor...) to register with the thermal > management > +solution and to be a part of it. > + > +This how-to focusses on enabling new thermal zone and cooling devices to > participate s/focusses/focuses/ > +in thermal management. > +This solution is platform independent and any type of thermal zone devices > and > +cooling devices should be able to make use of the infrastructure. > + > +The main task of the thermal sysfs driver is to expose thermal zone > attributes as well > +as cooling device attributes to the user space. > +An intelligent thermal management application can make decisions based on > inputs > +from thermal zone attributes (the current temperature and trip point > temperature) > +and throttle appropriate devices. > + > +[0-*] denotes any positive number starting from 0 > +[1-*] denotes any positive number starting from 1 > + ... > +2. sysfs attributes structure > + > +RO read only value > +RW read/write value > + > +All thermal sysfs attributes will be represented under /sys/class/thermal > +/sys/class/thermal/ Is that a duplicated path? or what? > + > +Thermal zone device sys I/F, created once it's registered: > +|thermal_zone[0-*]: > + |-----type: Type of the thermal zone > + |-----temp: Current temperature > + |-----mode: Working mode of the thermal zone > + |-----trip_point_[0-*]_temp: Trip point temperature > + |-----trip_point_[0-*]_type: Trip point type > + > +Thermal cooling device sys I/F, created once it's registered: > +|cooling_device[0-*]: > + |-----type : Type of the cooling > device(processor/fan/...) > + |-----max_state: Maximum cooling state of the cooling > device > + |-----cur_state: Current cooling state of the cooling > device > + > + > +These two dynamic attributes are created/removed in pairs. > +They represent the relationship between a thermal zone and its associated > cooling device. > +They are created/removed for each > +thermal_zone_bind_cooling_device/thermal_zone_unbind_cooling_device > successful exection. > + > +|thermal_zone[0-*] > + |-----cdev[0-*]: The [0-*]th cooling device in the > current thermal zone > + |-----cdev[0-*]_trip_point: Trip point that cdev[0-*] is associated > with > + > + > +*************************** > +* Thermal zone attributes * > +*************************** > + > +type Strings which represent the thermal zone type. > + This is given by thermal zone driver as part of > registration. > + Eg: "ACPI thermal zone" indicates it's a ACPI > thermal device > + RO > + Optional > + > +temp Current temperature as reported by thermal zone > (sensor) > + Unit: degree celsius > + RO > + Required > + > +mode One of the predifned values in [kernel, user] predefined > + This file gives information about the algorithm > + that is currently managing the thermal zone. > + It can be either default kernel based algorithm > + or user space application. > + RW > + Optional > + kernel = Thermal management in kernel thermal > zone driver. > + user = Preventing kernel thermal zone driver > actions upon > + trip points so that user application > can take full > + charge of the thermal management. > + > +trip_point_[0-*]_temp The temperature above which trip point > will be fired > + Unit: degree celsius > + RO > + Optional > + > +trip_point_[0-*]_type Strings which indicate the type of the > trip point > + Eg. it can be one of critical, hot, passive, E.g. > + active[0-*] for ACPI thermal zone. > + RO > + Optional > + > +cdev[0-*] Sysfs link to the thermal cooling device node > where the sys I/F > + for cooling device throttling control > represents. > + RO > + Optional > + > +cdev[0-*]_trip_point The trip point with which cdev[0-*] is > assocated in this thermal zone > + -1 means the cooling device is not associated > with any trip point. > + RO > + Optional > + > +****************************** > +* Cooling device attributes * > +****************************** > + > +type String which represents the type of device > + eg: For generic ACPI: this should be "Fan", > + "Processor" or "LCD" > + eg. For memory controller device on > intel_menlow platform: > + this should be "Memory controller" > + RO > + Optional > + > +max_state The maximum permissible cooling state of this > cooling device. > + RO > + Required > + > +cur_state The current cooling state of this cooling > device. > + the value can any integer numbers between 0 and > max_state, > + cur_state == 0 means no cooling > + cur_state == max_state means the maximum > cooling. > + RW > + Required > + > +3. A simple implementation > + > +ACPI thermal zone may support multiple trip points like > critical/hot/passive/active. > +If an ACPI thermal zone supports critical, passive, active[0] and active[1] > at the same time, > +it may register itself as a thermale_zone_device (thermal_zone1) with 4 trip > points in all. thermale ? > +It has one processor and one fan, which are both registered as > thermal_cooling_device. > +If the processor is listed in _PSL method, and the fan is listed in _AL0 > method, > +the sys I/F structure will be built like this: > + > +/sys/class/thermal: > + > +|thermal_zone1: > + |-----type: ACPI thermal zone > + |-----temp: 37 > + |-----mode: kernel > + |-----trip_point_0_temp: 100 > + |-----trip_point_0_type: critical > + |-----trip_point_1_temp: 80 > + |-----trip_point_1_type: passive > + |-----trip_point_2_temp: 70 > + |-----trip_point_2_type: active[0] > + |-----trip_point_3_temp: 60 > + |-----trip_point_3_type: active[1] > + |-----cdev0: --->/sys/class/thermal/cooling_device0 > + |-----cdev0_trip_point: 1 /* cdev0 can be used for > passive */ > + |-----cdev1: --->/sys/class/thermal/cooling_device3 > + |-----cdev1_trip_point: 2 /* cdev1 can be used for > active[0]*/ > + > +|cooling_device0: > + |-----type: Processor > + |-----max_state: 8 > + |-----cur_state: 0 > + > +|cooling_device3: > + |-----type: Fan > + |-----max_state: 2 > + |-----cur_state: 0 > diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig > new file mode 100644 > index 0000000..9b3f612 > --- /dev/null > +++ b/drivers/thermal/Kconfig > @@ -0,0 +1,15 @@ > +# > +# Generic thermal sysfs drivers configuration > +# > + > +menuconfig THERMAL > + bool "Generic Thermal sysfs driver" > + default y > + help > + Generic Thermal Sysfs driver offers a generic mechanism for > + thermal management. Usually it's made up of one or more thermal > + zone and cooling device. > + each thermal zone contains its own temperature, trip points, Each > + cooling devices. > + All platforms with ACPI thermal support can use this driver. > + If you want this support, you should say Y here here. > diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile > new file mode 100644 > index 0000000..8ef1232 > --- /dev/null > +++ b/drivers/thermal/Makefile > @@ -0,0 +1,5 @@ > +# > +# Makefile for sensor chip drivers. > +# > + > +obj-$(CONFIG_THERMAL) += thermal.o > diff --git a/drivers/thermal/thermal.c b/drivers/thermal/thermal.c > new file mode 100644 > index 0000000..3273e34 > --- /dev/null > +++ b/drivers/thermal/thermal.c > @@ -0,0 +1,714 @@ > +/* > + * thermal.c - Generic Thermal Management Sysfs support. > + * > + * Copyright (C) 2008 Intel Corp > + * Copyright (C) 2008 Zhang Rui <[EMAIL PROTECTED]> > + * Copyright (C) 2008 Sujith Thomas <[EMAIL PROTECTED]> > + * > + * > + */ > + > +#include <linux/module.h> > +#include <linux/device.h> > +#include <linux/err.h> > +#include <linux/kdev_t.h> > +#include <linux/idr.h> > +#include <linux/thermal.h> > +#include <linux/spinlock.h> > + > +MODULE_AUTHOR("Zhang Rui") > +MODULE_DESCRIPTION("Generic thermal management sysfs support"); > +MODULE_LICENSE("GPL"); > + > +#define PREFIX "Thermal: " > + > +struct thermal_cooling_device_instance { > + int id; > + char name[THERMAL_NAME_LENGTH]; > + struct thermal_zone_device *tz; > + struct thermal_cooling_device *cdev; > + int trip; > + char attr_name[THERMAL_NAME_LENGTH]; > + struct device_attribute attr; > + struct list_head node; > +}; > + > +static DEFINE_IDR(thermal_tz_idr); > +static DEFINE_IDR(thermal_cdev_idr); > +static DEFINE_MUTEX(thermal_idr_lock); > + > +static LIST_HEAD(thermal_tz_list); > +static LIST_HEAD(thermal_cdev_list); > +static DEFINE_MUTEX(thermal_list_lock); > + > +static int get_idr(struct idr *idr, struct mutex *lock, int *id) > +{ > + int err; > + > + again: Don't indent labels so much (just 0 or 1 spaces). When they are indented so much, it's difficult to see them (they are close to hidden). > + if (unlikely(idr_pre_get(idr, GFP_KERNEL) == 0)) > + return -ENOMEM; > + > + if (lock) > + mutex_lock(lock); > + err = idr_get_new(idr, NULL, id); > + if (lock) > + mutex_unlock(lock); > + if (unlikely(err == -EAGAIN)) > + goto again; > + else if (unlikely(err)) > + return err; > + > + *id = *id & MAX_ID_MASK; > + return 0; > +} > + > +/* Device management */ > + > +/** > + * thermal_zone_bind_cooling_device - bind a cooling device to a thermal zone > + * this function is usually called in the thermal zone device .bind callback. > + * @tz: thermal zone device > + * @trip: indicates which trip point the cooling devices is > + * associated with in this thermal zone. > + * @cdev: thermal cooling device > + */ Please see Documentation/kernel-doc-nano-HOWTO.txt for info on kernel-doc format. The second line above ("this function...") is out of place. It should be moved to follow the function parameters and separated from them by one "blank" (actually " *") line. > +int thermal_zone_bind_cooling_device(struct thermal_zone_device *tz, > + int trip, > + struct thermal_cooling_device *cdev) > +{ > + struct thermal_cooling_device_instance *dev; > + struct thermal_cooling_device_instance *pos; > + int result; > + > + if (trip >= tz->trips || > + (trip < 0 && trip != THERMAL_TRIPS_NONE)) > + return -EINVAL; > + > + if (!tz || !cdev) > + return -EINVAL; > + > + dev = > + kzalloc(sizeof(struct thermal_cooling_device_instance), GFP_KERNEL); > + if (!dev) > + return -ENOMEM; > + dev->tz = tz; > + dev->cdev = cdev; > + dev->trip = trip; > + result = get_idr(&tz->idr, &tz->lock, &dev->id); > + if (result) > + goto free_mem; > + > + sprintf(dev->name, "cdev%d", dev->id); > + result = > + sysfs_create_link(&tz->device.kobj, &cdev->device.kobj, dev->name); > + if (result) > + goto release_idr; > + > + sprintf(dev->attr_name, "cdev%d_trip_point", dev->id); > + dev->attr.attr.name = dev->attr_name; > + dev->attr.attr.mode = 0444; > + dev->attr.show = thermal_cooling_device_trip_point_show; > + result = device_create_file(&tz->device, &dev->attr); > + if (result) > + goto remove_symbol_link; > + > + mutex_lock(&tz->lock); > + list_for_each_entry(pos, &tz->cooling_devices, node) > + if (pos->tz == tz && pos->trip == trip && pos->cdev == cdev) { > + result = -EEXIST; > + break; > + } > + if (!result) > + list_add_tail(&dev->node, &tz->cooling_devices); > + mutex_unlock(&tz->lock); > + > + if (!result) > + return 0; > + > + device_remove_file(&tz->device, &dev->attr); > + remove_symbol_link: > + sysfs_remove_link(&tz->device.kobj, dev->name); > + release_idr: > + release_idr(&tz->idr, &tz->lock, dev->id); > + free_mem: > + kfree(dev); > + return result; > +} > +EXPORT_SYMBOL(thermal_zone_bind_cooling_device); > + > +/** > + * thermal_zone_unbind_cooling_device - unbind a cooling device from a > thermal zone > + * this function is usually called in the thermal zone device .unbind > callback. > + * @tz: thermal zone device > + * @trip: indicates which trip point the cooling devices is > + * associated with in this thermal zone. > + * @cdev: thermal cooling device > + */ Ditto. > +int thermal_zone_unbind_cooling_device(struct thermal_zone_device *tz, > + int trip, > + struct thermal_cooling_device *cdev) > +{ > + struct thermal_cooling_device_instance *pos, *next; > + > + mutex_lock(&tz->lock); > + list_for_each_entry_safe(pos, next, &tz->cooling_devices, node) { > + if (pos->tz == tz && pos->trip == trip > + && pos->cdev == cdev) { > + list_del(&pos->node); > + mutex_unlock(&tz->lock); > + goto unbind; > + } > + } > + mutex_unlock(&tz->lock); > + > + return -ENODEV; > + > + unbind: > + device_remove_file(&tz->device, &pos->attr); > + sysfs_remove_link(&tz->device.kobj, pos->name); > + release_idr(&tz->idr, &tz->lock, pos->id); > + kfree(pos); > + return 0; > +} > +EXPORT_SYMBOL(thermal_zone_unbind_cooling_device); > + > +/** > + * thermal_cooling_device_register - register a new thermal cooling device > + * @type: the thermal cooling device type. > + * @devdata: device private data. > + * @ops: standard thermal cooling devices callbacks. > + */ > +struct thermal_cooling_device *thermal_cooling_device_register(char *type, > + void *devdata, struct thermal_cooling_device_ops *ops) > +{ > + struct thermal_cooling_device *cdev; > + struct thermal_zone_device *pos; > + int result; > + > + if (strlen(type) >= THERMAL_NAME_LENGTH) > + return NULL; > + > + if (!ops || !ops->get_max_state || !ops->get_cur_state || > + !ops->set_cur_state) > + return NULL; > + > + cdev = kzalloc(sizeof(struct thermal_cooling_device), GFP_KERNEL); > + if (!cdev) > + return NULL; > + > + result = get_idr(&thermal_cdev_idr, &thermal_idr_lock, &cdev->id); > + if (result) { > + kfree(cdev); > + return NULL; > + } > + > + strcpy(cdev->type, type); > + cdev->ops = ops; > + cdev->device.class = &thermal_class; > + cdev->devdata = devdata; > + sprintf(cdev->device.bus_id, "cooling_device%d", cdev->id); > + result = device_register(&cdev->device); > + if (result) { > + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); > + kfree(cdev); > + return NULL; > + } > + > + /* sys I/F */ > + if (type) { > + result = device_create_file(&cdev->device, > + &dev_attr_cdev_type); > + if (result) > + goto unregister; > + } > + > + result = device_create_file(&cdev->device, &dev_attr_max_state); > + if (result) > + goto unregister; > + > + result = device_create_file(&cdev->device, &dev_attr_cur_state); > + if (result) > + goto unregister; > + > + mutex_lock(&thermal_list_lock); > + list_add(&cdev->node, &thermal_cdev_list); > + list_for_each_entry(pos, &thermal_tz_list, node) { > + if (!pos->ops->bind) > + continue; > + result = pos->ops->bind(pos, cdev); > + if (result) > + break; > + > + } > + mutex_unlock(&thermal_list_lock); > + > + if (!result) > + return cdev; > + > + unregister: Hidden label placement. Please check all labels. > + release_idr(&thermal_cdev_idr, &thermal_idr_lock, cdev->id); > + device_unregister(&cdev->device); > + return NULL; > +} > +EXPORT_SYMBOL(thermal_cooling_device_register); > + > +/** > + * thermal_cooling_device_unregister - removes the registered thermal > cooling device > + * No "blank" (" *") line allowed between function name-description and the function parameters. > + * @cdev: the thermal cooling device to remove. > + * > + * thermal_cooling_device_unregister() must be called when the device is no > + * longer needed. > + */ > +void thermal_cooling_device_unregister(struct > + thermal_cooling_device > + *cdev) > +{ ... > +} > +EXPORT_SYMBOL(thermal_cooling_device_unregister); > + > +/** > + * thermal_zone_device_register - register a new thermal zone device > + * @type: the thermal zone device type > + * @trips: the number of trip points the thermal zone support > + * @devdata: private device data > + * @ops: standard thermal zone device callbacks > + * > + * thermal_zone_device_unregister() must be called when the device is no > + * longer needed. > + */ > +struct thermal_zone_device *thermal_zone_device_register(char *type, > + int trips, void *devdata, > + struct thermal_zone_device_ops *ops) > +{ > + struct thermal_zone_device *tz; > + struct thermal_cooling_device *pos; > + int result; > + int count; > + > + if (strlen(type) >= THERMAL_NAME_LENGTH) > + return NULL; > + > + if (trips > THERMAL_MAX_TRIPS || trips < 0) > + return NULL; > + > + if (!ops || !ops->get_temp) > + return NULL; > + > + tz = kzalloc(sizeof(struct thermal_zone_device), GFP_KERNEL); > + if (!tz) > + return NULL; > + > + INIT_LIST_HEAD(&tz->cooling_devices); > + idr_init(&tz->idr); > + mutex_init(&tz->lock); > + result = get_idr(&thermal_tz_idr, &thermal_idr_lock, &tz->id); > + if (result) { > + kfree(tz); > + return NULL; > + } > + > + strcpy(tz->type, type); > + tz->ops = ops; > + tz->device.class = &thermal_class; > + tz->devdata = devdata; > + tz->trips = trips; > + sprintf(tz->device.bus_id, "thermal_zone%d", tz->id); > + result = device_register(&tz->device); > + if (result) { > + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); > + kfree(tz); > + return NULL; > + } > + > + /* sys I/F */ > + if (type) { > + result = device_create_file(&tz->device, &dev_attr_type); > + if (result) > + goto unregister; > + } > + > + result = device_create_file(&tz->device, &dev_attr_temp); > + if (result) > + goto unregister; > + > + if (ops->get_mode) { > + result = device_create_file(&tz->device, &dev_attr_mode); > + if (result) > + goto unregister; > + } > + > + for (count = 0; count < trips; count++) { > + TRIP_POINT_ATTR_ADD(&tz->device, count, result); > + if (result) > + goto unregister; > + } > + > + mutex_lock(&thermal_list_lock); > + list_add_tail(&tz->node, &thermal_tz_list); > + if (ops->bind) > + list_for_each_entry(pos, &thermal_cdev_list, node) { > + result = ops->bind(tz, pos); > + if (result) > + break; > + } > + mutex_unlock(&thermal_list_lock); > + > + if (!result) > + return tz; > + > + unregister: Hidden label. > + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); > + device_unregister(&tz->device); > + return NULL; > +} > +EXPORT_SYMBOL(thermal_zone_device_register); > + > +/** > + * thermal_device_unregister - removes the registered thermal zone device > + * No "blank" line here. > + * @tz: the thermal zone device to remove > + */ > +void thermal_zone_device_unregister(struct thermal_zone_device *tz) > +{ > + struct thermal_cooling_device *cdev; > + struct thermal_zone_device *pos = NULL; > + int count; > + > + if (!tz) > + return; > + > + mutex_lock(&thermal_list_lock); > + list_for_each_entry(pos, &thermal_tz_list, node) > + if (pos == tz) > + break; > + if (pos != tz) { > + /* thermal zone device not found */ > + mutex_unlock(&thermal_list_lock); > + return; > + } > + list_del(&tz->node); > + if (tz->ops->unbind) > + list_for_each_entry(cdev, &thermal_cdev_list, node) > + tz->ops->unbind(tz, cdev); > + mutex_unlock(&thermal_list_lock); > + > + if (tz->type[0]) > + device_remove_file(&tz->device, &dev_attr_type); > + device_remove_file(&tz->device, &dev_attr_temp); > + if (tz->ops->get_mode) > + device_remove_file(&tz->device, &dev_attr_mode); > + > + for (count = 0; count < tz->trips; count++) > + TRIP_POINT_ATTR_REMOVE(&tz->device, count); > + > + release_idr(&thermal_tz_idr, &thermal_idr_lock, tz->id); > + idr_destroy(&tz->idr); > + mutex_destroy(&tz->lock); > + device_unregister(&tz->device); > + return; > +} > +EXPORT_SYMBOL(thermal_zone_device_unregister); --- ~Randy - To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html