summaryrefslogtreecommitdiffstats
path: root/drivers/acpi/thermal.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2026-08-30 09:22:00 -0700
commit034dd340b08be1f2f0477ad16131d609f9dbd53c (patch)
tree2536f4b2d7893ccd916b5abdf3c454ad6edc03c2 /drivers/acpi/thermal.c
downloadlinux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.tar.gz
linux-stable-034dd340b08be1f2f0477ad16131d609f9dbd53c.zip
Merge tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-tracegrafted
Pull tracing fixes from Steven Rostedt: - Fix error output of boot instance creation failure Currently if a boot instance creation fails, instead of printing out the name of the instance that failed, it prints "(null)". That is because it prints "cur_str" that had already been processed by strsep(). Print the saved name instead. While at it, print the error code of the failure. - Fix use-after-free for same named historgrams Histograms can be named so that they can be used in multiple events. But if the named histogram has a variable attached, the second event that uses the named histogram which duplicates it and needs to free the original after duplication leaves the old variable in place and still visible. If another histogram uses than variable, it will use the stale one which will try to reference the freed duplicate histogram and crash the kernel. Free the duplicate variables along with the duplicated histogram data. - Check return value of kthread_run() in event self test The events self tests uses a kthread for testing but does not check if it succeeded in creating a kthread. If the kthread creation were to fail, the code will still try to call kthread_stop() on the error returned. - Fix race between reading trace_pipe and updating subbuffer size If a user is reading the trace_pipe file at the same time they update the ring buffer sub-buffer size, can cause the trace_pipe read to read stale data. Add trace_access_lock() around updating the ring buffer sub-buffer size. - Fix eventfs_inode on failure path in creation of the events directory In the creation of the "events" directory, if after allocating the eventfs_inode a failure is detected, it calls cleanup_ei() which calls free_ei(). The free_ei() will test if eventfs_inode being freed has no children. It is a bug if it does. But on the failure case of the creation of the "events" directory, the children lists have not yet been initialized and the free will trigger a warning because list_empty() on an uninitialized list returns false. Move the initialization into init_ei() where it makes more sense and makes sure that a created eventfs_inode has its lists initialized upon creation. - Check return value of kthread_run() in ftrace direct sample code The sample code that shows how to use the ftrace direct calls does not test the return of kthread_run() to see if it succeeds. Return a failure if the kthread_run() doesn't succeed. - Clear user events state on fork in case of alloc failure On fork, the child gets a pointer to the parent's user events state. It makes a copy of it then updates the child's pointer to it. But if the allocation fails, the duplication function leaves the child with a pointer to its parent's descriptor. When the child cleans up its data, it will free the parent's descriptor while the parent is still using it. In the duplication function, set the child's user_event_mm to NULL before testing if the allocation succeeded, and when it exits it will not free the parent's descriptor. - Fix retry exhaustion in simple ring buffer reader swap simple_ring_buffer_swap_reader_page() starts with retry set to 8 and post-decrements it only after a failed link replacement. On the final attempt, a successful replacement leaves retry at zero, while a failed replacement leaves it at -1. But the check for success expects the retry value to be non-zero and exits with an error on zero. This is the opposite result. Fix it. - Fail nicely when the remote swap_reader_page() returns an error Currently, if the swap_reader_page() of a remote buffer fails, it triggers a WARN_ON_ONCE() and continues normally. Instead, have it exit with an error and a pr_warn() print instead of a full WARNING. * tag 'trace-v7.3-2' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: ring-buffer: Stop remote reader update when page swap fails tracing: Fix retry exhaustion in simple ring buffer reader swap tracing/user_events: Clear copied tracing state before fork duplication samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-multi-modify samples/ftrace: Fix kthread_stop() on ERR_PTR in ftrace-direct-modify eventfs: Initialize ei->children and ei->list in init_ei() tracing: Fix use-after-free in trace_pipe read on sub-buffer order change tracing: Fix crash passing ERR_PTR to kthread_stop() tracing: Fix use-after-free with same-name named triggers tracing: Fix logged instance name on creation failure
Diffstat (limited to 'drivers/acpi/thermal.c')
-rw-r--r--drivers/acpi/thermal.c1053
1 files changed, 1053 insertions, 0 deletions
diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c
new file mode 100644
index 000000000..dd7666c17
--- /dev/null
+++ b/drivers/acpi/thermal.c
@@ -0,0 +1,1053 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * acpi_thermal.c - ACPI Thermal Zone Driver ($Revision: 41 $)
+ *
+ * Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+ * Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.com>
+ *
+ * This driver fully implements the ACPI thermal policy as described in the
+ * ACPI 2.0 Specification.
+ *
+ * TBD: 1. Implement passive cooling hysteresis.
+ * 2. Enhance passive cooling (CPU) states/limit interface to support
+ * concepts of 'multiple limiters', upper/lower limits, etc.
+ */
+
+#define pr_fmt(fmt) "ACPI: thermal: " fmt
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/jiffies.h>
+#include <linux/kmod.h>
+#include <linux/reboot.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/thermal.h>
+#include <linux/acpi.h>
+#include <linux/workqueue.h>
+#include <linux/uaccess.h>
+#include <linux/units.h>
+
+#include "internal.h"
+
+#define ACPI_THERMAL_CLASS "thermal_zone"
+#define ACPI_THERMAL_NOTIFY_TEMPERATURE 0x80
+#define ACPI_THERMAL_NOTIFY_THRESHOLDS 0x81
+#define ACPI_THERMAL_NOTIFY_DEVICES 0x82
+#define ACPI_THERMAL_NOTIFY_CRITICAL 0xF0
+#define ACPI_THERMAL_NOTIFY_HOT 0xF1
+#define ACPI_THERMAL_MODE_ACTIVE 0x00
+
+#define ACPI_THERMAL_MAX_ACTIVE 10
+#define ACPI_THERMAL_MAX_LIMIT_STR_LEN 65
+
+#define ACPI_THERMAL_TRIP_PASSIVE (-1)
+
+#define ACPI_THERMAL_MAX_NR_TRIPS (ACPI_THERMAL_MAX_ACTIVE + 3)
+
+/*
+ * This exception is thrown out in two cases:
+ * 1.An invalid trip point becomes invalid or a valid trip point becomes invalid
+ * when re-evaluating the AML code.
+ * 2.TODO: Devices listed in _PSL, _ALx, _TZD may change.
+ * We need to re-bind the cooling devices of a thermal zone when this occurs.
+ */
+#define ACPI_THERMAL_TRIPS_EXCEPTION(tz, str) \
+do { \
+ acpi_handle_info(tz->device->handle, \
+ "ACPI thermal trip point %s changed\n" \
+ "Please report to linux-acpi@vger.kernel.org\n", str); \
+} while (0)
+
+static int act;
+module_param(act, int, 0644);
+MODULE_PARM_DESC(act, "Disable or override all lowest active trip points.");
+
+static int crt;
+module_param(crt, int, 0644);
+MODULE_PARM_DESC(crt, "Disable or lower all critical trip points.");
+
+static int tzp;
+module_param(tzp, int, 0444);
+MODULE_PARM_DESC(tzp, "Thermal zone polling frequency, in 1/10 seconds.");
+
+static int off;
+module_param(off, int, 0);
+MODULE_PARM_DESC(off, "Set to disable ACPI thermal support.");
+
+static int psv;
+module_param(psv, int, 0644);
+MODULE_PARM_DESC(psv, "Disable or override all passive trip points.");
+
+static struct workqueue_struct *acpi_thermal_pm_queue;
+
+struct acpi_thermal_trip {
+ unsigned long temp_dk;
+ struct acpi_handle_list devices;
+};
+
+struct acpi_thermal_passive {
+ struct acpi_thermal_trip trip;
+ unsigned long tc1;
+ unsigned long tc2;
+ unsigned long delay;
+};
+
+struct acpi_thermal_active {
+ struct acpi_thermal_trip trip;
+};
+
+struct acpi_thermal_trips {
+ struct acpi_thermal_passive passive;
+ struct acpi_thermal_active active[ACPI_THERMAL_MAX_ACTIVE];
+};
+
+struct acpi_thermal {
+ struct acpi_device *device;
+ acpi_bus_id name;
+ unsigned long temp_dk;
+ unsigned long last_temp_dk;
+ unsigned long polling_frequency;
+ volatile u8 zombie;
+ struct acpi_thermal_trips trips;
+ struct thermal_zone_device *thermal_zone;
+ int kelvin_offset; /* in millidegrees */
+ struct work_struct thermal_check_work;
+ struct mutex thermal_check_lock;
+ refcount_t thermal_check_count;
+};
+
+/* --------------------------------------------------------------------------
+ Thermal Zone Management
+ -------------------------------------------------------------------------- */
+
+static int acpi_thermal_get_temperature(struct acpi_thermal *tz)
+{
+ acpi_status status = AE_OK;
+ unsigned long long tmp;
+
+ if (!tz)
+ return -EINVAL;
+
+ tz->last_temp_dk = tz->temp_dk;
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TMP", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ tz->temp_dk = tmp;
+
+ acpi_handle_debug(tz->device->handle, "Temperature is %lu dK\n",
+ tz->temp_dk);
+
+ return 0;
+}
+
+static int acpi_thermal_get_polling_frequency(struct acpi_thermal *tz)
+{
+ acpi_status status = AE_OK;
+ unsigned long long tmp;
+
+ if (!tz)
+ return -EINVAL;
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TZP", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return -ENODEV;
+
+ tz->polling_frequency = tmp;
+ acpi_handle_debug(tz->device->handle, "Polling frequency is %lu dS\n",
+ tz->polling_frequency);
+
+ return 0;
+}
+
+static int acpi_thermal_temp(struct acpi_thermal *tz, int temp_deci_k)
+{
+ int temp;
+
+ if (temp_deci_k == THERMAL_TEMP_INVALID)
+ return THERMAL_TEMP_INVALID;
+
+ temp = deci_kelvin_to_millicelsius_with_offset(temp_deci_k,
+ tz->kelvin_offset);
+ if (temp <= 0)
+ return THERMAL_TEMP_INVALID;
+
+ return temp;
+}
+
+static bool acpi_thermal_trip_valid(struct acpi_thermal_trip *acpi_trip)
+{
+ return acpi_trip->temp_dk != THERMAL_TEMP_INVALID;
+}
+
+static int active_trip_index(struct acpi_thermal *tz,
+ struct acpi_thermal_trip *acpi_trip)
+{
+ struct acpi_thermal_active *active;
+
+ active = container_of(acpi_trip, struct acpi_thermal_active, trip);
+ return active - tz->trips.active;
+}
+
+static long get_passive_temp(struct acpi_thermal *tz)
+{
+ int temp;
+
+ if (acpi_passive_trip_temp(tz->device, &temp))
+ return THERMAL_TEMP_INVALID;
+
+ return temp;
+}
+
+static long get_active_temp(struct acpi_thermal *tz, int index)
+{
+ int temp;
+
+ if (acpi_active_trip_temp(tz->device, index, &temp))
+ return THERMAL_TEMP_INVALID;
+
+ /*
+ * If an override has been provided, apply it so there are no active
+ * trips with thresholds greater than the override.
+ */
+ if (act > 0) {
+ unsigned long long override = celsius_to_deci_kelvin(act);
+
+ if (temp > override)
+ return override;
+ }
+ return temp;
+}
+
+static void acpi_thermal_update_trip(struct acpi_thermal *tz,
+ const struct thermal_trip *trip)
+{
+ struct acpi_thermal_trip *acpi_trip = trip->priv;
+
+ if (trip->type == THERMAL_TRIP_PASSIVE) {
+ if (psv > 0)
+ return;
+
+ acpi_trip->temp_dk = get_passive_temp(tz);
+ } else {
+ int index = active_trip_index(tz, acpi_trip);
+
+ acpi_trip->temp_dk = get_active_temp(tz, index);
+ }
+
+ if (!acpi_thermal_trip_valid(acpi_trip))
+ ACPI_THERMAL_TRIPS_EXCEPTION(tz, "state");
+}
+
+static bool update_trip_devices(struct acpi_thermal *tz,
+ struct acpi_thermal_trip *acpi_trip,
+ int index, bool compare)
+{
+ struct acpi_handle_list devices = { 0 };
+ char method[] = "_PSL";
+
+ if (index != ACPI_THERMAL_TRIP_PASSIVE) {
+ method[1] = 'A';
+ method[2] = 'L';
+ method[3] = '0' + index;
+ }
+
+ if (!acpi_evaluate_reference(tz->device->handle, method, NULL, &devices)) {
+ acpi_handle_info(tz->device->handle, "%s evaluation failure\n", method);
+ return false;
+ }
+
+ if (acpi_handle_list_equal(&acpi_trip->devices, &devices)) {
+ acpi_handle_list_free(&devices);
+ return true;
+ }
+
+ if (compare)
+ ACPI_THERMAL_TRIPS_EXCEPTION(tz, "device");
+
+ acpi_handle_list_replace(&acpi_trip->devices, &devices);
+ return true;
+}
+
+static void acpi_thermal_update_trip_devices(struct acpi_thermal *tz,
+ const struct thermal_trip *trip)
+{
+ struct acpi_thermal_trip *acpi_trip = trip->priv;
+ int index = trip->type == THERMAL_TRIP_PASSIVE ?
+ ACPI_THERMAL_TRIP_PASSIVE : active_trip_index(tz, acpi_trip);
+
+ if (update_trip_devices(tz, acpi_trip, index, true))
+ return;
+
+ acpi_trip->temp_dk = THERMAL_TEMP_INVALID;
+ ACPI_THERMAL_TRIPS_EXCEPTION(tz, "state");
+}
+
+struct adjust_trip_data {
+ struct acpi_thermal *tz;
+ u32 event;
+};
+
+static int acpi_thermal_adjust_trip(struct thermal_trip *trip, void *data)
+{
+ struct acpi_thermal_trip *acpi_trip = trip->priv;
+ struct adjust_trip_data *atd = data;
+ struct acpi_thermal *tz = atd->tz;
+ int temp;
+
+ if (!acpi_trip || !acpi_thermal_trip_valid(acpi_trip))
+ return 0;
+
+ if (atd->event == ACPI_THERMAL_NOTIFY_THRESHOLDS)
+ acpi_thermal_update_trip(tz, trip);
+ else
+ acpi_thermal_update_trip_devices(tz, trip);
+
+ if (acpi_thermal_trip_valid(acpi_trip))
+ temp = acpi_thermal_temp(tz, acpi_trip->temp_dk);
+ else
+ temp = THERMAL_TEMP_INVALID;
+
+ thermal_zone_set_trip_temp(tz->thermal_zone, trip, temp);
+
+ return 0;
+}
+
+static void acpi_queue_thermal_check(struct acpi_thermal *tz)
+{
+ if (!work_pending(&tz->thermal_check_work))
+ queue_work(acpi_thermal_pm_queue, &tz->thermal_check_work);
+}
+
+static void acpi_thermal_trips_update(struct acpi_thermal *tz, u32 event)
+{
+ struct adjust_trip_data atd = { .tz = tz, .event = event };
+ struct acpi_device *adev = tz->device;
+
+ /*
+ * Use thermal_zone_for_each_trip() to carry out the trip points
+ * update, so as to protect thermal_get_trend() from getting stale
+ * trip point temperatures and to prevent thermal_zone_device_update()
+ * invoked from acpi_thermal_check_fn() from producing inconsistent
+ * results.
+ */
+ thermal_zone_for_each_trip(tz->thermal_zone,
+ acpi_thermal_adjust_trip, &atd);
+ acpi_queue_thermal_check(tz);
+ acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
+ dev_name(&adev->dev), event, 0);
+}
+
+static int acpi_thermal_get_critical_trip(struct acpi_thermal *tz)
+{
+ int temp;
+
+ if (crt > 0) {
+ temp = celsius_to_deci_kelvin(crt);
+ goto set;
+ }
+ if (crt == -1) {
+ acpi_handle_debug(tz->device->handle, "Critical threshold disabled\n");
+ return THERMAL_TEMP_INVALID;
+ }
+
+ if (acpi_critical_trip_temp(tz->device, &temp))
+ return THERMAL_TEMP_INVALID;
+
+ if (temp <= 2732) {
+ /*
+ * Below zero (Celsius) values clearly aren't right for sure,
+ * so discard them as invalid.
+ */
+ pr_info(FW_BUG "Invalid critical threshold (%d)\n", temp);
+ return THERMAL_TEMP_INVALID;
+ }
+
+set:
+ acpi_handle_debug(tz->device->handle, "Critical threshold [%d]\n", temp);
+ return temp;
+}
+
+static int acpi_thermal_get_hot_trip(struct acpi_thermal *tz)
+{
+ int temp;
+
+ if (acpi_hot_trip_temp(tz->device, &temp) || temp == THERMAL_TEMP_INVALID) {
+ acpi_handle_debug(tz->device->handle, "No hot threshold\n");
+ return THERMAL_TEMP_INVALID;
+ }
+
+ acpi_handle_debug(tz->device->handle, "Hot threshold [%d]\n", temp);
+ return temp;
+}
+
+static bool passive_trip_params_init(struct acpi_thermal *tz)
+{
+ unsigned long long tmp;
+ acpi_status status;
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TC1", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return false;
+
+ tz->trips.passive.tc1 = tmp;
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TC2", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return false;
+
+ tz->trips.passive.tc2 = tmp;
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TFP", NULL, &tmp);
+ if (ACPI_SUCCESS(status)) {
+ tz->trips.passive.delay = tmp;
+ return true;
+ }
+
+ status = acpi_evaluate_integer(tz->device->handle, "_TSP", NULL, &tmp);
+ if (ACPI_FAILURE(status))
+ return false;
+
+ tz->trips.passive.delay = tmp * 100;
+
+ return true;
+}
+
+static bool acpi_thermal_init_trip(struct acpi_thermal *tz, int index)
+{
+ struct acpi_thermal_trip *acpi_trip;
+ long temp;
+
+ if (index == ACPI_THERMAL_TRIP_PASSIVE) {
+ acpi_trip = &tz->trips.passive.trip;
+
+ if (psv == -1)
+ goto fail;
+
+ if (!passive_trip_params_init(tz))
+ goto fail;
+
+ temp = psv > 0 ? celsius_to_deci_kelvin(psv) :
+ get_passive_temp(tz);
+ } else {
+ acpi_trip = &tz->trips.active[index].trip;
+
+ if (act == -1)
+ goto fail;
+
+ temp = get_active_temp(tz, index);
+ }
+
+ if (temp == THERMAL_TEMP_INVALID)
+ goto fail;
+
+ if (!update_trip_devices(tz, acpi_trip, index, false))
+ goto fail;
+
+ acpi_trip->temp_dk = temp;
+ return true;
+
+fail:
+ acpi_trip->temp_dk = THERMAL_TEMP_INVALID;
+ return false;
+}
+
+static void acpi_thermal_get_trip_points(struct acpi_thermal *tz)
+{
+ int i;
+
+ acpi_thermal_init_trip(tz, ACPI_THERMAL_TRIP_PASSIVE);
+
+ for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
+ if (!acpi_thermal_init_trip(tz, i))
+ break;
+ }
+
+ while (++i < ACPI_THERMAL_MAX_ACTIVE)
+ tz->trips.active[i].trip.temp_dk = THERMAL_TEMP_INVALID;
+}
+
+/* sys I/F for generic thermal sysfs support */
+
+static int thermal_get_temp(struct thermal_zone_device *thermal, int *temp)
+{
+ struct acpi_thermal *tz = thermal_zone_device_priv(thermal);
+ int result;
+
+ if (!tz)
+ return -EINVAL;
+
+ result = acpi_thermal_get_temperature(tz);
+ if (result)
+ return result;
+
+ *temp = deci_kelvin_to_millicelsius_with_offset(tz->temp_dk,
+ tz->kelvin_offset);
+ return 0;
+}
+
+static int thermal_get_trend(struct thermal_zone_device *thermal,
+ const struct thermal_trip *trip,
+ enum thermal_trend *trend)
+{
+ struct acpi_thermal *tz = thermal_zone_device_priv(thermal);
+ struct acpi_thermal_trip *acpi_trip;
+ int t;
+
+ if (!tz || !trip)
+ return -EINVAL;
+
+ acpi_trip = trip->priv;
+ if (!acpi_trip || !acpi_thermal_trip_valid(acpi_trip))
+ return -EINVAL;
+
+ switch (trip->type) {
+ case THERMAL_TRIP_PASSIVE:
+ t = tz->trips.passive.tc1 * (tz->temp_dk -
+ tz->last_temp_dk) +
+ tz->trips.passive.tc2 * (tz->temp_dk -
+ acpi_trip->temp_dk);
+ if (t > 0)
+ *trend = THERMAL_TREND_RAISING;
+ else if (t < 0)
+ *trend = THERMAL_TREND_DROPPING;
+ else
+ *trend = THERMAL_TREND_STABLE;
+
+ return 0;
+
+ case THERMAL_TRIP_ACTIVE:
+ t = acpi_thermal_temp(tz, tz->temp_dk);
+ if (t <= trip->temperature)
+ break;
+
+ *trend = THERMAL_TREND_RAISING;
+
+ return 0;
+
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void acpi_thermal_zone_device_hot(struct thermal_zone_device *thermal)
+{
+ struct acpi_thermal *tz = thermal_zone_device_priv(thermal);
+
+ acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
+ dev_name(&tz->device->dev),
+ ACPI_THERMAL_NOTIFY_HOT, 1);
+}
+
+static void acpi_thermal_zone_device_critical(struct thermal_zone_device *thermal)
+{
+ struct acpi_thermal *tz = thermal_zone_device_priv(thermal);
+
+ acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS,
+ dev_name(&tz->device->dev),
+ ACPI_THERMAL_NOTIFY_CRITICAL, 1);
+
+ thermal_zone_device_critical(thermal);
+}
+
+static bool acpi_thermal_should_bind_cdev(struct thermal_zone_device *thermal,
+ const struct thermal_trip *trip,
+ struct thermal_cooling_device *cdev,
+ struct cooling_spec *c)
+{
+ struct acpi_thermal_trip *acpi_trip = trip->priv;
+ struct acpi_device *cdev_adev = cdev->devdata;
+ int i;
+
+ /* Skip critical and hot trips. */
+ if (!acpi_trip)
+ return false;
+
+ for (i = 0; i < acpi_trip->devices.count; i++) {
+ acpi_handle handle = acpi_trip->devices.handles[i];
+
+ if (acpi_fetch_acpi_dev(handle) == cdev_adev)
+ return true;
+ }
+
+ return false;
+}
+
+static const struct thermal_zone_device_ops acpi_thermal_zone_ops = {
+ .should_bind = acpi_thermal_should_bind_cdev,
+ .get_temp = thermal_get_temp,
+ .get_trend = thermal_get_trend,
+ .hot = acpi_thermal_zone_device_hot,
+ .critical = acpi_thermal_zone_device_critical,
+};
+
+static int acpi_thermal_zone_sysfs_add(struct acpi_thermal *tz)
+{
+ struct device *tzdev = thermal_zone_device(tz->thermal_zone);
+ int ret;
+
+ ret = sysfs_create_link(&tz->device->dev.kobj,
+ &tzdev->kobj, "thermal_zone");
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_link(&tzdev->kobj,
+ &tz->device->dev.kobj, "device");
+ if (ret)
+ sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
+
+ return ret;
+}
+
+static void acpi_thermal_zone_sysfs_remove(struct acpi_thermal *tz)
+{
+ struct device *tzdev = thermal_zone_device(tz->thermal_zone);
+
+ sysfs_remove_link(&tz->device->dev.kobj, "thermal_zone");
+ sysfs_remove_link(&tzdev->kobj, "device");
+}
+
+static int acpi_thermal_register_thermal_zone(struct acpi_thermal *tz,
+ const struct thermal_trip *trip_table,
+ unsigned int trip_count,
+ int passive_delay)
+{
+ int result;
+
+ if (trip_count)
+ tz->thermal_zone = thermal_zone_device_register_with_trips(
+ "acpitz", trip_table, trip_count, tz,
+ &acpi_thermal_zone_ops, NULL, passive_delay,
+ tz->polling_frequency * 100);
+ else
+ tz->thermal_zone = thermal_tripless_zone_device_register(
+ "acpitz", tz, &acpi_thermal_zone_ops, NULL);
+
+ if (IS_ERR(tz->thermal_zone))
+ return PTR_ERR(tz->thermal_zone);
+
+ result = acpi_thermal_zone_sysfs_add(tz);
+ if (result)
+ goto unregister_tzd;
+
+ result = thermal_zone_device_enable(tz->thermal_zone);
+ if (result)
+ goto remove_links;
+
+ dev_info(&tz->device->dev, "registered as thermal_zone%d\n",
+ thermal_zone_device_id(tz->thermal_zone));
+
+ return 0;
+
+remove_links:
+ acpi_thermal_zone_sysfs_remove(tz);
+unregister_tzd:
+ thermal_zone_device_unregister(tz->thermal_zone);
+
+ return result;
+}
+
+static void acpi_thermal_zone_unregister(void *data)
+{
+ struct acpi_thermal *tz = data;
+
+ flush_workqueue(acpi_thermal_pm_queue);
+
+ thermal_zone_device_disable(tz->thermal_zone);
+ acpi_thermal_zone_sysfs_remove(tz);
+ thermal_zone_device_unregister(tz->thermal_zone);
+ tz->thermal_zone = NULL;
+}
+
+
+/* --------------------------------------------------------------------------
+ Driver Interface
+ -------------------------------------------------------------------------- */
+
+static void acpi_thermal_notify(acpi_handle handle, u32 event, void *data)
+{
+ struct acpi_thermal *tz = data;
+
+ if (!tz)
+ return;
+
+ switch (event) {
+ case ACPI_THERMAL_NOTIFY_TEMPERATURE:
+ acpi_queue_thermal_check(tz);
+ break;
+ case ACPI_THERMAL_NOTIFY_THRESHOLDS:
+ case ACPI_THERMAL_NOTIFY_DEVICES:
+ acpi_thermal_trips_update(tz, event);
+ break;
+ default:
+ acpi_handle_debug(tz->device->handle,
+ "Unsupported event [0x%x]\n", event);
+ break;
+ }
+}
+
+/*
+ * On some platforms, the AML code has dependency about
+ * the evaluating order of _TMP and _CRT/_HOT/_PSV/_ACx.
+ * 1. On HP Pavilion G4-1016tx, _TMP must be invoked after
+ * /_CRT/_HOT/_PSV/_ACx, or else system will be power off.
+ * 2. On HP Compaq 6715b/6715s, the return value of _PSV is 0
+ * if _TMP has never been evaluated.
+ *
+ * As this dependency is totally transparent to OS, evaluate
+ * all of them once, in the order of _CRT/_HOT/_PSV/_ACx,
+ * _TMP, before they are actually used.
+ */
+static void acpi_thermal_aml_dependency_fix(struct acpi_thermal *tz)
+{
+ acpi_handle handle = tz->device->handle;
+ unsigned long long value;
+ int i;
+
+ acpi_evaluate_integer(handle, "_CRT", NULL, &value);
+ acpi_evaluate_integer(handle, "_HOT", NULL, &value);
+ acpi_evaluate_integer(handle, "_PSV", NULL, &value);
+ for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
+ char name[5] = { '_', 'A', 'C', ('0' + i), '\0' };
+ acpi_status status;
+
+ status = acpi_evaluate_integer(handle, name, NULL, &value);
+ if (status == AE_NOT_FOUND)
+ break;
+ }
+ acpi_evaluate_integer(handle, "_TMP", NULL, &value);
+}
+
+/*
+ * The exact offset between Kelvin and degree Celsius is 273.15. However ACPI
+ * handles temperature values with a single decimal place. As a consequence,
+ * some implementations use an offset of 273.1 and others use an offset of
+ * 273.2. Try to find out which one is being used, to present the most
+ * accurate and visually appealing number.
+ *
+ * The heuristic below should work for all ACPI thermal zones which have a
+ * critical trip point with a value being a multiple of 0.5 degree Celsius.
+ */
+static void acpi_thermal_guess_offset(struct acpi_thermal *tz, long crit_temp)
+{
+ if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1)
+ tz->kelvin_offset = 273100;
+ else
+ tz->kelvin_offset = 273200;
+}
+
+static void acpi_thermal_check_fn(struct work_struct *work)
+{
+ struct acpi_thermal *tz = container_of(work, struct acpi_thermal,
+ thermal_check_work);
+
+ /*
+ * In general, it is not sufficient to check the pending bit, because
+ * subsequent instances of this function may be queued after one of them
+ * has started running (e.g. if _TMP sleeps). Avoid bailing out if just
+ * one of them is running, though, because it may have done the actual
+ * check some time ago, so allow at least one of them to block on the
+ * mutex while another one is running the update.
+ */
+ if (!refcount_dec_not_one(&tz->thermal_check_count))
+ return;
+
+ mutex_lock(&tz->thermal_check_lock);
+
+ thermal_zone_device_update(tz->thermal_zone, THERMAL_EVENT_UNSPECIFIED);
+
+ refcount_inc(&tz->thermal_check_count);
+
+ mutex_unlock(&tz->thermal_check_lock);
+}
+
+static void acpi_thermal_zone_free(void *data)
+{
+ struct acpi_thermal *tz = data;
+ int i;
+
+ acpi_handle_list_free(&tz->trips.passive.trip.devices);
+ for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++)
+ acpi_handle_list_free(&tz->trips.active[i].trip.devices);
+
+ kfree(tz);
+}
+
+static int acpi_thermal_probe(struct platform_device *pdev)
+{
+ struct thermal_trip trip_table[ACPI_THERMAL_MAX_NR_TRIPS] = { 0 };
+ struct device *dev = &pdev->dev;
+ struct acpi_device *device = ACPI_COMPANION(dev);
+ struct acpi_thermal_trip *acpi_trip;
+ struct thermal_trip *trip;
+ struct acpi_thermal *tz;
+ int crit_temp, hot_temp;
+ int passive_delay = 0;
+ int result;
+ int i;
+
+ if (!device)
+ return -ENODEV;
+
+ tz = kzalloc_obj(struct acpi_thermal);
+ if (!tz)
+ return -ENOMEM;
+
+ result = devm_add_action_or_reset(dev, acpi_thermal_zone_free, tz);
+ if (result)
+ return result;
+
+ platform_set_drvdata(pdev, tz);
+
+ tz->device = device;
+ strscpy(tz->name, device->pnp.bus_id);
+
+ acpi_thermal_aml_dependency_fix(tz);
+
+ /*
+ * Set the cooling mode [_SCP] to active cooling. This needs to happen before
+ * we retrieve the trip point values.
+ */
+ acpi_execute_simple_method(tz->device->handle, "_SCP", ACPI_THERMAL_MODE_ACTIVE);
+
+ /* Get trip points [_ACi, _PSV, etc.] (required). */
+ acpi_thermal_get_trip_points(tz);
+
+ crit_temp = acpi_thermal_get_critical_trip(tz);
+ hot_temp = acpi_thermal_get_hot_trip(tz);
+
+ /* Get temperature [_TMP] (required). */
+ result = acpi_thermal_get_temperature(tz);
+ if (result)
+ return result;
+
+ /* Determine the default polling frequency [_TZP]. */
+ if (tzp)
+ tz->polling_frequency = tzp;
+ else
+ acpi_thermal_get_polling_frequency(tz);
+
+ acpi_thermal_guess_offset(tz, crit_temp);
+
+ trip = trip_table;
+
+ if (crit_temp != THERMAL_TEMP_INVALID) {
+ trip->type = THERMAL_TRIP_CRITICAL;
+ trip->temperature = acpi_thermal_temp(tz, crit_temp);
+ trip++;
+ }
+
+ if (hot_temp != THERMAL_TEMP_INVALID) {
+ trip->type = THERMAL_TRIP_HOT;
+ trip->temperature = acpi_thermal_temp(tz, hot_temp);
+ trip++;
+ }
+
+ acpi_trip = &tz->trips.passive.trip;
+ if (acpi_thermal_trip_valid(acpi_trip)) {
+ passive_delay = tz->trips.passive.delay;
+
+ trip->type = THERMAL_TRIP_PASSIVE;
+ trip->temperature = acpi_thermal_temp(tz, acpi_trip->temp_dk);
+ trip->priv = acpi_trip;
+ trip++;
+ }
+
+ for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE; i++) {
+ acpi_trip = &tz->trips.active[i].trip;
+
+ if (!acpi_thermal_trip_valid(acpi_trip))
+ break;
+
+ trip->type = THERMAL_TRIP_ACTIVE;
+ trip->temperature = acpi_thermal_temp(tz, acpi_trip->temp_dk);
+ trip->priv = acpi_trip;
+ trip++;
+ }
+
+ if (trip == trip_table)
+ pr_warn(FW_BUG "No valid trip points!\n");
+
+ result = acpi_thermal_register_thermal_zone(tz, trip_table,
+ trip - trip_table,
+ passive_delay);
+ if (result)
+ return result;
+
+ result = devm_add_action_or_reset(dev, acpi_thermal_zone_unregister, tz);
+ if (result)
+ return result;
+
+ refcount_set(&tz->thermal_check_count, 3);
+ mutex_init(&tz->thermal_check_lock);
+ INIT_WORK(&tz->thermal_check_work, acpi_thermal_check_fn);
+
+ pr_info("Thermal Zone [%s] (%ld C)\n", acpi_device_bid(device),
+ deci_kelvin_to_celsius(tz->temp_dk));
+
+ return devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
+ acpi_thermal_notify, tz);
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int acpi_thermal_prepare(struct device *dev)
+{
+ /* Make sure the previously queued thermal check work has been done */
+ flush_workqueue(acpi_thermal_pm_queue);
+ return 0;
+}
+
+static void acpi_thermal_complete(struct device *dev)
+{
+ acpi_queue_thermal_check(dev_get_drvdata(dev));
+}
+
+static const struct dev_pm_ops acpi_thermal_pm_ops = {
+ .prepare = acpi_thermal_prepare,
+ .complete = acpi_thermal_complete,
+};
+#define ACPI_THERMAL_PM &acpi_thermal_pm_ops
+#else /* !CONFIG_PM_SLEEP */
+#define ACPI_THERMAL_PM NULL
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct acpi_device_id thermal_device_ids[] = {
+ {ACPI_THERMAL_HID, 0},
+ {"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, thermal_device_ids);
+
+static struct platform_driver acpi_thermal_driver = {
+ .probe = acpi_thermal_probe,
+ .driver = {
+ .name = "acpi-thermal",
+ .acpi_match_table = thermal_device_ids,
+ .pm = ACPI_THERMAL_PM,
+ },
+};
+
+static int thermal_act(const struct dmi_system_id *d)
+{
+ if (act == 0) {
+ pr_notice("%s detected: disabling all active thermal trip points\n",
+ d->ident);
+ act = -1;
+ }
+ return 0;
+}
+
+static int thermal_nocrt(const struct dmi_system_id *d)
+{
+ pr_notice("%s detected: disabling all critical thermal trip point actions.\n",
+ d->ident);
+ crt = -1;
+ return 0;
+}
+
+static int thermal_tzp(const struct dmi_system_id *d)
+{
+ if (tzp == 0) {
+ pr_notice("%s detected: enabling thermal zone polling\n",
+ d->ident);
+ tzp = 300; /* 300 dS = 30 Seconds */
+ }
+ return 0;
+}
+
+static int thermal_psv(const struct dmi_system_id *d)
+{
+ if (psv == 0) {
+ pr_notice("%s detected: disabling all passive thermal trip points\n",
+ d->ident);
+ psv = -1;
+ }
+ return 0;
+}
+
+static const struct dmi_system_id thermal_dmi_table[] __initconst = {
+ /*
+ * Award BIOS on this AOpen makes thermal control almost worthless.
+ * http://bugzilla.kernel.org/show_bug.cgi?id=8842
+ */
+ {
+ .callback = thermal_act,
+ .ident = "AOpen i915GMm-HFS",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
+ DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
+ },
+ },
+ {
+ .callback = thermal_psv,
+ .ident = "AOpen i915GMm-HFS",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
+ DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
+ },
+ },
+ {
+ .callback = thermal_tzp,
+ .ident = "AOpen i915GMm-HFS",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
+ DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
+ },
+ },
+ {
+ .callback = thermal_nocrt,
+ .ident = "Gigabyte GA-7ZX",
+ .matches = {
+ DMI_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+ DMI_MATCH(DMI_BOARD_NAME, "7ZX"),
+ },
+ },
+ {}
+};
+
+static int __init acpi_thermal_init(void)
+{
+ int result;
+
+ dmi_check_system(thermal_dmi_table);
+
+ if (off) {
+ pr_notice("thermal control disabled\n");
+ return -ENODEV;
+ }
+
+ acpi_thermal_pm_queue = alloc_workqueue("acpi_thermal_pm",
+ WQ_HIGHPRI | WQ_MEM_RECLAIM | WQ_PERCPU,
+ 0);
+ if (!acpi_thermal_pm_queue)
+ return -ENODEV;
+
+ result = platform_driver_register(&acpi_thermal_driver);
+ if (result < 0) {
+ destroy_workqueue(acpi_thermal_pm_queue);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static void __exit acpi_thermal_exit(void)
+{
+ platform_driver_unregister(&acpi_thermal_driver);
+ destroy_workqueue(acpi_thermal_pm_queue);
+}
+
+module_init(acpi_thermal_init);
+module_exit(acpi_thermal_exit);
+
+MODULE_IMPORT_NS("ACPI_THERMAL");
+MODULE_AUTHOR("Paul Diefenbaugh");
+MODULE_DESCRIPTION("ACPI Thermal Zone Driver");
+MODULE_LICENSE("GPL");