This patch implements a generic CPU hotplug cooling device. The implementation scales down the number of running CPUs when temperature increases through a thermal trip point and prevents booting CPUs until thermal conditions are restored. Upon restoration, the action of starting up a CPU is left to another entity (e.g. CPU offline governor, for which a patch is in the works). In the past two years, ARM considerably reduced the time required for CPUs to boot and shutdown; this time is now measured in microseconds. This patch is predominantly intended for ARM big.LITTLE architectures where big cores are expected to have a much bigger impact on thermal budget than little cores, resulting in fast temperature ramps to a trip point, i.e. thermal runaways. Switching off the big core(s) may be one of the recovery mechanisms to restore system temperature, but the actual strategy is left to the thermal governor. The assumption is that CPU shutdown/startup is a rare event, so no attempt was made to make the code atomic, i.e. the code evidently races with CPU hotplug driver. The set_cur_state() function offlines CPUs iteratively one at a time, checking the cooling state before each CPU shutdown. A hotplug notifier callback validates any CPU boot requests against current cooling state and approves/denies accordingly. This mechanism guarantees that the desired cooling state could be reached in a maximum of d-c iterations, where d and c are the "desired" and "current" cooling states expressed in the number of offline CPUs. Credits to Amit Daniel Kachhap for initial attempt to upstream this feature. Cc: Zhang Rui <rui.zhang@xxxxxxxxx> Cc: Eduardo Valentin <eduardo.valentin@xxxxxx> Cc: Rob Landley <rob@xxxxxxxxxxx> Cc: Amit Daniel Kachhap <amit.daniel@xxxxxxxxxxx> Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> Cc: Durgadoss R <durgadoss.r@xxxxxxxxx> Cc: Christian Daudt <bcm@xxxxxxxxxxxxx> Cc: James King <james.king@xxxxxxxxxx> Signed-off-by: Zoran Markovic <zoran.markovic@xxxxxxxxxx> --- Documentation/thermal/cpu-cooling-api.txt | 17 ++ drivers/thermal/Kconfig | 10 + drivers/thermal/Makefile | 1 + drivers/thermal/cpu_hotplug.c | 362 +++++++++++++++++++++++++++++ include/linux/cpuhp_cooling.h | 57 +++++ 5 files changed, 447 insertions(+) create mode 100644 drivers/thermal/cpu_hotplug.c create mode 100644 include/linux/cpuhp_cooling.h diff --git a/Documentation/thermal/cpu-cooling-api.txt b/Documentation/thermal/cpu-cooling-api.txt index fca24c9..2f94f68 100644 --- a/Documentation/thermal/cpu-cooling-api.txt +++ b/Documentation/thermal/cpu-cooling-api.txt @@ -30,3 +30,20 @@ the user. The registration APIs returns the cooling device pointer. This interface function unregisters the "thermal-cpufreq-%x" cooling device. cdev: Cooling device pointer which has to be unregistered. + +1.2 cpu hotplug registration/unregistration APIs +1.2.1 struct thermal_cooling_device *cpuhp_cooling_register( + struct cpumask *cpus, const char *ext) + + This function creates and registers a cpu hotplug cooling device with + the name "cpu-hotplug-%s". + + cpus: cpumask of cpu cores participating in cooling. + ext: instance-specific name of device + +1.2.2 void cpuhotplug_cooling_unregister(struct thermal_cooling_device *cdev) + + This function unregisters and frees the cpu hotplug cooling device cdev. + + cdev: Pointer to cooling device to unregister. + diff --git a/drivers/thermal/Kconfig b/drivers/thermal/Kconfig index 52b6ed7..3509100 100644 --- a/drivers/thermal/Kconfig +++ b/drivers/thermal/Kconfig @@ -79,6 +79,16 @@ config CPU_THERMAL If you want this support, you should say Y here. +config CPU_THERMAL_HOTPLUG + bool "Generic CPU hotplug cooling" + depends on HOTPLUG_CPU + help + Shutdown CPUs to prevent the device from overheating. This feature + uses generic CPU hot-unplug capabilities to control device + temperature. When the temperature increases over a trip point, a + random subset of CPUs is shut down to reach the desired cooling + state. + config THERMAL_EMULATION bool "Thermal emulation mode support" help diff --git a/drivers/thermal/Makefile b/drivers/thermal/Makefile index 5ee0db0..0bd08be 100644 --- a/drivers/thermal/Makefile +++ b/drivers/thermal/Makefile @@ -12,6 +12,7 @@ thermal_sys-$(CONFIG_THERMAL_GOV_USER_SPACE) += user_space.o # cpufreq cooling thermal_sys-$(CONFIG_CPU_THERMAL) += cpu_cooling.o +thermal_sys-$(CONFIG_CPU_THERMAL_HOTPLUG) += cpu_hotplug.o # platform thermal drivers obj-$(CONFIG_SPEAR_THERMAL) += spear_thermal.o diff --git a/drivers/thermal/cpu_hotplug.c b/drivers/thermal/cpu_hotplug.c new file mode 100644 index 0000000..8c3021e --- /dev/null +++ b/drivers/thermal/cpu_hotplug.c @@ -0,0 +1,362 @@ +/* + * drivers/thermal/cpu_hotplug.c + * + * Copyright (C) 2013 Broadcom Corporation Ltd. + * Copyright (C) 2013 Zoran Markovic <zoran.markovic@xxxxxxxxxx> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <linux/module.h> +#include <linux/thermal.h> +#include <linux/workqueue.h> +#include <linux/cpu.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/cpuhp_cooling.h> + +/** + * struct cpuhotplug_cooling_device - cpu hotplug cooling device data + * @cpus: cpu mask representing cpus that can be hot-unplugged for cooling + * @cdev: pointer to generic cooling device + */ +struct cpuhotplug_cooling_device { + unsigned int target; + struct cpumask cpus; + struct thermal_cooling_device *cdev; + struct list_head list; +}; + +/** + * cpuhotplug_list - list of all cpu hotplug cooling devices. Traversed + * by cpu hotplug notifier to check constraints on booting cpus. Locked + * by cpuhotplug_cooling_lock mutex. + */ +static LIST_HEAD(cpuhotplug_list); +static DEFINE_MUTEX(cpuhotplug_cooling_lock); + +/** + * boot_cpu - return index of boot CPU; same criteria as in + * disable_nonboot_cpus() + */ +static inline int boot_cpu(void) +{ + int cpu; + get_online_cpus(); + cpu = cpumask_first(cpu_online_mask); + put_online_cpus(); + return cpu; +} + +/** + * random_online_cpu - pick any online hot-unpluggable cpu + * @d: pointer to cpuhotplug_cooling_device containing hot-pluggable cpu mask + */ +static inline int random_online_cpu(struct cpuhotplug_cooling_device *d) +{ + int cpu; + + get_online_cpus(); + cpu = any_online_cpu(d->cpus); + put_online_cpus(); + + return cpu; +} + +/** + * _num_offline_cpus - number of hot-pluggable cpus currently offline + * @d: pointer to cpuhotplug_cooling_device containing hot-pluggable cpu mask + */ +static inline int _num_offline_cpus(struct cpuhotplug_cooling_device *d) +{ + struct cpumask offline; + + cpumask_andnot(&offline, &(d->cpus), cpu_online_mask); + return cpumask_weight(&offline); +} + +/** + * num_offline_cpus - same as _num_offline_cpus, but safe from background + * hotplug events. + * @d: pointer to cpuhotplug_cooling_device containing hot-pluggable cpu mask + */ +static inline int num_offline_cpus(struct cpuhotplug_cooling_device *d) +{ + int num; + + get_online_cpus(); + num = _num_offline_cpus(d); + put_online_cpus(); + + return num; +} + +/** + * cpuhotplug_get_max_state - get maximum cooling state of device + * @cdev: pointer to generic cooling device + * @state: returned maximum cooling state + * + * Thermal framework callback to get the maximum cooling state of cpu + * hotplug cooling device. + * + * Return: always 0. + */ +static int cpuhotplug_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cpuhotplug_cooling_device *d = cdev->devdata; + + /* defined as number of CPUs in hot-pluggable mask: this is invariant */ + *state = cpumask_weight(&(d->cpus)); + + return 0; +} + +/** + * cpuhotplug_get_cur_state - get current cooling state of device + * @cdev: pointer to generic cooling device + * @state: current cooling state + * + * Thermal framework callback to get the current cooling state of cpu + * hotplug cooling device. + * + * Return: always 0. + */ +static int cpuhotplug_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *state) +{ + struct cpuhotplug_cooling_device *d = cdev->devdata; + + *state = d->target; + + return 0; +} + +/** + * cpuhotplug_get_cur_state - set cooling state of device + * @cdev: pointer to generic cooling device + * @state: cooling state + * + * Thermal framework callback to set/change cooling state of cpu hotplug + * cooling device. + * + * Return: 0 on success, or error code otherwise + */ +static int cpuhotplug_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) +{ + struct cpuhotplug_cooling_device *d = cdev->devdata; + unsigned long cstate; + unsigned int cpu; + int err = 0; + + if (state > cpumask_weight(&(d->cpus))) + return -EINVAL; /* out of allowed range */ + + /* + * Set target state here; hot-unplug CPUs if we are too hot, but + * don't attempt to hot-plug CPUs if we're cold. Starting CPUs + * should be left to CPUOffline governor. + * + * There is a chance that CPU hotplug driver is racing with this + * code. Rather than trying to make the procedure atomic, iterate + * until we reach the desired state, or signal error if the state + * cannot be reached. + * + * Neither CPU hotplug nor this code is expected to run too often. + */ + d->target = state; + + /* compare desired cooling state to current cooling state */ + while ((cstate = num_offline_cpus(d)) < state && !err) { + /* cstate < cstate: we're too hot, unplug any cpu */ + cpu = random_online_cpu(d); + if (cpu < nr_cpu_ids) + err = work_on_cpu(boot_cpu(), + (long(*)(void *))cpu_down, + (void *)cpu); + /* on error, message would come from cpu_down() */ + else { + pr_warn("cpuhotplug: CPUs already down\n"); + err = -EAGAIN; + } + } + + return err; +} + +/* cpu hotplug cooling device ops */ +static struct thermal_cooling_device_ops const cpuhotplug_cooling_ops = { + .get_max_state = cpuhotplug_get_max_state, + .get_cur_state = cpuhotplug_get_cur_state, + .set_cur_state = cpuhotplug_set_cur_state, +}; + +/** + * _cpu_startup_allowed - traverse list of hotplug cooling devices to + * check if startup of cpu violates thermal constraints + */ +static inline int _cpu_startup_allowed(int cpu) +{ + struct cpuhotplug_cooling_device *d; + int ret = 1; + + /* + * Prevent starting CPU if it violates any cooling + * device's constraint. Called from hotplug notifier, so + * cpu_up()/cpu_down() already holds a lock on hotplug + * events. + */ + mutex_lock(&cpuhotplug_cooling_lock); + list_for_each_entry(d, &cpuhotplug_list, list) { + if (cpumask_test_cpu(cpu, &(d->cpus)) && + d->target >= _num_offline_cpus(d)) { + pr_warn("%s: CPU%d startup prevented\n", + dev_name(&(d->cdev->device)), cpu); + ret = 0; + break; + } + } + mutex_unlock(&cpuhotplug_cooling_lock); + return ret; +} + +/** + * cpuhotplug_thermal_notifier - notifier callback for CPU hotplug events. + * @nb: struct notifier_block + * @event: cpu hotplug event for which callback is invoked. + * @data: context data, in this particular case CPU index. + * + * Callback intercepting CPU hotplug events. Compares CPU hotplug action + * with current thermal state and allows/denies accordingly. + * + * Return: 0 (allow) or error (deny). + */ +static int cpuhotplug_thermal_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + int cpu = (int)data; + + switch (event) { + case CPU_UP_PREPARE: + case CPU_UP_PREPARE_FROZEN: + /* _cpu_up() only cares about result from CPU_UP_PREPAREs */ + if (!_cpu_startup_allowed(cpu)) + return notifier_from_errno(-EAGAIN); + break; + default: + /* allow all other actions */ + break; + } + return 0; +} + +static struct notifier_block cpuhotplug_thermal_notifier_block = { + .notifier_call = cpuhotplug_thermal_notifier, +}; + +/** + * cpuhotplug_cooling_register - create cpu hotplug cooling device + * @cpus: cpumask of cpu cores participating in cooling + * @ext: instance-specific name of device + * + * Creates and registers a cpu hotplug cooling device with the name + * "cpu-hotplug-<ext>". + * + * Return: valid pointer to cpuhotplug_cooling_device struct on success, + * corresponding ERR_PTR() on failure. + */ +struct thermal_cooling_device * +cpuhotplug_cooling_register(const struct cpumask *cpus, const char *ext) +{ + struct thermal_cooling_device *cdev; + struct cpuhotplug_cooling_device *cpuhotplug_cdev; + struct cpumask test; + char name[THERMAL_NAME_LENGTH]; + int err; + + /* test if we passed in a good cpumask */ + cpu_maps_update_begin(); + cpumask_and(&test, cpus, cpu_possible_mask); + cpu_maps_update_done(); + + if (cpumask_test_cpu(boot_cpu(), &test)) { + pr_warn("cannot hot-plug boot CPU%d\n", boot_cpu()); + cpumask_clear_cpu(boot_cpu(), &test); + } + if (cpumask_empty(&test)) { + pr_err("CPUs unavailable for hot-plug cooling\n"); + err = -EINVAL; + goto out; + } + + cpuhotplug_cdev = kzalloc(sizeof(struct cpuhotplug_cooling_device), + GFP_KERNEL); + if (!cpuhotplug_cdev) { + err = -ENOMEM; + goto out; + } + + cpumask_copy(&cpuhotplug_cdev->cpus, &test); + + snprintf(name, sizeof(name), "cpu-hotplug-%s", ext); + + cpuhotplug_cdev->target = 0; + cdev = thermal_cooling_device_register(name, cpuhotplug_cdev, + &cpuhotplug_cooling_ops); + if (!cdev) { + pr_err("%s: cooling device registration failed.\n", name); + err = -EINVAL; + goto out_free; + } + cpuhotplug_cdev->cdev = cdev; + + mutex_lock(&cpuhotplug_cooling_lock); + if (list_empty(&cpuhotplug_list)) + register_cpu_notifier(&cpuhotplug_thermal_notifier_block); + list_add(&(cpuhotplug_cdev->list), &cpuhotplug_list); + mutex_unlock(&cpuhotplug_cooling_lock); + + return cdev; + +out_free: + kfree(cpuhotplug_cdev); +out: + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(cpuhotplug_cooling_register); + +/** + * cpuhotplug_cooling_unregister - remove cpu hotplug cooling device + * @cdev: cooling device to remove + * + * Unregisters and frees the cpu hotplug cooling device. + */ +void cpuhotplug_cooling_unregister(struct thermal_cooling_device *cdev) +{ + struct cpuhotplug_cooling_device *cpuhotplug_cdev = cdev->devdata; + + mutex_lock(&cpuhotplug_cooling_lock); + list_del(&(cpuhotplug_cdev->list)); + if (list_empty(&cpuhotplug_list)) + unregister_cpu_notifier(&cpuhotplug_thermal_notifier_block); + mutex_unlock(&cpuhotplug_cooling_lock); + + thermal_cooling_device_unregister(cpuhotplug_cdev->cdev); + kfree(cpuhotplug_cdev); +} +EXPORT_SYMBOL_GPL(cpuhotplug_cooling_unregister); + diff --git a/include/linux/cpuhp_cooling.h b/include/linux/cpuhp_cooling.h new file mode 100644 index 0000000..ace1d5b --- /dev/null +++ b/include/linux/cpuhp_cooling.h @@ -0,0 +1,57 @@ +/* + * linux/include/linux/cpuhp_cooling.h + * + * Copyright (C) 2013 Broadcom Corporation Ltd. + * Copyright (C) 2013 Zoran Markovic <zoran.markovic@xxxxxxxxxx> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +#ifndef __CPUHP_COOLING_H__ +#define __CPUHP_COOLING_H__ + +#include <linux/thermal.h> +#include <linux/cpumask.h> + +#ifdef CONFIG_CPU_THERMAL_HOTPLUG +/** + * cpuhotplug_cooling_register - create cpu hotplug cooling device. + * @cpus: cpumask of hot-pluggable cpus + * @ext: instance-specific device name + */ +struct thermal_cooling_device * +cpuhotplug_cooling_register(const struct cpumask *cpus, const char *ext); + +/** + * cpuhotplug_cooling_unregister - remove cpu hoptlug cooling device. + * @cdev: thermal cooling device pointer. + */ +void cpuhotplug_cooling_unregister(struct thermal_cooling_device *cdev); +#else /* !CONFIG_CPU_THERMAL_HOTPLUG */ +static inline struct thermal_cooling_device * +cpuhotplug_cooling_register(const struct cpumask *cpus, const char *ext) +{ + return NULL; +} +static inline +void cpuhotplug_cooling_unregister(struct thermal_cooling_device *cdev) +{ + return; +} +#endif /* CONFIG_CPU_THERMAL_HOTPLUG */ + +#endif /* __CPU_COOLING_H__ */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-doc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html