diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-30 13:10:30 -1000 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-10-30 13:10:30 -1000 |
| commit | e3e15872e77f3feecd831eccfb753b9af916f387 (patch) | |
| tree | 31fede01214b37fcc15994046772c554b8c357e8 | |
| parent | 17fa96334e15c6b38e8d9dd3627f3a6c5ca034f8 (diff) | |
| parent | 3f971ee94fb57377f0a9d5a8d190d1d465ac6b80 (diff) | |
| download | linux-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.c | 1 | ||||
| -rw-r--r-- | drivers/acpi/battery.c | 12 | ||||
| -rw-r--r-- | drivers/acpi/platform_profile.c | 82 | ||||
| -rw-r--r-- | drivers/acpi/sysfs.c | 20 | ||||
| -rw-r--r-- | drivers/acpi/utils.c | 41 | ||||
| -rw-r--r-- | drivers/platform/x86/lenovo/ideapad-laptop.c | 12 | ||||
| -rw-r--r-- | drivers/thermal/intel/int340x_thermal/int3403_thermal.c | 3 | ||||
| -rw-r--r-- | drivers/thermal/intel/int340x_thermal/int340x_thermal_zone.c | 6 | ||||
| -rw-r--r-- | drivers/thermal/intel/int340x_thermal/processor_thermal_device.c | 2 | ||||
| -rw-r--r-- | include/acpi/acpi_bus.h | 7 |
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, ¶ms, &result); + status = acpi_evaluate_integer_exclusive(handle, (char *)name, ¶ms, &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", ¶ms, NULL); + status = acpi_evaluate_object_exclusive(handle, "VPCW", ¶ms, 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); |
