Merge branch 'acpi-driver' into linux-next * acpi-driver: 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
diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 8599949..6edea55 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 79d2e92..b4c91d5 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; + 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);
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 7c584d4..27ab228 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 8213524..1335f4e 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 264c9bc..2262de1 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 965700e..9451fea 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 850bfe7..cef1d34 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 a755b15..f59b2de 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);