Subject: [PATCH] [acpi thermal] Add policy handling - Add ->t_state to struct acpi_thermal - Add drivers/acpi/drivers/thermal/policy.c - Implement thermal_check() and put it there Should be functionally identical to legacy driver, but broken into several functions for understandability: - check_trips() - invoke_policy() - thermal_policy_critical() - thermal_policy_hot() - thermal_policy_passive() - passive_above() and passive_below() - thermal_policy_active() - active_above() and active_below() - calc_new_state() - schedule_next() Signed-off-by: Patrick Mochel --- drivers/acpi/drivers/thermal/Makefile | 2 drivers/acpi/drivers/thermal/policy.c | 363 ++++++++++++++++++++++++++++++++ drivers/acpi/drivers/thermal/thermal.h | 2 3 files changed, 366 insertions(+), 1 deletions(-) create mode 100644 drivers/acpi/drivers/thermal/policy.c applies-to: db08ec0aa45fe02418273325fe67981bd1413526 303a9060d3c6db11ff195dec8a8204ab2fa11677 diff --git a/drivers/acpi/drivers/thermal/Makefile b/drivers/acpi/drivers/thermal/Makefile index c601270..5362aa7 100644 --- a/drivers/acpi/drivers/thermal/Makefile +++ b/drivers/acpi/drivers/thermal/Makefile @@ -1,6 +1,6 @@ obj-$(CONFIG_ACPI_THERMAL) += thermal.o -thermal-y := driver.o device.o event.o +thermal-y := driver.o device.o policy.o event.o thermal-$(CONFIG_ACPI_DM_PROC) += proc.o thermal-$(CONFIG_ACPI_DM_SYSFS) += sysfs.o diff --git a/drivers/acpi/drivers/thermal/policy.c b/drivers/acpi/drivers/thermal/policy.c new file mode 100644 index 0000000..098726c --- /dev/null +++ b/drivers/acpi/drivers/thermal/policy.c @@ -0,0 +1,363 @@ +/*** + * drivers/acpi/drivers/thermal/policy.c - Thermal policy control + * + * Copyright (C) 2006 Patrick Mochel + * + * Based on drivers/acpi/thermal.c, which was + * + * Copyright (C) 2001, 2002 Andy Grover + * Copyright (C) 2001, 2002 Paul Diefenbaugh + * + * This file is released under the GPLv2. + */ + +#include "thermal.h" + + +#define ACPI_THERMAL_NOTIFY_CRITICAL 0xF0 +#define ACPI_THERMAL_NOTIFY_HOT 0xF1 + + +static void check_trips(struct acpi_thermal * at, u32 * s) +{ + u32 state = at->t_state; + int i; + + at->t_state = 0; + + /* + * Check Trip Points + * ----------------- + * Compare the current temperature to the trip point values to see + * if we've entered one of the thermal policy states. Note that + * this function determines when a state is entered, but the + * individual policy decides when it is exited (e.g. hysteresis). + */ + if (at->t_trips.crit_val) { + if (at->t_temp >= at->t_trips.crit_temp) + state |= THERMAL_STATE_CRIT; + } + if (at->t_trips.hot_val) { + if (at->t_temp >= at->t_trips.hot_temp) + state |= THERMAL_STATE_HOT; + } + if (at->t_trips.psv_val) { + if (at->t_temp >= at->t_trips.psv_temp) + state |= THERMAL_STATE_PSV; + } + + for (i = 0; i < THERMAL_MAX_ACTIVE; i++) { + struct thermal_active * ta = at->t_trips.act + i; + if (ta->val) { + if (at->t_temp >= ta->temp) + state |= THERMAL_STATE_ACT; + } + } + *s = state; +} + +static void thermal_call_usermode(struct acpi_thermal * at) +{ + char * envp[] = { + [0] = "HOME=/", + [1] = "PATH=/sbin:", + [2] = NULL, + }; + char * argv[] = { + [0] = "/sbin/poweroff", /* Ok to hard code this? */ + [1] = "-h", /* disks in standby mode */ + [2] = "-f", /* force halt */ + [3] = NULL, + }; + + call_usermodehelper(argv[0], argv, envp, 0); +} + + +static int thermal_policy_critical(struct acpi_thermal * at) +{ + at->t_trips.crit_en = 1; + + printk(KERN_EMERG + "Critical temperature reached (%ld C), shutting down!\n", + thermal_temp_c(at->t_temp)); + + acpi_bus_generate_event(at->t_ad->acpi_device, + ACPI_THERMAL_NOTIFY_CRITICAL, 1); + + thermal_call_usermode(at); + + /* + * Are we still here? + * + * We should probably sleep for a bit, then call pm_power_off() + */ + pm_power_off(); + + return 0; +} + +static int thermal_policy_hot(struct acpi_thermal * at) +{ + at->t_trips.hot_en = 1; + dbg("Hot trip point reached"); + + acpi_bus_generate_event(at->t_ad->acpi_device, + ACPI_THERMAL_NOTIFY_HOT, + 1); + + /* + * FIXME: Should we call suspend-to-disk here? + */ + return 0; +} + + +static void passive_above(struct acpi_thermal * at) +{ + int trend; + int i; + + /* + * Above Trip? + * ----------- + * Calculate the thermal trend (using the passive cooling equation) + * and modify the performance limit for all passive cooling devices + * accordingly. Note that we assume symmetry. + */ + + trend = (at->t_trips.psv_tc1 * (at->t_temp - at->t_last_temp)); + trend += (at->t_trips.psv_tc2 * (at->t_temp - at->t_trips.psv_temp)); + + dbg("trend[%d] = (tc1[%lu] * (tmp[%lu] - last[%lu])) +" + "(tc2[%lu] * (tmp[%lu] - psv[%lu]))\n", + trend, at->t_trips.psv_tc1, at->t_temp, + at->t_last_temp, at->t_trips.psv_tc2, + at->t_temp, at->t_trips.psv_temp); + + at->t_trips.psv_en = 1; + + if (trend > 0) { + /* + * Heating Up + */ + for (i = 0; i < at->t_trips.psv_devices.count; i++) { + acpi_handle h = at->t_trips.psv_devices.handles[i]; + acpi_processor_set_thermal_limit(h, + ACPI_PROCESSOR_LIMIT_INCREMENT); + } + } else if (trend < 0) { + /* + * Cooling Down + */ + for (i = 0; i < at->t_trips.psv_devices.count; i++) { + acpi_handle h = at->t_trips.psv_devices.handles[i]; + acpi_processor_set_thermal_limit(h, + ACPI_PROCESSOR_LIMIT_DECREMENT); + } + + } +} + +static void passive_below(struct acpi_thermal * at) +{ + int i; + int ret; + + /* + * Below Trip? + * ----------- + * Implement passive cooling hysteresis to slowly increase performance + * and avoid thrashing around the passive trip point. Note that we + * assume symmetry. + */ + + for (i = 0; i < at->t_trips.psv_devices.count; i++) { + acpi_handle h = at->t_trips.psv_devices.handles[i]; + ret = acpi_processor_set_thermal_limit(h, + ACPI_PROCESSOR_LIMIT_DECREMENT); + if (ret) { + at->t_trips.psv_en = 0; + dbg("Disabling passive cooling (zone is cool)"); + } + } + +} + +static int thermal_policy_passive(struct acpi_thermal * at) +{ + if (at->t_temp >= at->t_trips.psv_temp) + passive_above(at); + else + passive_below(at); + return 0; +} + + +static void active_above(struct thermal_active * ta) +{ + int i; + int ret; + + /* + * Above Threshold? + * ---------------- + * If not already enabled, turn ON all cooling devices + * associated with this active threshold. + */ + + if (!ta->en) { + for (i = 0; i < ta->devices.count; i++) { + acpi_handle h = ta->devices.handles[i]; + ret = acpi_bus_set_power(h, ACPI_STATE_D0); + if (ret) { + dbg("Unable to turn cooling device[%p] on", h); + continue; + } + ta->en = 1; + dbg("Cooling device [%p] now on", h); + } + } + +} + +static void active_below(struct thermal_active * ta) +{ + int i; + int ret; + + /* + * Below Threshold? + * ---------------- + * Turn OFF all cooling devices associated with this + * threshold. + */ + for (i = 0; i < ta->devices.count; i++) { + acpi_handle h = ta->devices.handles[i]; + ret = acpi_bus_set_power(h, ACPI_STATE_D3); + if (ret) { + dbg("Unable to turn off cooling device [%p]", h); + continue; + } + ta->en = 0; + dbg("Cooling device [%p] now off", h); + } +} + + +static int thermal_policy_active(struct acpi_thermal * at) +{ + struct thermal_active * ta; + unsigned long maxtemp = 0; + int i; + + for (i = 0; i < THERMAL_MAX_ACTIVE; i++) { + ta = at->t_trips.act + i; + + if (at->t_temp >= ta->temp) { + if (ta->temp > maxtemp) { + at->t_trips.act_index = i; + maxtemp = ta->temp; + } + active_above(ta); + } else + active_below(ta); + } + return 0; +} + +static void invoke_policy(struct acpi_thermal * at, u32 state) +{ + /* + * Invoke Policy + * ------------- + * Separated from the above check to allow individual policy to + * determine when to exit a given state. + */ + if (state & THERMAL_STATE_CRIT) + thermal_policy_critical(at); + if (state & THERMAL_STATE_HOT) + thermal_policy_hot(at); + if (state & THERMAL_STATE_PSV) + thermal_policy_passive(at); + if (state & THERMAL_STATE_ACT) + thermal_policy_active(at); +} + +static void calc_new_state(struct acpi_thermal * at) +{ + int i; + + /* + * Calculate State + * --------------- + * Again, separated from the above two to allow independent policy + * decisions. + */ + if (at->t_trips.crit_en) + at->t_state |= THERMAL_STATE_CRIT; + if (at->t_trips.hot_en) + at->t_state |= THERMAL_STATE_HOT; + if (at->t_trips.psv_en) + at->t_state |= THERMAL_STATE_PSV; + + for (i = 0; i < THERMAL_MAX_ACTIVE; i++) { + struct thermal_active * ta = at->t_trips.act + i; + if (ta->en) { + at->t_state |= THERMAL_STATE_ACT; + break; + } + } +} + +static void calc_interval(struct acpi_thermal * at, unsigned long * t) +{ + /* + * Calculate Sleep Time + * -------------------- + * If we're in the passive state, use _TSP's value. Otherwise + * use the default polling frequency (e.g. _TZP). If no polling + * frequency is specified then we'll wait forever (at least until + * a thermal event occurs). Note that _TSP and _TZD values are + * given in 1/10th seconds (we must covert to milliseconds). + */ + + if (at->t_state & THERMAL_STATE_PSV) + *t = at->t_trips.psv_tsp * 100; + else if (at->t_poll_freq > 0) + *t = at->t_poll_freq * 100; + else + *t = 0; +} + +static void schedule_next(struct acpi_thermal * at) +{ + unsigned long interval; + + calc_interval(at, &interval); + + dbg("%s%d: sleep [%lu]", at->t_name, at->t_num, interval); + + if (!interval) { + if (timer_pending(&at->t_timer)) + del_timer(&at->t_timer); + } else { + mod_timer(&at->t_timer, (HZ * interval) / 1000); + } +} + +void thermal_check(struct acpi_thermal * at) +{ + u32 state; + + if (thermal_get_temp(at)) + return; + + check_trips(at, &state); + invoke_policy(at, state); + calc_new_state(at); + + dbg("%s%d: temperature [%lu]", at->t_name, at->t_num, at->t_temp); + + schedule_next(at); +} diff --git a/drivers/acpi/drivers/thermal/thermal.h b/drivers/acpi/drivers/thermal/thermal.h index d4bddd3..25f18e1 100644 --- a/drivers/acpi/drivers/thermal/thermal.h +++ b/drivers/acpi/drivers/thermal/thermal.h @@ -57,6 +57,8 @@ struct thermal_trips { struct acpi_thermal { struct acpi_dev * t_ad; + + u32 t_state; long t_temp; long t_last_temp; --- 0.99.9.GIT