mirror of
https://github.com/torvalds/linux.git
synced 2024-12-29 06:12:08 +00:00
Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux
Pull thermal management update from Zhang Rui: - Fix race condition in imx_thermal_probe() (Mikhail Lappo) - Add cooling device's statistics in sysfs (Viresh Kumar) * 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux: thermal: Add cooling device's statistics in sysfs thermal: imx: Fix race condition in imx_thermal_probe()
This commit is contained in:
commit
ba2b137d10
@ -255,6 +255,7 @@ temperature) and throttle appropriate devices.
|
||||
2. sysfs attributes structure
|
||||
|
||||
RO read only value
|
||||
WO write only value
|
||||
RW read/write value
|
||||
|
||||
Thermal sysfs attributes will be represented under /sys/class/thermal.
|
||||
@ -286,6 +287,11 @@ Thermal cooling device sys I/F, created once it's registered:
|
||||
|---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
|
||||
|---stats: Directory containing cooling device's statistics
|
||||
|---stats/reset: Writing any value resets the statistics
|
||||
|---stats/time_in_state_ms: Time (msec) spent in various cooling states
|
||||
|---stats/total_trans: Total number of times cooling state is changed
|
||||
|---stats/trans_table: Cooing state transition table
|
||||
|
||||
|
||||
Then next two dynamic attributes are created/removed in pairs. They represent
|
||||
@ -490,6 +496,31 @@ cur_state
|
||||
- cur_state == max_state means the maximum cooling.
|
||||
RW, Required
|
||||
|
||||
stats/reset
|
||||
Writing any value resets the cooling device's statistics.
|
||||
WO, Required
|
||||
|
||||
stats/time_in_state_ms:
|
||||
The amount of time spent by the cooling device in various cooling
|
||||
states. The output will have "<state> <time>" pair in each line, which
|
||||
will mean this cooling device spent <time> msec of time at <state>.
|
||||
Output will have one line for each of the supported states. usertime
|
||||
units here is 10mS (similar to other time exported in /proc).
|
||||
RO, Required
|
||||
|
||||
stats/total_trans:
|
||||
A single positive value showing the total number of times the state of a
|
||||
cooling device is changed.
|
||||
RO, Required
|
||||
|
||||
stats/trans_table:
|
||||
This gives fine grained information about all the cooling state
|
||||
transitions. The cat output here is a two dimensional matrix, where an
|
||||
entry <i,j> (row i, column j) represents the number of transitions from
|
||||
State_i to State_j. If the transition table is bigger than PAGE_SIZE,
|
||||
reading this will return an -EFBIG error.
|
||||
RO, Required
|
||||
|
||||
3. A simple implementation
|
||||
|
||||
ACPI thermal zone may support multiple trip points like critical, hot,
|
||||
|
@ -15,6 +15,13 @@ menuconfig THERMAL
|
||||
|
||||
if THERMAL
|
||||
|
||||
config THERMAL_STATISTICS
|
||||
bool "Thermal state transition statistics"
|
||||
help
|
||||
Export thermal state transition statistics information through sysfs.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config THERMAL_EMERGENCY_POWEROFF_DELAY_MS
|
||||
int "Emergency poweroff delay in milli-seconds"
|
||||
depends on THERMAL
|
||||
|
@ -637,6 +637,9 @@ static int imx_thermal_probe(struct platform_device *pdev)
|
||||
regmap_write(map, TEMPSENSE0 + REG_CLR, TEMPSENSE0_POWER_DOWN);
|
||||
regmap_write(map, TEMPSENSE0 + REG_SET, TEMPSENSE0_MEASURE_TEMP);
|
||||
|
||||
data->irq_enabled = true;
|
||||
data->mode = THERMAL_DEVICE_ENABLED;
|
||||
|
||||
ret = devm_request_threaded_irq(&pdev->dev, data->irq,
|
||||
imx_thermal_alarm_irq, imx_thermal_alarm_irq_thread,
|
||||
0, "imx_thermal", data);
|
||||
@ -649,9 +652,6 @@ static int imx_thermal_probe(struct platform_device *pdev)
|
||||
return ret;
|
||||
}
|
||||
|
||||
data->irq_enabled = true;
|
||||
data->mode = THERMAL_DEVICE_ENABLED;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -972,8 +972,8 @@ __thermal_cooling_device_register(struct device_node *np,
|
||||
cdev->ops = ops;
|
||||
cdev->updated = false;
|
||||
cdev->device.class = &thermal_class;
|
||||
thermal_cooling_device_setup_sysfs(cdev);
|
||||
cdev->devdata = devdata;
|
||||
thermal_cooling_device_setup_sysfs(cdev);
|
||||
dev_set_name(&cdev->device, "cooling_device%d", cdev->id);
|
||||
result = device_register(&cdev->device);
|
||||
if (result) {
|
||||
@ -1106,6 +1106,7 @@ void thermal_cooling_device_unregister(struct thermal_cooling_device *cdev)
|
||||
|
||||
ida_simple_remove(&thermal_cdev_ida, cdev->id);
|
||||
device_unregister(&cdev->device);
|
||||
thermal_cooling_device_destroy_sysfs(cdev);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(thermal_cooling_device_unregister);
|
||||
|
||||
|
@ -73,6 +73,7 @@ int thermal_build_list_of_policies(char *buf);
|
||||
int thermal_zone_create_device_groups(struct thermal_zone_device *, int);
|
||||
void thermal_zone_destroy_device_groups(struct thermal_zone_device *);
|
||||
void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *);
|
||||
void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev);
|
||||
/* used only at binding time */
|
||||
ssize_t
|
||||
thermal_cooling_device_trip_point_show(struct device *,
|
||||
@ -84,6 +85,15 @@ ssize_t thermal_cooling_device_weight_store(struct device *,
|
||||
struct device_attribute *,
|
||||
const char *, size_t);
|
||||
|
||||
#ifdef CONFIG_THERMAL_STATISTICS
|
||||
void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
|
||||
unsigned long new_state);
|
||||
#else
|
||||
static inline void
|
||||
thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
|
||||
unsigned long new_state) {}
|
||||
#endif /* CONFIG_THERMAL_STATISTICS */
|
||||
|
||||
#ifdef CONFIG_THERMAL_GOV_STEP_WISE
|
||||
int thermal_gov_step_wise_register(void);
|
||||
void thermal_gov_step_wise_unregister(void);
|
||||
|
@ -187,7 +187,10 @@ void thermal_cdev_update(struct thermal_cooling_device *cdev)
|
||||
if (instance->target > target)
|
||||
target = instance->target;
|
||||
}
|
||||
cdev->ops->set_cur_state(cdev, target);
|
||||
|
||||
if (!cdev->ops->set_cur_state(cdev, target))
|
||||
thermal_cooling_device_stats_update(cdev, target);
|
||||
|
||||
cdev->updated = true;
|
||||
mutex_unlock(&cdev->lock);
|
||||
trace_cdev_update(cdev, target);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/jiffies.h>
|
||||
|
||||
#include "thermal_core.h"
|
||||
|
||||
@ -721,6 +722,7 @@ thermal_cooling_device_cur_state_store(struct device *dev,
|
||||
result = cdev->ops->set_cur_state(cdev, state);
|
||||
if (result)
|
||||
return result;
|
||||
thermal_cooling_device_stats_update(cdev, state);
|
||||
return count;
|
||||
}
|
||||
|
||||
@ -745,14 +747,237 @@ static const struct attribute_group cooling_device_attr_group = {
|
||||
|
||||
static const struct attribute_group *cooling_device_attr_groups[] = {
|
||||
&cooling_device_attr_group,
|
||||
NULL, /* Space allocated for cooling_device_stats_attr_group */
|
||||
NULL,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_THERMAL_STATISTICS
|
||||
struct cooling_dev_stats {
|
||||
spinlock_t lock;
|
||||
unsigned int total_trans;
|
||||
unsigned long state;
|
||||
unsigned long max_states;
|
||||
ktime_t last_time;
|
||||
ktime_t *time_in_state;
|
||||
unsigned int *trans_table;
|
||||
};
|
||||
|
||||
static void update_time_in_state(struct cooling_dev_stats *stats)
|
||||
{
|
||||
ktime_t now = ktime_get(), delta;
|
||||
|
||||
delta = ktime_sub(now, stats->last_time);
|
||||
stats->time_in_state[stats->state] =
|
||||
ktime_add(stats->time_in_state[stats->state], delta);
|
||||
stats->last_time = now;
|
||||
}
|
||||
|
||||
void thermal_cooling_device_stats_update(struct thermal_cooling_device *cdev,
|
||||
unsigned long new_state)
|
||||
{
|
||||
struct cooling_dev_stats *stats = cdev->stats;
|
||||
|
||||
spin_lock(&stats->lock);
|
||||
|
||||
if (stats->state == new_state)
|
||||
goto unlock;
|
||||
|
||||
update_time_in_state(stats);
|
||||
stats->trans_table[stats->state * stats->max_states + new_state]++;
|
||||
stats->state = new_state;
|
||||
stats->total_trans++;
|
||||
|
||||
unlock:
|
||||
spin_unlock(&stats->lock);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
thermal_cooling_device_total_trans_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
||||
struct cooling_dev_stats *stats = cdev->stats;
|
||||
int ret;
|
||||
|
||||
spin_lock(&stats->lock);
|
||||
ret = sprintf(buf, "%u\n", stats->total_trans);
|
||||
spin_unlock(&stats->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
thermal_cooling_device_time_in_state_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
||||
struct cooling_dev_stats *stats = cdev->stats;
|
||||
ssize_t len = 0;
|
||||
int i;
|
||||
|
||||
spin_lock(&stats->lock);
|
||||
update_time_in_state(stats);
|
||||
|
||||
for (i = 0; i < stats->max_states; i++) {
|
||||
len += sprintf(buf + len, "state%u\t%llu\n", i,
|
||||
ktime_to_ms(stats->time_in_state[i]));
|
||||
}
|
||||
spin_unlock(&stats->lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
thermal_cooling_device_reset_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
||||
struct cooling_dev_stats *stats = cdev->stats;
|
||||
int i, states = stats->max_states;
|
||||
|
||||
spin_lock(&stats->lock);
|
||||
|
||||
stats->total_trans = 0;
|
||||
stats->last_time = ktime_get();
|
||||
memset(stats->trans_table, 0,
|
||||
states * states * sizeof(*stats->trans_table));
|
||||
|
||||
for (i = 0; i < stats->max_states; i++)
|
||||
stats->time_in_state[i] = ktime_set(0, 0);
|
||||
|
||||
spin_unlock(&stats->lock);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
thermal_cooling_device_trans_table_show(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct thermal_cooling_device *cdev = to_cooling_device(dev);
|
||||
struct cooling_dev_stats *stats = cdev->stats;
|
||||
ssize_t len = 0;
|
||||
int i, j;
|
||||
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, " From : To\n");
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, " : ");
|
||||
for (i = 0; i < stats->max_states; i++) {
|
||||
if (len >= PAGE_SIZE)
|
||||
break;
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "state%2u ", i);
|
||||
}
|
||||
if (len >= PAGE_SIZE)
|
||||
return PAGE_SIZE;
|
||||
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
|
||||
|
||||
for (i = 0; i < stats->max_states; i++) {
|
||||
if (len >= PAGE_SIZE)
|
||||
break;
|
||||
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "state%2u:", i);
|
||||
|
||||
for (j = 0; j < stats->max_states; j++) {
|
||||
if (len >= PAGE_SIZE)
|
||||
break;
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "%8u ",
|
||||
stats->trans_table[i * stats->max_states + j]);
|
||||
}
|
||||
if (len >= PAGE_SIZE)
|
||||
break;
|
||||
len += snprintf(buf + len, PAGE_SIZE - len, "\n");
|
||||
}
|
||||
|
||||
if (len >= PAGE_SIZE) {
|
||||
pr_warn_once("Thermal transition table exceeds PAGE_SIZE. Disabling\n");
|
||||
return -EFBIG;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(total_trans, 0444, thermal_cooling_device_total_trans_show,
|
||||
NULL);
|
||||
static DEVICE_ATTR(time_in_state_ms, 0444,
|
||||
thermal_cooling_device_time_in_state_show, NULL);
|
||||
static DEVICE_ATTR(reset, 0200, NULL, thermal_cooling_device_reset_store);
|
||||
static DEVICE_ATTR(trans_table, 0444,
|
||||
thermal_cooling_device_trans_table_show, NULL);
|
||||
|
||||
static struct attribute *cooling_device_stats_attrs[] = {
|
||||
&dev_attr_total_trans.attr,
|
||||
&dev_attr_time_in_state_ms.attr,
|
||||
&dev_attr_reset.attr,
|
||||
&dev_attr_trans_table.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group cooling_device_stats_attr_group = {
|
||||
.attrs = cooling_device_stats_attrs,
|
||||
.name = "stats"
|
||||
};
|
||||
|
||||
static void cooling_device_stats_setup(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
struct cooling_dev_stats *stats;
|
||||
unsigned long states;
|
||||
int var;
|
||||
|
||||
if (cdev->ops->get_max_state(cdev, &states))
|
||||
return;
|
||||
|
||||
states++; /* Total number of states is highest state + 1 */
|
||||
|
||||
var = sizeof(*stats);
|
||||
var += sizeof(*stats->time_in_state) * states;
|
||||
var += sizeof(*stats->trans_table) * states * states;
|
||||
|
||||
stats = kzalloc(var, GFP_KERNEL);
|
||||
if (!stats)
|
||||
return;
|
||||
|
||||
stats->time_in_state = (ktime_t *)(stats + 1);
|
||||
stats->trans_table = (unsigned int *)(stats->time_in_state + states);
|
||||
cdev->stats = stats;
|
||||
stats->last_time = ktime_get();
|
||||
stats->max_states = states;
|
||||
|
||||
spin_lock_init(&stats->lock);
|
||||
|
||||
/* Fill the empty slot left in cooling_device_attr_groups */
|
||||
var = ARRAY_SIZE(cooling_device_attr_groups) - 2;
|
||||
cooling_device_attr_groups[var] = &cooling_device_stats_attr_group;
|
||||
}
|
||||
|
||||
static void cooling_device_stats_destroy(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
kfree(cdev->stats);
|
||||
cdev->stats = NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline void
|
||||
cooling_device_stats_setup(struct thermal_cooling_device *cdev) {}
|
||||
static inline void
|
||||
cooling_device_stats_destroy(struct thermal_cooling_device *cdev) {}
|
||||
|
||||
#endif /* CONFIG_THERMAL_STATISTICS */
|
||||
|
||||
void thermal_cooling_device_setup_sysfs(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
cooling_device_stats_setup(cdev);
|
||||
cdev->device.groups = cooling_device_attr_groups;
|
||||
}
|
||||
|
||||
void thermal_cooling_device_destroy_sysfs(struct thermal_cooling_device *cdev)
|
||||
{
|
||||
cooling_device_stats_destroy(cdev);
|
||||
}
|
||||
|
||||
/* these helper will be used only at the time of bindig */
|
||||
ssize_t
|
||||
thermal_cooling_device_trip_point_show(struct device *dev,
|
||||
|
@ -148,6 +148,7 @@ struct thermal_cooling_device {
|
||||
struct device device;
|
||||
struct device_node *np;
|
||||
void *devdata;
|
||||
void *stats;
|
||||
const struct thermal_cooling_device_ops *ops;
|
||||
bool updated; /* true if the cooling device does not need update */
|
||||
struct mutex lock; /* protect thermal_instances list */
|
||||
|
Loading…
Reference in New Issue
Block a user