If any problems occur when changing profiles for any driver, then revert back to the previous profile. Tested-by: Matthew Schwartz <matthew.schwartz@xxxxxxxxx> Signed-off-by: Mario Limonciello <mario.limonciello@xxxxxxx> --- drivers/acpi/platform_profile.c | 203 ++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 86 deletions(-) diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c index 091ca6941a925..915e3c49f0b5f 100644 --- a/drivers/acpi/platform_profile.c +++ b/drivers/acpi/platform_profile.c @@ -9,7 +9,6 @@ #include <linux/platform_profile.h> #include <linux/sysfs.h> -static struct platform_profile_handler *cur_profile; static LIST_HEAD(platform_profile_handler_list); static DEFINE_MUTEX(profile_lock); @@ -36,26 +35,26 @@ static ssize_t platform_profile_choices_show(struct device *dev, struct device_attribute *attr, char *buf) { + struct platform_profile_handler *handler; + unsigned long seen = 0; int len = 0; - int err, i; - - err = mutex_lock_interruptible(&profile_lock); - if (err) - return err; - - if (!cur_profile) { - mutex_unlock(&profile_lock); - return -ENODEV; + int i; + + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { + list_for_each_entry(handler, &platform_profile_handler_list, list) { + for_each_set_bit(i, handler->choices, PLATFORM_PROFILE_LAST) { + if (seen & BIT(i)) + continue; + if (len == 0) + len += sysfs_emit_at(buf, len, "%s", profile_names[i]); + else + len += sysfs_emit_at(buf, len, " %s", profile_names[i]); + seen |= BIT(i); + } + } } - for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) { - if (len == 0) - len += sysfs_emit_at(buf, len, "%s", profile_names[i]); - else - len += sysfs_emit_at(buf, len, " %s", profile_names[i]); - } len += sysfs_emit_at(buf, len, "\n"); - mutex_unlock(&profile_lock); return len; } @@ -64,22 +63,20 @@ static ssize_t platform_profile_show(struct device *dev, char *buf) { enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED; + struct platform_profile_handler *handler; int err; - err = mutex_lock_interruptible(&profile_lock); - if (err) - return err; - if (!cur_profile) { - mutex_unlock(&profile_lock); - return -ENODEV; + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { + if (!platform_profile_is_registered()) + return -ENODEV; + list_for_each_entry(handler, &platform_profile_handler_list, list) { + err = handler->profile_get(handler, &profile); + if (err) + return err; + } } - err = cur_profile->profile_get(cur_profile, &profile); - mutex_unlock(&profile_lock); - if (err) - return err; - /* Check that profile is valid index */ if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names)))) return -EIO; @@ -91,37 +88,48 @@ static ssize_t platform_profile_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { + struct platform_profile_handler *handler; + enum platform_profile_option profile; int err, i; - err = mutex_lock_interruptible(&profile_lock); - if (err) - return err; - - if (!cur_profile) { - mutex_unlock(&profile_lock); - return -ENODEV; - } - /* Scan for a matching profile */ i = sysfs_match_string(profile_names, buf); if (i < 0) { - mutex_unlock(&profile_lock); return -EINVAL; } - /* Check that platform supports this profile choice */ - if (!test_bit(i, cur_profile->choices)) { - mutex_unlock(&profile_lock); - return -EOPNOTSUPP; + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { + if (!platform_profile_is_registered()) + return -ENODEV; + + /* Check that all handlers support this profile choice */ + list_for_each_entry(handler, &platform_profile_handler_list, list) { + if (!test_bit(i, handler->choices)) + return -EOPNOTSUPP; + + /* save the profile so that it can be reverted if necessary */ + err = handler->profile_get(handler, &profile); + if (err) + return err; + } + + list_for_each_entry(handler, &platform_profile_handler_list, list) { + err = handler->profile_set(handler, i); + if (err) { + pr_err("Failed to set profile for handler %s\n", handler->name); + break; + } + } + if (err) { + list_for_each_entry_continue_reverse(handler, &platform_profile_handler_list, list) { + if (handler->profile_set(handler, profile)) + pr_err("Failed to revert profile for handler %s\n", handler->name); + } + return err; + } } - err = cur_profile->profile_set(cur_profile, i); - if (!err) - sysfs_notify(acpi_kobj, NULL, "platform_profile"); - - mutex_unlock(&profile_lock); - if (err) - return err; + sysfs_notify(acpi_kobj, NULL, "platform_profile"); return count; } @@ -140,7 +148,8 @@ static const struct attribute_group platform_profile_group = { void platform_profile_notify(void) { - if (!cur_profile) + guard(mutex)(&profile_lock); + if (!platform_profile_is_registered()) return; sysfs_notify(acpi_kobj, NULL, "platform_profile"); } @@ -148,40 +157,65 @@ EXPORT_SYMBOL_GPL(platform_profile_notify); int platform_profile_cycle(void) { + struct platform_profile_handler *handler; enum platform_profile_option profile; - enum platform_profile_option next; + enum platform_profile_option next = PLATFORM_PROFILE_LAST; + enum platform_profile_option next2 = PLATFORM_PROFILE_LAST; int err; - err = mutex_lock_interruptible(&profile_lock); - if (err) - return err; - - if (!cur_profile) { - mutex_unlock(&profile_lock); - return -ENODEV; - } - - err = cur_profile->profile_get(cur_profile, &profile); - if (err) { - mutex_unlock(&profile_lock); - return err; - } - - next = find_next_bit_wrap(cur_profile->choices, PLATFORM_PROFILE_LAST, - profile + 1); - - if (WARN_ON(next == PLATFORM_PROFILE_LAST)) { - mutex_unlock(&profile_lock); - return -EINVAL; + scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) { + /* first pass, make sure all handlers agree on the definition of "next" profile */ + list_for_each_entry(handler, &platform_profile_handler_list, list) { + + err = handler->profile_get(handler, &profile); + if (err) + return err; + + if (next == PLATFORM_PROFILE_LAST) + next = find_next_bit_wrap(handler->choices, + PLATFORM_PROFILE_LAST, + profile + 1); + else + next2 = find_next_bit_wrap(handler->choices, + PLATFORM_PROFILE_LAST, + profile + 1); + + if (WARN_ON(next == PLATFORM_PROFILE_LAST)) + return -EINVAL; + + if (next2 == PLATFORM_PROFILE_LAST) + continue; + + if (next != next2) { + pr_warn("Next profile to cycle to is ambiguous between platform_profile handlers\n"); + return -EINVAL; + } + next = next2; + } + + /* + * Second pass: apply "next" to each handler + * If any failures occur unwind and revert all back to the original profile + */ + list_for_each_entry(handler, &platform_profile_handler_list, list) { + err = handler->profile_set(handler, next); + if (err) { + pr_err("Failed to set profile for handler %s\n", handler->name); + break; + } + } + if (err) { + list_for_each_entry_continue_reverse(handler, &platform_profile_handler_list, list) { + err = handler->profile_set(handler, profile); + if (err) + pr_err("Failed to revert profile for handler %s\n", handler->name); + } + } } - err = cur_profile->profile_set(cur_profile, next); - mutex_unlock(&profile_lock); - - if (!err) - sysfs_notify(acpi_kobj, NULL, "platform_profile"); + sysfs_notify(acpi_kobj, NULL, "platform_profile"); - return err; + return 0; } EXPORT_SYMBOL_GPL(platform_profile_cycle); @@ -190,21 +224,19 @@ int platform_profile_register(struct platform_profile_handler *pprof) int err; guard(mutex)(&profile_lock); - /* We can only have one active profile */ - if (cur_profile) - return -EEXIST; /* Sanity check the profile handler field are set */ if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) || !pprof->profile_set || !pprof->profile_get) return -EINVAL; - err = sysfs_create_group(acpi_kobj, &platform_profile_group); - if (err) - return err; + if (!platform_profile_is_registered()) { + err = sysfs_create_group(acpi_kobj, &platform_profile_group); + if (err) + return err; + } list_add_tail(&pprof->list, &platform_profile_handler_list); - cur_profile = pprof; return 0; } EXPORT_SYMBOL_GPL(platform_profile_register); @@ -215,7 +247,6 @@ int platform_profile_remove(struct platform_profile_handler *pprof) list_del(&pprof->list); - cur_profile = NULL; if (!platform_profile_is_registered()) sysfs_remove_group(acpi_kobj, &platform_profile_group); -- 2.43.0