ACPI support fixes for 7.4-rc1

- 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)
 -----BEGIN PGP SIGNATURE-----
 
 iQFGBAABCAAwFiEEcM8Aw/RY0dgsiRUR7l+9nS/U47UFAmrk+i0SHHJqd0Byand5
 c29ja2kubmV0AAoJEO5fvZ0v1OO1BKUIAIvsvGOzWnUBWGY2GN9umZfNbEFnYrXH
 7mbAzDTP5lDjyqnrod7eSjXAATxWISxupR7qVPSbqjqGURwzMCZsPeX4ZRd5pUTZ
 THA0KTeE57hMKj/dvzvFQKIswGUkruQ09NZ3J6KZ514CJJ7SgSOilbUDf+7xzbLZ
 yLDDBc0POqhZaKIuAYSFsoRw+D7PB2q/k5Hvv8VVhumDgl2YcCLcpQSG4lftqdxn
 R6bjVAQaKJj/nArxwdQRt/LdNSQu1WQgnLkq57Ewwd4tORBlgMsGP9GrR/9QaQqY
 E6VINShueGRnDNIMAzb4imbXVsfE+gtoyYdAffCTV3HXy8aoJ95OwGA=
 =04kC
 -----END PGP SIGNATURE-----

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
This commit is contained in:
Linus Torvalds 2026-10-30 13:10:30 -10:00
commit e3e15872e7
10 changed files with 129 additions and 57 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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;
guard(mutex)(&profile_lock);
dev = class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered);
if (!dev)
return 0;
put_device(dev);
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);

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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);