From e0cb6023dcc317fe2fd35faf0e7f0ee7df7d2e00 Mon Sep 17 00:00:00 2001 From: Ram Chandrasekar Date: Mon, 13 Jan 2020 17:10:55 -0700 Subject: [PATCH] drivers: thermal: Add support to change thermal zone configuration Add debug file system node to configure the thermal zone parameters dynamically. This node will use the same format that is used to print the configuration of the thermal zone. Change-Id: I4fd8cdafcb689f4d986738864e31177401f9bc47 Signed-off-by: Ram Chandrasekar --- drivers/thermal/Makefile | 3 + drivers/thermal/of-thermal.c | 11 + drivers/thermal/thermal_core.c | 2 + drivers/thermal/thermal_core.h | 8 + drivers/thermal/thermal_debugfs.c | 394 ++++++++++++++++++++++++++++++ 5 files changed, 418 insertions(+) create mode 100644 drivers/thermal/thermal_debugfs.c diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 7243037382bf..191ce99e4953 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -27,6 +27,9 @@ thermal_sys-$(CONFIG_CLOCK_THERMAL) += clock_cooling.o # devfreq cooling thermal_sys-$(CONFIG_DEVFREQ_THERMAL) += devfreq_cooling.o +# debugfs support +thermal_sys-$(CONFIG_QTI_THERMAL) += thermal_debugfs.o + # platform thermal drivers obj-y += broadcom/ obj-$(CONFIG_THERMAL_MMIO) += thermal_mmio.o diff --git a/drivers/thermal/of-thermal.c b/drivers/thermal/of-thermal.c index 51b7c10a2507..6ca6d9c2c735 100644 --- a/drivers/thermal/of-thermal.c +++ b/drivers/thermal/of-thermal.c @@ -466,8 +466,19 @@ static int of_thermal_set_mode(struct thermal_zone_device *tz, mutex_lock(&tz->lock); if (mode == THERMAL_DEVICE_ENABLED) { +#ifdef CONFIG_QTI_THERMAL + if (!tz->polling_delay) + tz->polling_delay = data->polling_delay; + else + data->polling_delay = tz->polling_delay; + if (!tz->passive_delay) + tz->passive_delay = data->passive_delay; + else + data->passive_delay = tz->passive_delay; +#else tz->polling_delay = data->polling_delay; tz->passive_delay = data->passive_delay; +#endif } else { tz->polling_delay = 0; tz->passive_delay = 0; diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 40f5bce822a5..e47ac29af1d0 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1799,6 +1799,7 @@ static int __init thermal_init(void) if (result) pr_warn("Thermal: Can not register suspend notifier, return %d\n", result); + thermal_debug_init(); return 0; @@ -1824,6 +1825,7 @@ static void thermal_exit(void) destroy_workqueue(thermal_passive_wq); genetlink_exit(); class_unregister(&thermal_class); + thermal_debug_exit(); thermal_unregister_governors(); ida_destroy(&thermal_tz_ida); ida_destroy(&thermal_cdev_ida); diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index 4e4898416e9e..3d60ddbd5ce5 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -126,6 +126,8 @@ void of_thermal_handle_trip(struct device *dev, void of_thermal_handle_trip_temp(struct device *dev, struct thermal_zone_device *tz, int trip_temp); +int thermal_debug_init(void); +void thermal_debug_exit(void); #else static inline int of_thermal_aggregate_trip(struct device *dev, struct thermal_zone_device *tz, @@ -141,6 +143,12 @@ static inline void of_thermal_handle_trip_temp(struct device *dev, struct thermal_zone_device *tz, int trip_temp) { } +static inline int thermal_debug_init(void) +{ + return -ENODEV; +} +static inline void thermal_debug_exit(void) +{ } #endif #endif /* __THERMAL_CORE_H__ */ diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c new file mode 100644 index 000000000000..cf4e4c94df47 --- /dev/null +++ b/drivers/thermal/thermal_debugfs.c @@ -0,0 +1,394 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +#include "thermal_core.h" + +#define SPACE_DELIMITER " \t" + +static struct dentry *thermal_debugfs_parent; +static struct dentry *thermal_debugfs_config; + +static int fetch_cdev(struct thermal_zone_device *tz, char *dev_token, + char *upper_lim_token, char *lower_lim_token, int trip) +{ + unsigned long upper_limit, lower_limit; + char cdev_name[THERMAL_NAME_LENGTH] = ""; + char limit_str[THERMAL_NAME_LENGTH] = ""; + struct thermal_instance *instance; + bool match_found = false; + + dev_token = strim(dev_token); + if (sscanf(dev_token, "%20[^ +\n\t]", cdev_name) != 1) + return -EINVAL; + list_for_each_entry(instance, &tz->thermal_instances, tz_node) { + if (!instance->cdev || instance->trip != trip) + continue; + + if (strcmp(instance->cdev->type, cdev_name)) + continue; + match_found = true; + break; + } + if (!match_found) + return -ENODEV; + if (upper_lim_token) { + if (sscanf(upper_lim_token, "%20[^ +\n\t]", limit_str) != 1) + return -EINVAL; + if (kstrtoul(limit_str, 0, &upper_limit)) + return -EINVAL; + instance->upper = upper_limit; + } + if (lower_lim_token) { + if (sscanf(lower_lim_token, "%20[^ +\n\t]", limit_str) != 1) + return -EINVAL; + if (kstrtoul(limit_str, 0, &lower_limit)) + return -EINVAL; + instance->lower = lower_limit; + } + + return 0; +} + +static int fetch_and_update_cdev(struct thermal_zone_device *tz, int trip, + char **dev_buf, char *dev_buf_end, + char **up_buf, char *up_buf_end, + char **low_buf, char *low_buf_end) +{ + int ret; + char *dev_token, *upper_lim_token, *lower_lim_token, *dev1_token; + + dev_token = strsep(dev_buf, SPACE_DELIMITER); + if (*up_buf) { + upper_lim_token = strsep(up_buf, SPACE_DELIMITER); + upper_lim_token = strnchr(*up_buf, up_buf_end - *up_buf, + ' '); + if (upper_lim_token && upper_lim_token < up_buf_end) + up_buf_end = upper_lim_token; + } + if (*low_buf) { + lower_lim_token = strsep(low_buf, SPACE_DELIMITER); + lower_lim_token = strnchr(*low_buf, low_buf_end - *low_buf, + ' '); + if (lower_lim_token && lower_lim_token < low_buf_end) + low_buf_end = lower_lim_token; + } + if (dev_token && *dev_buf < dev_buf_end) { + do { + ret = fetch_cdev(tz, *dev_buf, *up_buf, + *low_buf, trip); + if (ret) + return ret; + dev1_token = strnchr(*dev_buf, dev_buf_end - *dev_buf, + '+'); + if (!dev1_token || dev1_token >= dev_buf_end) + break; + dev1_token = strsep(dev_buf, "+"); + if (*up_buf) { + upper_lim_token = strnchr(*up_buf, + up_buf_end - *up_buf, + '+'); + if (!upper_lim_token || + upper_lim_token >= up_buf_end) + return -EINVAL; + upper_lim_token = strsep(up_buf, "+"); + } + if (*low_buf) { + lower_lim_token = strnchr(*low_buf, + low_buf_end - *low_buf, + '+'); + if (!lower_lim_token || + lower_lim_token >= low_buf_end) + return -EINVAL; + lower_lim_token = strsep(low_buf, "+"); + } + } while (dev1_token); + } + + return 0; +} + +static int fetch_and_update_threshold(struct thermal_zone_device *tz, int trip, + char **temp_buf, char *temp_buf_end, bool is_trip) +{ + int ret, trip_temp; + char *tripT_token; + + tripT_token = strsep(temp_buf, SPACE_DELIMITER); + if (tripT_token && *temp_buf < temp_buf_end) { + if (kstrtoint(*temp_buf, 0, &trip_temp)) + return -EINVAL; + if (is_trip) + ret = tz->ops->set_trip_temp(tz, trip, trip_temp); + else + ret = tz->ops->set_trip_hyst(tz, trip, trip_temp); + if (ret) + return ret; + } else { + return -EINVAL; + } + + return 0; +} + +static int parse_threshold(struct thermal_zone_device *tz, char *buf_ptr, + size_t count) +{ + char *trip_buf_end = NULL, *temp_buf_end = NULL, *hyst_buf_end = NULL; + char *trip_buf = NULL, *temp_buf = NULL, *hyst_buf = NULL; + char *dev_buf_end = NULL, *up_buf_end = NULL, *low_buf_end = NULL; + char *dev_buf = NULL, *up_buf = NULL, *low_buf = NULL; + char *buf = buf_ptr, *token = NULL; + int ret; + + trip_buf = strnstr(buf, "trip", count); + trip_buf_end = strnchr(trip_buf, buf_ptr + count - trip_buf, '\n'); + if (!trip_buf_end) + return -EINVAL; + + temp_buf = strnstr(buf, "trip_threshold", count); + if (!temp_buf) + goto eval_device; + if (!tz->ops->set_trip_temp) + return -EPERM; + + temp_buf_end = strnchr(temp_buf, buf_ptr + count - temp_buf, '\n'); + if (!temp_buf_end) + return -EINVAL; + + hyst_buf = strnstr(buf, "trip_threshold_clr", count); + if (hyst_buf) { + if (!tz->ops->set_trip_hyst) + return -EPERM; + hyst_buf_end = strnchr(hyst_buf, + buf_ptr + count - hyst_buf, '\n'); + if (!hyst_buf_end) + return -EINVAL; + } + +eval_device: + dev_buf = strnstr(buf, "device", count); + if (!dev_buf) { + if (!temp_buf) + return -EINVAL; + goto parse_config; + } + dev_buf_end = strnchr(dev_buf, buf_ptr + count - dev_buf, '\n'); + if (!dev_buf_end) + return -EINVAL; + + up_buf = strnstr(buf, "upper_limit", count); + if (!up_buf) + return -EINVAL; + up_buf_end = strnchr(up_buf, buf_ptr + count - up_buf, '\n'); + if (!up_buf_end) + return -EINVAL; + + low_buf = strnstr(buf, "lower_limit", count); + if (!low_buf) + return -EINVAL; + low_buf_end = strnchr(low_buf, buf_ptr + count - low_buf, '\n'); + if (!low_buf_end) + return -EINVAL; +parse_config: + *trip_buf_end = '\0'; + if (temp_buf_end) + *temp_buf_end = '\0'; + if (hyst_buf_end) + *hyst_buf_end = '\0'; + if (dev_buf_end) + *dev_buf_end = '\0'; + if (up_buf_end) + *up_buf_end = '\0'; + if (low_buf_end) + *low_buf_end = '\0'; + token = strnchr(trip_buf, trip_buf_end - trip_buf, ' '); + while (token && token < trip_buf_end) { + int trip; + + token = strsep((char **)&trip_buf, " "); + if (kstrtoint(trip_buf, 0, &trip)) + return -EINVAL; + + if (temp_buf) { + ret = fetch_and_update_threshold(tz, trip, &temp_buf, + temp_buf_end, true); + if (ret) + return ret; + } + + if (hyst_buf) { + ret = fetch_and_update_threshold(tz, trip, &hyst_buf, + hyst_buf_end, false); + if (ret) + return ret; + } + + if (dev_buf) { + ret = fetch_and_update_cdev(tz, trip, &dev_buf, + dev_buf_end, &up_buf, up_buf_end, + &low_buf, low_buf_end); + if (ret) + return ret; + } + token = strnchr(trip_buf, trip_buf_end - trip_buf, ' '); + } + + return 0; +} + +static int parse_delay(struct thermal_zone_device *tz, char *buf, + size_t count, bool is_polling) +{ + int delay = 0; + + if (is_polling) { + if (sscanf(buf, "polling_delay %d", &delay) != 1) + return -EINVAL; + tz->polling_delay = delay; + } else { + if (sscanf(buf, "passive_polling_delay %d", &delay) != 1) + return -EINVAL; + tz->passive_delay = delay; + } + + return 0; +} + +static int parse_config(struct thermal_zone_device *tz, char *buf_ptr, + size_t buf_ct) +{ + char *buf, *buf_end, *next_buf, *curr_buf; + int count, ret = 0; + enum thermal_device_mode mode = THERMAL_DEVICE_ENABLED; + + if (tz->ops->get_mode) { + ret = tz->ops->get_mode(tz, &mode); + if (ret) + return ret; + } + if (tz->ops->set_mode) { + ret = tz->ops->set_mode(tz, THERMAL_DEVICE_DISABLED); + if (ret) + return ret; + } + mutex_lock(&tz->lock); + buf = kzalloc(sizeof(char) * (buf_ct + 1), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto parse_exit; + } + strlcpy(buf, buf_ptr, (buf_ct + 1)); + buf_end = buf + buf_ct; + if (strnstr(buf, "trip", buf_ct)) { + ret = parse_threshold(tz, buf, buf_ct); + if (ret) + goto parse_exit; + } + strlcpy(buf, buf_ptr, (buf_ct + 1)); + curr_buf = next_buf = buf; + strsep(&next_buf, "\n"); + while (curr_buf && curr_buf < buf_end) { + if (next_buf) + count = next_buf - curr_buf; + else + count = buf_end - curr_buf + 1; + if (strnstr(curr_buf, "passive_polling_delay", count)) + ret = parse_delay(tz, curr_buf, count, false); + else if (strnstr(curr_buf, "polling_delay", count)) + ret = parse_delay(tz, curr_buf, count, true); + + if (ret) + goto parse_exit; + + curr_buf = next_buf; + if (next_buf < buf_end) + strsep(&next_buf, "\n"); + else + next_buf = NULL; + } +parse_exit: + mutex_unlock(&tz->lock); + if (mode == THERMAL_DEVICE_ENABLED && tz->ops->set_mode) + tz->ops->set_mode(tz, THERMAL_DEVICE_ENABLED); + kfree(buf); + return ret ? ret : buf_ct; +} + +static ssize_t thermal_dbgfs_config_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct thermal_zone_device *tz = NULL; + char *sensor_buf = NULL, sensor_name[THERMAL_NAME_LENGTH] = "", *buf; + int ret = -EINVAL; + + buf = kzalloc(sizeof(char) * (count + 1), GFP_KERNEL); + if (!buf) + return -ENOMEM; + if (copy_from_user(buf, user_buf, count)) { + ret = -EFAULT; + goto config_exit; + } + sensor_buf = strnstr(buf, "sensor", count); + if (sensor_buf) { + if (sscanf(sensor_buf, "sensor %20[^\n\t ]", + sensor_name) != 1) { + pr_err("sensor name not found\n"); + ret = -EINVAL; + goto config_exit; + } + tz = thermal_zone_get_zone_by_name((const char *)sensor_name); + if (IS_ERR(tz)) { + ret = PTR_ERR(tz); + pr_err("No thermal zone for sensor:%s. err:%d\n", + sensor_name, ret); + goto config_exit; + } + ret = parse_config(tz, buf, count); + } + +config_exit: + kfree(buf); + return ret; +} + +static const struct file_operations thermal_dbgfs_config_fops = { + .write = thermal_dbgfs_config_write, +}; + +int thermal_debug_init(void) +{ + int ret = 0; + + thermal_debugfs_parent = debugfs_create_dir("thermal", NULL); + if (IS_ERR_OR_NULL(thermal_debugfs_parent)) { + ret = PTR_ERR(thermal_debugfs_parent); + pr_err("Error creating thermal debugfs directory. err:%d\n", + ret); + return ret; + } + thermal_debugfs_config = debugfs_create_file("config", 0200, + thermal_debugfs_parent, NULL, + &thermal_dbgfs_config_fops); + if (IS_ERR_OR_NULL(thermal_debugfs_config)) { + ret = PTR_ERR(thermal_debugfs_config); + pr_err("Error creating thermal config debugfs. err:%d\n", + ret); + return ret; + } + + return ret; +} + +void thermal_debug_exit(void) +{ + debugfs_remove_recursive(thermal_debugfs_parent); +}