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