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); +}