summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-10-30 13:10:30 -1000
committerLinus Torvalds <torvalds@linux-foundation.org>2026-10-30 13:10:30 -1000
commite3e15872e77f3feecd831eccfb753b9af916f387 (patch)
tree31fede01214b37fcc15994046772c554b8c357e8
parent17fa96334e15c6b38e8d9dd3627f3a6c5ca034f8 (diff)
parent3f971ee94fb57377f0a9d5a8d190d1d465ac6b80 (diff)
downloadlinux-e3e15872e77f3feecd831eccfb753b9af916f387.tar.gz
linux-e3e15872e77f3feecd831eccfb753b9af916f387.zip
Merge tag 'acpi-7.4-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
Pull ACPI support fixes from Rafael Wysocki: "These address assorted issues in ACPI support code and work around concurrency-related issues in AML on some platforms: - Separate handler locking from sysfs lifetime in the ACPI platform profile driver to avoid possible deadlocks due to sysfs accesses during driver removal (Daniel Lezcano) - Introduce additional locking around the evaluation of selected ACPI control methods in a few places to work around issues related to concurrency in AML on some platforms (Ali Ahmet Memiş) - Address two issues reported by Sashiko in the ACPI table data sysfs interface (Jamie Nguyen) - Initialize parser_state.aml_end in acpi_install_method() properly to avoid out-of-bounds accesses during control method installation (Ivan Hu)" * tag 'acpi-7.4-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: ACPI: sysfs: Finish sysfs init when creating a table file fails ACPI: sysfs: Map only the requested range in acpi_data_show() ACPICA: Initialize parser_state.aml_end in acpi_install_method() ACPI: platform_profile: Separate handler locking from sysfs lifetime platform/x86: ideapad-laptop: Evaluate ACPI methods exclusively thermal: intel: int340x: Evaluate _TMP exclusively ACPI: battery: Evaluate battery methods exclusively ACPI: utils: Add exclusive AML evaluation helpers
-rw-r--r--drivers/acpi/acpica/nsxfname.c1
-rw-r--r--drivers/acpi/battery.c12
-rw-r--r--drivers/acpi/platform_profile.c82
-rw-r--r--drivers/acpi/sysfs.c20
-rw-r--r--drivers/acpi/utils.c41
-rw-r--r--drivers/platform/x86/lenovo/ideapad-laptop.c12
-rw-r--r--drivers/thermal/intel/int340x_thermal/int3403_thermal.c3
-rw-r--r--drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c6
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_device.c2
-rw-r--r--include/acpi/acpi_bus.h7
10 files changed, 129 insertions, 57 deletions
diff --git a/drivers/acpi/acpica/nsxfname.c b/drivers/acpi/acpica/nsxfname.c
index b6534187cd43..fcd94947fedb 100644
--- a/drivers/acpi/acpica/nsxfname.c
+++ b/drivers/acpi/acpica/nsxfname.c
@@ -503,6 +503,7 @@ acpi_status acpi_install_method(u8 *buffer)
/* First AML opcode in the table must be a control method */
parser_state.aml = buffer + sizeof(struct acpi_table_header);
+ parser_state.aml_end = buffer + table->length;
opcode = acpi_ps_peek_opcode(&parser_state);
if (opcode != AML_METHOD_OP) {
return (AE_BAD_PARAMETER);
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c
index 8599949f8786..6edea559a5bc 100644
--- a/drivers/acpi/battery.c
+++ b/drivers/acpi/battery.c
@@ -647,9 +647,8 @@ static int acpi_battery_get_info(struct acpi_battery *battery)
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_status status = AE_ERROR;
- status = acpi_evaluate_object(battery->device->handle,
- use_bix ? "_BIX":"_BIF",
- NULL, &buffer);
+ status = acpi_evaluate_object_exclusive(battery->device->handle,
+ use_bix ? "_BIX" : "_BIF", NULL, &buffer);
if (ACPI_FAILURE(status)) {
acpi_handle_info(battery->device->handle,
@@ -688,8 +687,7 @@ static int acpi_battery_get_state(struct acpi_battery *battery)
msecs_to_jiffies(cache_time)))
return 0;
- status = acpi_evaluate_object(battery->device->handle, "_BST",
- NULL, &buffer);
+ status = acpi_evaluate_object_exclusive(battery->device->handle, "_BST", NULL, &buffer);
if (ACPI_FAILURE(status)) {
acpi_handle_info(battery->device->handle,
"_BST evaluation failed: %s",
@@ -739,8 +737,8 @@ static int acpi_battery_set_alarm(struct acpi_battery *battery)
!test_bit(ACPI_BATTERY_ALARM_PRESENT, &battery->flags))
return -ENODEV;
- status = acpi_execute_simple_method(battery->device->handle, "_BTP",
- battery->alarm);
+ status = acpi_execute_simple_method_exclusive(battery->device->handle, "_BTP",
+ battery->alarm);
if (ACPI_FAILURE(status))
return -ENODEV;
diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c
index 79d2e92e5c28..b4c91d57f3d4 100644
--- a/drivers/acpi/platform_profile.c
+++ b/drivers/acpi/platform_profile.c
@@ -8,15 +8,25 @@
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/init.h>
+#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/platform_profile.h>
#include <linux/sysfs.h>
#define to_pprof_handler(d) (container_of(d, struct platform_profile_handler, dev))
+/*
+ * Protects profile_handlers membership and serializes profile operations
+ * against each other and handler removal. Never held across sysfs teardown,
+ * which can wait for callbacks that acquire this lock.
+ */
static DEFINE_MUTEX(profile_lock);
+/* Serialize registration and sysfs updates without blocking sysfs callbacks. */
+static DEFINE_MUTEX(profile_registration_lock);
+static LIST_HEAD(profile_handlers);
struct platform_profile_handler {
+ struct list_head node;
const char *name;
struct device dev;
int minor;
@@ -44,6 +54,23 @@ static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
static DEFINE_IDA(platform_profile_ida);
+static int profile_for_each_handler(void *data,
+ int (*fn)(struct device *dev, void *data))
+{
+ struct platform_profile_handler *handler;
+ int ret;
+
+ lockdep_assert_held(&profile_lock);
+
+ list_for_each_entry(handler, &profile_handlers, node) {
+ ret = fn(&handler->dev, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* _commmon_choices_show - Show the available profile choices
* @choices: The available profile choices
@@ -306,20 +333,18 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj,
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ err = profile_for_each_handler(&data, _aggregate_choices);
if (err)
return err;
if (data.count == 1) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _remove_hidden_choices);
+ err = profile_for_each_handler(&data, _remove_hidden_choices);
if (err)
return err;
}
}
/* no profile handler registered any more */
- if (bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
+ if (!data.count || bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
return -EINVAL;
return _commmon_choices_show(data.aggregate, buf);
@@ -384,8 +409,7 @@ static ssize_t platform_profile_show(struct kobject *kobj,
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &profile, _aggregate_profiles);
+ err = profile_for_each_handler(&profile, _aggregate_profiles);
if (err)
return err;
}
@@ -423,15 +447,13 @@ static ssize_t platform_profile_store(struct kobject *kobj,
return -EINVAL;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- ret = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ ret = profile_for_each_handler(&data, _aggregate_choices);
if (ret)
return ret;
- if (!test_bit(i, data.aggregate))
+ if (!data.count || !test_bit(i, data.aggregate))
return -EOPNOTSUPP;
- ret = class_for_each_device(&platform_profile_class, NULL, &i,
- _store_and_notify);
+ ret = profile_for_each_handler(&i, _store_and_notify);
if (ret)
return ret;
}
@@ -450,22 +472,11 @@ static struct attribute *platform_profile_attrs[] = {
NULL
};
-static int profile_class_registered(struct device *dev, const void *data)
-{
- return 1;
-}
-
static umode_t profile_class_is_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
- struct device *dev;
-
- dev = class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered);
- if (!dev)
- return 0;
-
- put_device(dev);
+ guard(mutex)(&profile_lock);
- return attr->mode;
+ return list_empty(&profile_handlers) ? 0 : attr->mode;
}
static const struct attribute_group platform_profile_group = {
@@ -502,8 +513,7 @@ int platform_profile_cycle(void)
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &profile, _aggregate_profiles);
+ err = profile_for_each_handler(&profile, _aggregate_profiles);
if (err)
return err;
@@ -512,8 +522,7 @@ int platform_profile_cycle(void)
profile == PLATFORM_PROFILE_LAST)
return -EINVAL;
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ err = profile_for_each_handler(&data, _aggregate_choices);
if (err)
return err;
@@ -525,8 +534,7 @@ int platform_profile_cycle(void)
PLATFORM_PROFILE_LAST,
profile + 1);
- err = class_for_each_device(&platform_profile_class, NULL, &next,
- _store_and_notify);
+ err = profile_for_each_handler(&next, _store_and_notify);
if (err)
return err;
@@ -583,7 +591,7 @@ struct device *platform_profile_register(struct device *dev, const char *name,
}
}
- guard(mutex)(&profile_lock);
+ guard(mutex)(&profile_registration_lock);
/* create class interface for individual handler */
minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
@@ -604,6 +612,8 @@ struct device *platform_profile_register(struct device *dev, const char *name,
put_device(ppdev);
goto cleanup_ida;
}
+ scoped_guard(mutex, &profile_lock)
+ list_add_tail(&to_pprof_handler(ppdev)->node, &profile_handlers);
sysfs_notify(acpi_kobj, NULL, "platform_profile");
@@ -614,6 +624,9 @@ struct device *platform_profile_register(struct device *dev, const char *name,
return ppdev;
cleanup_cur:
+ scoped_guard(mutex, &profile_lock)
+ list_del(&to_pprof_handler(ppdev)->node);
+
device_unregister(ppdev);
cleanup_ida:
@@ -636,7 +649,10 @@ void platform_profile_remove(struct device *dev)
pprof = to_pprof_handler(dev);
- guard(mutex)(&profile_lock);
+ guard(mutex)(&profile_registration_lock);
+
+ scoped_guard(mutex, &profile_lock)
+ list_del(&pprof->node);
ida_free(&platform_profile_ida, pprof->minor);
device_unregister(&pprof->dev);
diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c
index a99373547126..a4534b3e3cfb 100644
--- a/drivers/acpi/sysfs.c
+++ b/drivers/acpi/sysfs.c
@@ -420,6 +420,7 @@ static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj,
struct acpi_data_attr *data_attr;
void __iomem *base;
ssize_t size;
+ u64 end;
data_attr = container_of(bin_attr, struct acpi_data_attr, attr);
size = data_attr->attr.size;
@@ -433,13 +434,17 @@ static ssize_t acpi_data_show(struct file *filp, struct kobject *kobj,
if (count > size - offset)
count = size - offset;
- base = acpi_os_map_iomem(data_attr->addr, size);
+ /* acpi_os_map_iomem() can't handle a range that wraps around */
+ if (check_add_overflow(data_attr->addr, size, &end))
+ return -EINVAL;
+
+ base = acpi_os_map_iomem(data_attr->addr + offset, count);
if (!base)
return -ENOMEM;
- memcpy_fromio(buf, base + offset, count);
+ memcpy_fromio(buf, base, count);
- acpi_os_unmap_iomem(base, size);
+ acpi_os_unmap_iomem(base, count);
return count;
}
@@ -511,7 +516,6 @@ static int acpi_tables_sysfs_init(void)
struct acpi_table_header *table_header = NULL;
int table_index;
acpi_status status;
- int ret;
tables_kobj = kobject_create_and_add("tables", acpi_kobj);
if (!tables_kobj)
@@ -536,13 +540,11 @@ static int acpi_tables_sysfs_init(void)
table_attr = kzalloc_obj(*table_attr);
if (!table_attr)
- return -ENOMEM;
+ break;
- ret = acpi_table_attr_init(tables_kobj,
- table_attr, table_header);
- if (ret) {
+ if (acpi_table_attr_init(tables_kobj, table_attr, table_header)) {
kfree(table_attr);
- return ret;
+ break;
}
list_add_tail(&table_attr->node, &acpi_table_attr_list);
acpi_table_data_init(table_header, table_attr);
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c
index 7c584d490d6a..27ab2283937a 100644
--- a/drivers/acpi/utils.c
+++ b/drivers/acpi/utils.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/types.h>
@@ -243,6 +244,28 @@ acpi_extract_package(union acpi_object *package,
EXPORT_SYMBOL(acpi_extract_package);
+static DEFINE_MUTEX(acpi_exclusive_eval_lock);
+
+/**
+ * acpi_evaluate_object_exclusive - Evaluate an object without interleaving
+ * @handle: ACPI handle of the object or its scope.
+ * @pathname: Path of the object relative to @handle.
+ * @arguments: Arguments to pass, or NULL.
+ * @buffer: Buffer for the return value, or NULL.
+ *
+ * Same as acpi_evaluate_object(), but serialized with other exclusive
+ * evaluations. Use it only for methods known to break when interleaved.
+ */
+acpi_status acpi_evaluate_object_exclusive(acpi_handle handle, acpi_string pathname,
+ struct acpi_object_list *arguments,
+ struct acpi_buffer *buffer)
+{
+ guard(mutex)(&acpi_exclusive_eval_lock);
+
+ return acpi_evaluate_object(handle, pathname, arguments, buffer);
+}
+EXPORT_SYMBOL_GPL(acpi_evaluate_object_exclusive);
+
acpi_status
acpi_evaluate_integer(acpi_handle handle,
acpi_string pathname,
@@ -277,6 +300,16 @@ acpi_evaluate_integer(acpi_handle handle,
EXPORT_SYMBOL(acpi_evaluate_integer);
+acpi_status acpi_evaluate_integer_exclusive(acpi_handle handle, acpi_string pathname,
+ struct acpi_object_list *arguments,
+ unsigned long long *data)
+{
+ guard(mutex)(&acpi_exclusive_eval_lock);
+
+ return acpi_evaluate_integer(handle, pathname, arguments, data);
+}
+EXPORT_SYMBOL_GPL(acpi_evaluate_integer_exclusive);
+
int acpi_get_local_u64_address(acpi_handle handle, u64 *addr)
{
acpi_status status;
@@ -685,6 +718,14 @@ acpi_status acpi_execute_simple_method(acpi_handle handle, char *method,
}
EXPORT_SYMBOL(acpi_execute_simple_method);
+acpi_status acpi_execute_simple_method_exclusive(acpi_handle handle, char *method, u64 arg)
+{
+ guard(mutex)(&acpi_exclusive_eval_lock);
+
+ return acpi_execute_simple_method(handle, method, arg);
+}
+EXPORT_SYMBOL_GPL(acpi_execute_simple_method_exclusive);
+
/**
* acpi_evaluate_ej0: Evaluate _EJ0 method for hotplug operations
* @handle: ACPI device handle
diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index fcb5ed7035f5..857a449c4f8a 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -297,12 +297,16 @@ static void ideapad_shared_exit(struct ideapad_private *priv)
#define IDEAPAD_EC_POLL_MIN_US 150
#define IDEAPAD_EC_POLL_MAX_US 300
+/*
+ * On some models GBMD and the battery _BIX take the EC mutex and a
+ * serialized method in opposite order, so evaluate all methods exclusively.
+ */
static int eval_int(acpi_handle handle, const char *name, unsigned long *res)
{
unsigned long long result;
acpi_status status;
- status = acpi_evaluate_integer(handle, (char *)name, NULL, &result);
+ status = acpi_evaluate_integer_exclusive(handle, (char *)name, NULL, &result);
if (ACPI_FAILURE(status))
return -EIO;
@@ -324,7 +328,7 @@ static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long
in_obj.type = ACPI_TYPE_INTEGER;
in_obj.integer.value = arg;
- status = acpi_evaluate_integer(handle, (char *)name, &params, &result);
+ status = acpi_evaluate_integer_exclusive(handle, (char *)name, &params, &result);
if (ACPI_FAILURE(status))
return -EIO;
@@ -336,7 +340,7 @@ static int eval_int_with_arg(acpi_handle handle, const char *name, unsigned long
static int exec_simple_method(acpi_handle handle, const char *name, unsigned long arg)
{
- acpi_status status = acpi_execute_simple_method(handle, (char *)name, arg);
+ acpi_status status = acpi_execute_simple_method_exclusive(handle, (char *)name, arg);
return ACPI_FAILURE(status) ? -EIO : 0;
}
@@ -394,7 +398,7 @@ static int eval_vpcw(acpi_handle handle, unsigned long cmd, unsigned long data)
in_obj[1].type = ACPI_TYPE_INTEGER;
in_obj[1].integer.value = data;
- status = acpi_evaluate_object(handle, "VPCW", &params, NULL);
+ status = acpi_evaluate_object_exclusive(handle, "VPCW", &params, NULL);
if (ACPI_FAILURE(status))
return -EIO;
diff --git a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
index 264c9bc8e645..2262de1e14d9 100644
--- a/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
+++ b/drivers/thermal/intel/int340x_thermal/int3403_thermal.c
@@ -217,8 +217,7 @@ static int int3403_add(struct platform_device *pdev)
}
- status = acpi_evaluate_integer(priv->adev->handle, "_TMP",
- NULL, &tmp);
+ status = acpi_evaluate_integer_exclusive(priv->adev->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status)) {
status = acpi_evaluate_integer(priv->adev->handle, "PTYP",
NULL, &priv->type);
diff --git a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
index 965700ee6962..9451fea1f65c 100644
--- a/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
+++ b/drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c
@@ -18,7 +18,11 @@ static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
unsigned long long tmp;
acpi_status status;
- status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp);
+ /*
+ * On some ThinkPads _TMP switches the EC page selector without
+ * taking the mutex the battery methods use.
+ */
+ status = acpi_evaluate_integer_exclusive(d->adev->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status))
return -EIO;
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
index 850bfe7c1ede..cef1d34f3f53 100644
--- a/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_device.c
@@ -287,7 +287,7 @@ int proc_thermal_add(struct device *dev, struct proc_thermal_device *proc_priv)
if (ret)
return ret;
- status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp);
+ status = acpi_evaluate_integer_exclusive(adev->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status)) {
/* there is no _TMP method, add local method */
if (intel_tcc_get_tjmax(-1) > 0)
diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h
index a755b15af9db..f59b2de865e6 100644
--- a/include/acpi/acpi_bus.h
+++ b/include/acpi/acpi_bus.h
@@ -34,6 +34,12 @@ acpi_status
acpi_evaluate_integer(acpi_handle handle,
acpi_string pathname,
struct acpi_object_list *arguments, unsigned long long *data);
+acpi_status acpi_evaluate_object_exclusive(acpi_handle handle, acpi_string pathname,
+ struct acpi_object_list *arguments,
+ struct acpi_buffer *buffer);
+acpi_status acpi_evaluate_integer_exclusive(acpi_handle handle, acpi_string pathname,
+ struct acpi_object_list *arguments,
+ unsigned long long *data);
bool acpi_evaluate_reference(acpi_handle handle, acpi_string pathname,
struct acpi_object_list *arguments,
struct acpi_handle_list *list);
@@ -50,6 +56,7 @@ acpi_evaluate_ost(acpi_handle handle, u32 source_event, u32 status_code,
bool acpi_has_method(acpi_handle handle, char *name);
acpi_status acpi_execute_simple_method(acpi_handle handle, char *method,
u64 arg);
+acpi_status acpi_execute_simple_method_exclusive(acpi_handle handle, char *method, u64 arg);
acpi_status acpi_evaluate_ej0(acpi_handle handle);
acpi_status acpi_evaluate_lck(acpi_handle handle, int lock);
acpi_status acpi_evaluate_reg(acpi_handle handle, u8 space_id, u32 function);