mbox series

[v5,00/20] Add support for binding ACPI platform profile to multiple drivers

Message ID 20241107060254.17615-1-mario.limonciello@amd.com
Headers show
Series Add support for binding ACPI platform profile to multiple drivers | expand

Message

Mario Limonciello Nov. 7, 2024, 6:02 a.m. UTC
Currently there are a number of ASUS products on the market that happen to
have ACPI objects for amd-pmf to bind to as well as an ACPI platform
profile provided by asus-wmi.

The ACPI platform profile support created by amd-pmf on these ASUS
products is "Function 9" which is specifically for "BIOS or EC
notification" of power slider position. This feature is actively used
by some designs such as Framework 13 and Framework 16.

On these ASUS designs we keep on quirking more and more of them to turn
off this notification so that asus-wmi can bind.

This however isn't how Windows works.  "Multiple" things are notified for
the power slider position. This series adjusts Linux to behave similarly.

Multiple drivers can now register an ACPI platform profile and will react
to set requests.

To avoid chaos, only positions that are common to both drivers are
accepted when the legacy /sys/firmware/acpi/platform_profile interface
is used.

This series also adds a new concept of a "custom" profile.  This allows
userspace to discover that there are multiple driver handlers that are
configured differently.

This series also allows dropping all of the PMF quirks from amd-pmf.
---
v5:
 * Adjust mutex handling
 * Add missing error handling
 * Drop dev member
 * Add cleanup handling for module unload
 * Fix crash on accessing legacy files after all drivers unloaded

Mario Limonciello (20):
  ACPI: platform-profile: Add a name member to handlers
  platform/x86/dell: dell-pc: Create platform device
  ACPI: platform_profile: Add platform handler argument to
    platform_profile_remove()
  ACPI: platform_profile: Move sanity check out of the mutex
  ACPI: platform_profile: Move matching string for new profile out of
    mutex
  ACPI: platform_profile: Use guard(mutex) for register/unregister
  ACPI: platform_profile: Use `scoped_cond_guard`
  ACPI: platform_profile: Create class for ACPI platform profile
  ACPI: platform_profile: Unregister class and sysfs group on module
    unload
  ACPI: platform_profile: Add name attribute to class interface
  ACPI: platform_profile: Add choices attribute for class interface
  ACPI: platform_profile: Add profile attribute for class interface
  ACPI: platform_profile: Notify change events on register and
    unregister
  ACPI: platform_profile: Only show profiles common for all handlers
  ACPI: platform_profile: Add concept of a "custom" profile
  ACPI: platform_profile: Make sure all profile handlers agree on
    profile
  ACPI: platform_profile: Check all profile handler to calculate next
  ACPI: platform_profile: Allow multiple handlers
  platform/x86/amd: pmf: Drop all quirks
  Documentation: Add documentation about class interface for platform
    profiles

 .../ABI/testing/sysfs-platform_profile        |   5 +
 .../userspace-api/sysfs-platform_profile.rst  |  28 +
 drivers/acpi/platform_profile.c               | 494 ++++++++++++++----
 .../surface/surface_platform_profile.c        |   7 +-
 drivers/platform/x86/acer-wmi.c               |   5 +-
 drivers/platform/x86/amd/pmf/Makefile         |   2 +-
 drivers/platform/x86/amd/pmf/core.c           |   1 -
 drivers/platform/x86/amd/pmf/pmf-quirks.c     |  66 ---
 drivers/platform/x86/amd/pmf/pmf.h            |   3 -
 drivers/platform/x86/amd/pmf/sps.c            |   3 +-
 drivers/platform/x86/asus-wmi.c               |   5 +-
 drivers/platform/x86/dell/alienware-wmi.c     |   3 +-
 drivers/platform/x86/dell/dell-pc.c           |  35 +-
 drivers/platform/x86/hp/hp-wmi.c              |   3 +-
 drivers/platform/x86/ideapad-laptop.c         |   3 +-
 .../platform/x86/inspur_platform_profile.c    |   6 +-
 drivers/platform/x86/thinkpad_acpi.c          |   3 +-
 include/linux/platform_profile.h              |   6 +-
 18 files changed, 488 insertions(+), 190 deletions(-)
 delete mode 100644 drivers/platform/x86/amd/pmf/pmf-quirks.c


base-commit: d68cb6023356af3bd3193983ad4ec03954a0b3e2

Comments

Armin Wolf Nov. 7, 2024, 8:07 a.m. UTC | #1
Am 07.11.24 um 07:02 schrieb Mario Limonciello:

> In order to have a device for the platform profile core to reference
> create a platform device for dell-pc.
>
> While doing this change the memory allocation for the thermal handler
> to be device managed to follow the lifecycle of that device.
>
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
> ---
> v5:
>   * use platform_device_register_simple()
> ---
>   drivers/platform/x86/dell/dell-pc.c | 32 +++++++++++++++++++++--------
>   1 file changed, 23 insertions(+), 9 deletions(-)
>
> diff --git a/drivers/platform/x86/dell/dell-pc.c b/drivers/platform/x86/dell/dell-pc.c
> index 3cf79e55e3129..0cd9b26572b61 100644
> --- a/drivers/platform/x86/dell/dell-pc.c
> +++ b/drivers/platform/x86/dell/dell-pc.c
> @@ -18,10 +18,13 @@
>   #include <linux/kernel.h>
>   #include <linux/module.h>
>   #include <linux/platform_profile.h>
> +#include <linux/platform_device.h>
>   #include <linux/slab.h>
>
>   #include "dell-smbios.h"
>
> +static struct platform_device *platform_device;
> +
>   static const struct dmi_system_id dell_device_table[] __initconst = {
>   	{
>   		.ident = "Dell Inc.",
> @@ -244,9 +247,15 @@ static int thermal_init(void)
>   	if (!supported_modes)
>   		return 0;
>
> -	thermal_handler = kzalloc(sizeof(*thermal_handler), GFP_KERNEL);
> -	if (!thermal_handler)
> +	platform_device = platform_device_register_simple("dell-pc", -1, NULL, 0);

Please keep using PLATFORM_DEVID_NONE here.

Thanks,
Armin Wolf

> +	if (!platform_device)
>   		return -ENOMEM;
> +
> +	thermal_handler = devm_kzalloc(&platform_device->dev, sizeof(*thermal_handler), GFP_KERNEL);
> +	if (!thermal_handler) {
> +		ret = -ENOMEM;
> +		goto cleanup_platform_device;
> +	}
>   	thermal_handler->name = "dell-pc";
>   	thermal_handler->profile_get = thermal_platform_profile_get;
>   	thermal_handler->profile_set = thermal_platform_profile_set;
> @@ -262,20 +271,25 @@ static int thermal_init(void)
>
>   	/* Clean up if failed */
>   	ret = platform_profile_register(thermal_handler);
> -	if (ret) {
> -		kfree(thermal_handler);
> -		thermal_handler = NULL;
> -	}
> +	if (ret)
> +		goto cleanup_thermal_handler;
> +
> +	return 0;
> +
> +cleanup_thermal_handler:
> +	thermal_handler = NULL;
> +
> +cleanup_platform_device:
> +	platform_device_unregister(platform_device);
>
>   	return ret;
>   }
>
>   static void thermal_cleanup(void)
>   {
> -	if (thermal_handler) {
> +	if (thermal_handler)
>   		platform_profile_remove();
> -		kfree(thermal_handler);
> -	}
> +	platform_device_unregister(platform_device);
>   }
>
>   static int __init dell_init(void)
Armin Wolf Nov. 7, 2024, 8:21 a.m. UTC | #2
Am 07.11.24 um 07:02 schrieb Mario Limonciello:

> The class and sysfs group are no longer needed when the platform profile
> core is a module and unloaded.
>
> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
> ---
>   drivers/acpi/platform_profile.c | 7 +++++++
>   1 file changed, 7 insertions(+)
>
> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c
> index 652034b71ee9b..9caf070f77f6a 100644
> --- a/drivers/acpi/platform_profile.c
> +++ b/drivers/acpi/platform_profile.c
> @@ -224,6 +224,13 @@ int platform_profile_remove(struct platform_profile_handler *pprof)
>   }
>   EXPORT_SYMBOL_GPL(platform_profile_remove);
>
> +static void __exit platform_profile_exit(void)
> +{
> +	class_unregister(&platform_profile_class);
> +	sysfs_remove_group(acpi_kobj, &platform_profile_group);

This will crash should the class still not exist.

I suggest you register the class and the legacy sysfs group during module initialization, and
add a is_visible() callback to the legacy sysfs group. Then you can use sysfs_update_group() to
update the visibility of the sysfs files when platform profiles come and go.

Also please squash this patch with the patch introducing the class infrastructure.

Thanks,
Armin Wolf

> +}
> +module_exit(platform_profile_exit);
> +
>   MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
>   MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
>   MODULE_LICENSE("GPL");
Armin Wolf Nov. 7, 2024, 9:06 a.m. UTC | #3
Am 07.11.24 um 07:02 schrieb Mario Limonciello:

> Currently there are a number of ASUS products on the market that happen to
> have ACPI objects for amd-pmf to bind to as well as an ACPI platform
> profile provided by asus-wmi.
>
> The ACPI platform profile support created by amd-pmf on these ASUS
> products is "Function 9" which is specifically for "BIOS or EC
> notification" of power slider position. This feature is actively used
> by some designs such as Framework 13 and Framework 16.
>
> On these ASUS designs we keep on quirking more and more of them to turn
> off this notification so that asus-wmi can bind.
>
> This however isn't how Windows works.  "Multiple" things are notified for
> the power slider position. This series adjusts Linux to behave similarly.
>
> Multiple drivers can now register an ACPI platform profile and will react
> to set requests.
>
> To avoid chaos, only positions that are common to both drivers are
> accepted when the legacy /sys/firmware/acpi/platform_profile interface
> is used.
>
> This series also adds a new concept of a "custom" profile.  This allows
> userspace to discover that there are multiple driver handlers that are
> configured differently.
>
> This series also allows dropping all of the PMF quirks from amd-pmf.

Thank you for this patch series. The overall design seems good to me, but i think
you forgot to extend platform_profile_notify().

Thanks,
Armin Wolf

> ---
> v5:
>   * Adjust mutex handling
>   * Add missing error handling
>   * Drop dev member
>   * Add cleanup handling for module unload
>   * Fix crash on accessing legacy files after all drivers unloaded
>
> Mario Limonciello (20):
>    ACPI: platform-profile: Add a name member to handlers
>    platform/x86/dell: dell-pc: Create platform device
>    ACPI: platform_profile: Add platform handler argument to
>      platform_profile_remove()
>    ACPI: platform_profile: Move sanity check out of the mutex
>    ACPI: platform_profile: Move matching string for new profile out of
>      mutex
>    ACPI: platform_profile: Use guard(mutex) for register/unregister
>    ACPI: platform_profile: Use `scoped_cond_guard`
>    ACPI: platform_profile: Create class for ACPI platform profile
>    ACPI: platform_profile: Unregister class and sysfs group on module
>      unload
>    ACPI: platform_profile: Add name attribute to class interface
>    ACPI: platform_profile: Add choices attribute for class interface
>    ACPI: platform_profile: Add profile attribute for class interface
>    ACPI: platform_profile: Notify change events on register and
>      unregister
>    ACPI: platform_profile: Only show profiles common for all handlers
>    ACPI: platform_profile: Add concept of a "custom" profile
>    ACPI: platform_profile: Make sure all profile handlers agree on
>      profile
>    ACPI: platform_profile: Check all profile handler to calculate next
>    ACPI: platform_profile: Allow multiple handlers
>    platform/x86/amd: pmf: Drop all quirks
>    Documentation: Add documentation about class interface for platform
>      profiles
>
>   .../ABI/testing/sysfs-platform_profile        |   5 +
>   .../userspace-api/sysfs-platform_profile.rst  |  28 +
>   drivers/acpi/platform_profile.c               | 494 ++++++++++++++----
>   .../surface/surface_platform_profile.c        |   7 +-
>   drivers/platform/x86/acer-wmi.c               |   5 +-
>   drivers/platform/x86/amd/pmf/Makefile         |   2 +-
>   drivers/platform/x86/amd/pmf/core.c           |   1 -
>   drivers/platform/x86/amd/pmf/pmf-quirks.c     |  66 ---
>   drivers/platform/x86/amd/pmf/pmf.h            |   3 -
>   drivers/platform/x86/amd/pmf/sps.c            |   3 +-
>   drivers/platform/x86/asus-wmi.c               |   5 +-
>   drivers/platform/x86/dell/alienware-wmi.c     |   3 +-
>   drivers/platform/x86/dell/dell-pc.c           |  35 +-
>   drivers/platform/x86/hp/hp-wmi.c              |   3 +-
>   drivers/platform/x86/ideapad-laptop.c         |   3 +-
>   .../platform/x86/inspur_platform_profile.c    |   6 +-
>   drivers/platform/x86/thinkpad_acpi.c          |   3 +-
>   include/linux/platform_profile.h              |   6 +-
>   18 files changed, 488 insertions(+), 190 deletions(-)
>   delete mode 100644 drivers/platform/x86/amd/pmf/pmf-quirks.c
>
>
> base-commit: d68cb6023356af3bd3193983ad4ec03954a0b3e2
Mario Limonciello Nov. 7, 2024, 9:41 p.m. UTC | #4
On 11/7/2024 02:34, Armin Wolf wrote:
> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
> 
>> Reading and writing the `profile` sysfs file will use the callbacks for
>> the platform profile handler to read or set the given profile.
>>
>> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
>> ---
>> v5:
>>   * Drop recovery flow
>>   * Don't get profile before setting (not needed)
>>   * Simplify casting for call to _store_class_profile()
>>   * Only notify legacy interface of changes
>>   * Adjust mutex use
>> ---
>>   drivers/acpi/platform_profile.c | 110 ++++++++++++++++++++++++++++++++
>>   1 file changed, 110 insertions(+)
>>
>> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/ 
>> platform_profile.c
>> index 5e0bb91c5f451..35e0e8f666072 100644
>> --- a/drivers/acpi/platform_profile.c
>> +++ b/drivers/acpi/platform_profile.c
>> @@ -65,6 +65,62 @@ static int _get_class_choices(struct device *dev, 
>> unsigned long *choices)
>>       return 0;
>>   }
>>
>> +/**
>> + * _store_class_profile - Set the profile for a class device
>> + * @dev: The class device
>> + * @data: The profile to set
>> + */
>> +static int _store_class_profile(struct device *dev, void *data)
>> +{
>> +    struct platform_profile_handler *handler;
>> +    unsigned long choices;
>> +    int *i = (int *)data;
>> +    int err;
>> +
>> +    err = _get_class_choices(dev, &choices);
>> +    if (err)
>> +        return err;
>> +
>> +    lockdep_assert_held(&profile_lock);
>> +    if (!test_bit(*i, &choices))
>> +        return -EOPNOTSUPP;
>> +
>> +    handler = dev_get_drvdata(dev);
>> +    err = handler->profile_set(handler, *i);
>> +    if (err)
>> +        return err;
>> +
>> +    return err ? err : 0;
> 
> Please just return 0 here.
> 
>> +}
>> +
>> +/**
>> + * get_class_profile - Show the current profile for a class device
>> + * @dev: The class device
>> + * @profile: The profile to return
>> + * Return: 0 on success, -errno on failure
>> + */
>> +static int get_class_profile(struct device *dev,
>> +                 enum platform_profile_option *profile)
>> +{
>> +    struct platform_profile_handler *handler;
>> +    enum platform_profile_option val;
>> +    int err;
>> +
>> +    lockdep_assert_held(&profile_lock);
>> +    handler = dev_get_drvdata(dev);
>> +    err = handler->profile_get(handler, &val);
>> +    if (err) {
>> +        pr_err("Failed to get profile for handler %s\n", handler->name);
>> +        return err;
>> +    }
>> +
>> +    if (WARN_ON(val >= PLATFORM_PROFILE_LAST))
>> +        return -EINVAL;
>> +    *profile = val;
>> +
>> +    return 0;
>> +}
>> +
>>   /**
>>    * name_show - Show the name of the profile handler
>>    * @dev: The device
>> @@ -106,12 +162,66 @@ static ssize_t choices_show(struct device *dev,
>>       return _commmon_choices_show(choices, buf);
>>   }
>>
>> +/**
>> + * profile_show - Show the current profile for a class device
>> + * @dev: The device
>> + * @attr: The attribute
>> + * @buf: The buffer to write to
>> + * Return: The number of bytes written
>> + */
>> +static ssize_t profile_show(struct device *dev,
>> +                struct device_attribute *attr,
>> +                char *buf)
>> +{
>> +    enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
>> +    int err;
>> +
>> +    scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
>> +        err = get_class_profile(dev, &profile);
>> +        if (err)
>> +            return err;
>> +    }
>> +
>> +    return sysfs_emit(buf, "%s\n", profile_names[profile]);
> 
> AFAIK we do not need to take the mutex here, since querying the current 
> platform profile
> should not change any state.

I think it's still needed, in case someone attempts to unload the driver
at the same time as it's being read.  It's not static information 
because it needs to use the function pointer into the driver to get it.

This will protect from that occurring.

That's the same reason I was thinking name needed protection too.

> 
> Thanks,
> Armin Wolf
> 
>> +}
>> +
>> +/**
>> + * profile_store - Set the profile for a class device
>> + * @dev: The device
>> + * @attr: The attribute
>> + * @buf: The buffer to read from
>> + * @count: The number of bytes to read
>> + * Return: The number of bytes read
>> + */
>> +static ssize_t profile_store(struct device *dev,
>> +                 struct device_attribute *attr,
>> +                 const char *buf, size_t count)
>> +{
>> +    int i, ret;
>> +
>> +    i = sysfs_match_string(profile_names, buf);
>> +    if (i < 0)
>> +        return -EINVAL;
>> +
>> +    scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
>> +        ret = _store_class_profile(dev, &i);
>> +        if (ret)
>> +            return ret;
>> +    }
>> +
>> +    sysfs_notify(acpi_kobj, NULL, "platform_profile");
>> +
>> +    return count;
>> +}
>> +
>>   static DEVICE_ATTR_RO(name);
>>   static DEVICE_ATTR_RO(choices);
>> +static DEVICE_ATTR_RW(profile);
>>
>>   static struct attribute *profile_attrs[] = {
>>       &dev_attr_name.attr,
>>       &dev_attr_choices.attr,
>> +    &dev_attr_profile.attr,
>>       NULL
>>   };
>>   ATTRIBUTE_GROUPS(profile);
Mario Limonciello Nov. 7, 2024, 9:45 p.m. UTC | #5
On 11/7/2024 03:06, Armin Wolf wrote:
> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
> 
>> Currently there are a number of ASUS products on the market that 
>> happen to
>> have ACPI objects for amd-pmf to bind to as well as an ACPI platform
>> profile provided by asus-wmi.
>>
>> The ACPI platform profile support created by amd-pmf on these ASUS
>> products is "Function 9" which is specifically for "BIOS or EC
>> notification" of power slider position. This feature is actively used
>> by some designs such as Framework 13 and Framework 16.
>>
>> On these ASUS designs we keep on quirking more and more of them to turn
>> off this notification so that asus-wmi can bind.
>>
>> This however isn't how Windows works.  "Multiple" things are notified for
>> the power slider position. This series adjusts Linux to behave similarly.
>>
>> Multiple drivers can now register an ACPI platform profile and will react
>> to set requests.
>>
>> To avoid chaos, only positions that are common to both drivers are
>> accepted when the legacy /sys/firmware/acpi/platform_profile interface
>> is used.
>>
>> This series also adds a new concept of a "custom" profile.  This allows
>> userspace to discover that there are multiple driver handlers that are
>> configured differently.
>>
>> This series also allows dropping all of the PMF quirks from amd-pmf.
> 
> Thank you for this patch series. The overall design seems good to me, 
> but i think
> you forgot to extend platform_profile_notify().

What did you have in mind?  platform_profile_notify() is called from 
drivers and just used to notify the legacy sysfs in the event of a change.

Were you thinking it also needs to notify the class device perhaps?

> 
> Thanks,
> Armin Wolf
> 
>> ---
>> v5:
>>   * Adjust mutex handling
>>   * Add missing error handling
>>   * Drop dev member
>>   * Add cleanup handling for module unload
>>   * Fix crash on accessing legacy files after all drivers unloaded
>>
>> Mario Limonciello (20):
>>    ACPI: platform-profile: Add a name member to handlers
>>    platform/x86/dell: dell-pc: Create platform device
>>    ACPI: platform_profile: Add platform handler argument to
>>      platform_profile_remove()
>>    ACPI: platform_profile: Move sanity check out of the mutex
>>    ACPI: platform_profile: Move matching string for new profile out of
>>      mutex
>>    ACPI: platform_profile: Use guard(mutex) for register/unregister
>>    ACPI: platform_profile: Use `scoped_cond_guard`
>>    ACPI: platform_profile: Create class for ACPI platform profile
>>    ACPI: platform_profile: Unregister class and sysfs group on module
>>      unload
>>    ACPI: platform_profile: Add name attribute to class interface
>>    ACPI: platform_profile: Add choices attribute for class interface
>>    ACPI: platform_profile: Add profile attribute for class interface
>>    ACPI: platform_profile: Notify change events on register and
>>      unregister
>>    ACPI: platform_profile: Only show profiles common for all handlers
>>    ACPI: platform_profile: Add concept of a "custom" profile
>>    ACPI: platform_profile: Make sure all profile handlers agree on
>>      profile
>>    ACPI: platform_profile: Check all profile handler to calculate next
>>    ACPI: platform_profile: Allow multiple handlers
>>    platform/x86/amd: pmf: Drop all quirks
>>    Documentation: Add documentation about class interface for platform
>>      profiles
>>
>>   .../ABI/testing/sysfs-platform_profile        |   5 +
>>   .../userspace-api/sysfs-platform_profile.rst  |  28 +
>>   drivers/acpi/platform_profile.c               | 494 ++++++++++++++----
>>   .../surface/surface_platform_profile.c        |   7 +-
>>   drivers/platform/x86/acer-wmi.c               |   5 +-
>>   drivers/platform/x86/amd/pmf/Makefile         |   2 +-
>>   drivers/platform/x86/amd/pmf/core.c           |   1 -
>>   drivers/platform/x86/amd/pmf/pmf-quirks.c     |  66 ---
>>   drivers/platform/x86/amd/pmf/pmf.h            |   3 -
>>   drivers/platform/x86/amd/pmf/sps.c            |   3 +-
>>   drivers/platform/x86/asus-wmi.c               |   5 +-
>>   drivers/platform/x86/dell/alienware-wmi.c     |   3 +-
>>   drivers/platform/x86/dell/dell-pc.c           |  35 +-
>>   drivers/platform/x86/hp/hp-wmi.c              |   3 +-
>>   drivers/platform/x86/ideapad-laptop.c         |   3 +-
>>   .../platform/x86/inspur_platform_profile.c    |   6 +-
>>   drivers/platform/x86/thinkpad_acpi.c          |   3 +-
>>   include/linux/platform_profile.h              |   6 +-
>>   18 files changed, 488 insertions(+), 190 deletions(-)
>>   delete mode 100644 drivers/platform/x86/amd/pmf/pmf-quirks.c
>>
>>
>> base-commit: d68cb6023356af3bd3193983ad4ec03954a0b3e2
Mario Limonciello Nov. 7, 2024, 10:05 p.m. UTC | #6
On 11/7/2024 02:58, Armin Wolf wrote:
> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
> 
>> As multiple platform profile handlers might not all support the same
>> profile, cycling to the next profile could have a different result
>> depending on what handler are registered.
>>
>> Check what is active and supported by all handlers to decide what
>> to do.
>>
>> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
>> ---
>> v5:
>>   * Adjust mutex use
>> ---
>>   drivers/acpi/platform_profile.c | 23 ++++++++++++++---------
>>   1 file changed, 14 insertions(+), 9 deletions(-)
>>
>> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/ 
>> platform_profile.c
>> index 7f302ac4d3779..2c466f2d16b42 100644
>> --- a/drivers/acpi/platform_profile.c
>> +++ b/drivers/acpi/platform_profile.c
>> @@ -411,34 +411,39 @@ EXPORT_SYMBOL_GPL(platform_profile_notify);
>>
>>   int platform_profile_cycle(void)
>>   {
>> +    enum platform_profile_option next = PLATFORM_PROFILE_LAST;
>>       enum platform_profile_option profile;
>> -    enum platform_profile_option next;
>> +    unsigned long choices;
>>       int err;
>>
>>       if (!class_is_registered(&platform_profile_class))
>>           return -ENODEV;
>>
>>       scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
>> -        if (!cur_profile)
>> -            return -ENODEV;
>> +        err = class_for_each_device(&platform_profile_class, NULL,
>> +                        &profile, _aggregate_profiles);
>> +        if (err)
>> +            return err;
>>
>> -        err = cur_profile->profile_get(cur_profile, &profile);
>> +        err = class_for_each_device(&platform_profile_class, NULL,
>> +                        &choices, _aggregate_choices);
>>           if (err)
>>               return err;
>>
>> -        next = find_next_bit_wrap(cur_profile->choices, 
>> PLATFORM_PROFILE_LAST,
>> +        next = find_next_bit_wrap(&choices,
>> +                      PLATFORM_PROFILE_LAST,
>>                         profile + 1);
> 
> Could it be that this would lead to be "custom" profile being selected 
> under some conditions?

Yeah, you're right.  If all drivers supported custom then this could 
happen.  I'll clear custom like this:

		choices &= ~BIT(PLATFORM_PROFILE_CUSTOM);

> Also _aggregate_profiles() expects profile to be initialized with 
> PLATFORM_PROFILE_LAST.

Will correct initialization in platform_profile_cycle() to this.

	enum platform_profile_option profile = PLATFORM_PROFILE_LAST;

But this also raises a good point.  If _aggregate_profiles() returns
custom then this should be an error because next profile is undefined.
So I'll catch that like this.
		err = class_for_each_device()
		if (err)
			return err;
		if (profile == PLATFORM_PROFILE_CUSTOM)
			return -EINVAL;
> 
> Thanks,
> Armin Wolf
> 
>>
>> -        if (WARN_ON(next == PLATFORM_PROFILE_LAST))
>> -            return -EINVAL;
>> +        err = class_for_each_device(&platform_profile_class, NULL, 
>> &next,
>> +                        _store_class_profile);
>>
>> -        err = cur_profile->profile_set(cur_profile, next);
>>           if (err)
>>               return err;
>>       }
>>
>>       sysfs_notify(acpi_kobj, NULL, "platform_profile");
>> -    return 0;
>> +
>> +    return err;
>>   }
>>   EXPORT_SYMBOL_GPL(platform_profile_cycle);
>>
Armin Wolf Nov. 8, 2024, 6 p.m. UTC | #7
Am 07.11.24 um 22:41 schrieb Mario Limonciello:

> On 11/7/2024 02:34, Armin Wolf wrote:
>> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
>>
>>> Reading and writing the `profile` sysfs file will use the callbacks for
>>> the platform profile handler to read or set the given profile.
>>>
>>> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>>> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
>>> ---
>>> v5:
>>>   * Drop recovery flow
>>>   * Don't get profile before setting (not needed)
>>>   * Simplify casting for call to _store_class_profile()
>>>   * Only notify legacy interface of changes
>>>   * Adjust mutex use
>>> ---
>>>   drivers/acpi/platform_profile.c | 110
>>> ++++++++++++++++++++++++++++++++
>>>   1 file changed, 110 insertions(+)
>>>
>>> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/
>>> platform_profile.c
>>> index 5e0bb91c5f451..35e0e8f666072 100644
>>> --- a/drivers/acpi/platform_profile.c
>>> +++ b/drivers/acpi/platform_profile.c
>>> @@ -65,6 +65,62 @@ static int _get_class_choices(struct device *dev,
>>> unsigned long *choices)
>>>       return 0;
>>>   }
>>>
>>> +/**
>>> + * _store_class_profile - Set the profile for a class device
>>> + * @dev: The class device
>>> + * @data: The profile to set
>>> + */
>>> +static int _store_class_profile(struct device *dev, void *data)
>>> +{
>>> +    struct platform_profile_handler *handler;
>>> +    unsigned long choices;
>>> +    int *i = (int *)data;
>>> +    int err;
>>> +
>>> +    err = _get_class_choices(dev, &choices);
>>> +    if (err)
>>> +        return err;
>>> +
>>> +    lockdep_assert_held(&profile_lock);
>>> +    if (!test_bit(*i, &choices))
>>> +        return -EOPNOTSUPP;
>>> +
>>> +    handler = dev_get_drvdata(dev);
>>> +    err = handler->profile_set(handler, *i);
>>> +    if (err)
>>> +        return err;
>>> +
>>> +    return err ? err : 0;
>>
>> Please just return 0 here.
>>
>>> +}
>>> +
>>> +/**
>>> + * get_class_profile - Show the current profile for a class device
>>> + * @dev: The class device
>>> + * @profile: The profile to return
>>> + * Return: 0 on success, -errno on failure
>>> + */
>>> +static int get_class_profile(struct device *dev,
>>> +                 enum platform_profile_option *profile)
>>> +{
>>> +    struct platform_profile_handler *handler;
>>> +    enum platform_profile_option val;
>>> +    int err;
>>> +
>>> +    lockdep_assert_held(&profile_lock);
>>> +    handler = dev_get_drvdata(dev);
>>> +    err = handler->profile_get(handler, &val);
>>> +    if (err) {
>>> +        pr_err("Failed to get profile for handler %s\n",
>>> handler->name);
>>> +        return err;
>>> +    }
>>> +
>>> +    if (WARN_ON(val >= PLATFORM_PROFILE_LAST))
>>> +        return -EINVAL;
>>> +    *profile = val;
>>> +
>>> +    return 0;
>>> +}
>>> +
>>>   /**
>>>    * name_show - Show the name of the profile handler
>>>    * @dev: The device
>>> @@ -106,12 +162,66 @@ static ssize_t choices_show(struct device *dev,
>>>       return _commmon_choices_show(choices, buf);
>>>   }
>>>
>>> +/**
>>> + * profile_show - Show the current profile for a class device
>>> + * @dev: The device
>>> + * @attr: The attribute
>>> + * @buf: The buffer to write to
>>> + * Return: The number of bytes written
>>> + */
>>> +static ssize_t profile_show(struct device *dev,
>>> +                struct device_attribute *attr,
>>> +                char *buf)
>>> +{
>>> +    enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
>>> +    int err;
>>> +
>>> +    scoped_cond_guard(mutex_intr, return -ERESTARTSYS,
>>> &profile_lock) {
>>> +        err = get_class_profile(dev, &profile);
>>> +        if (err)
>>> +            return err;
>>> +    }
>>> +
>>> +    return sysfs_emit(buf, "%s\n", profile_names[profile]);
>>
>> AFAIK we do not need to take the mutex here, since querying the
>> current platform profile
>> should not change any state.
>
> I think it's still needed, in case someone attempts to unload the driver
> at the same time as it's being read.  It's not static information
> because it needs to use the function pointer into the driver to get it.
>
> This will protect from that occurring.
>
> That's the same reason I was thinking name needed protection too.
>
I see, good point.

Thanks,
Armin Wolf

>>
>> Thanks,
>> Armin Wolf
>>
>>> +}
>>> +
>>> +/**
>>> + * profile_store - Set the profile for a class device
>>> + * @dev: The device
>>> + * @attr: The attribute
>>> + * @buf: The buffer to read from
>>> + * @count: The number of bytes to read
>>> + * Return: The number of bytes read
>>> + */
>>> +static ssize_t profile_store(struct device *dev,
>>> +                 struct device_attribute *attr,
>>> +                 const char *buf, size_t count)
>>> +{
>>> +    int i, ret;
>>> +
>>> +    i = sysfs_match_string(profile_names, buf);
>>> +    if (i < 0)
>>> +        return -EINVAL;
>>> +
>>> +    scoped_cond_guard(mutex_intr, return -ERESTARTSYS,
>>> &profile_lock) {
>>> +        ret = _store_class_profile(dev, &i);
>>> +        if (ret)
>>> +            return ret;
>>> +    }
>>> +
>>> +    sysfs_notify(acpi_kobj, NULL, "platform_profile");
>>> +
>>> +    return count;
>>> +}
>>> +
>>>   static DEVICE_ATTR_RO(name);
>>>   static DEVICE_ATTR_RO(choices);
>>> +static DEVICE_ATTR_RW(profile);
>>>
>>>   static struct attribute *profile_attrs[] = {
>>>       &dev_attr_name.attr,
>>>       &dev_attr_choices.attr,
>>> +    &dev_attr_profile.attr,
>>>       NULL
>>>   };
>>>   ATTRIBUTE_GROUPS(profile);
>
Armin Wolf Nov. 8, 2024, 6:10 p.m. UTC | #8
Am 07.11.24 um 23:05 schrieb Mario Limonciello:

> On 11/7/2024 02:58, Armin Wolf wrote:
>> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
>>
>>> As multiple platform profile handlers might not all support the same
>>> profile, cycling to the next profile could have a different result
>>> depending on what handler are registered.
>>>
>>> Check what is active and supported by all handlers to decide what
>>> to do.
>>>
>>> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>>> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
>>> ---
>>> v5:
>>>   * Adjust mutex use
>>> ---
>>>   drivers/acpi/platform_profile.c | 23 ++++++++++++++---------
>>>   1 file changed, 14 insertions(+), 9 deletions(-)
>>>
>>> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/ 
>>> platform_profile.c
>>> index 7f302ac4d3779..2c466f2d16b42 100644
>>> --- a/drivers/acpi/platform_profile.c
>>> +++ b/drivers/acpi/platform_profile.c
>>> @@ -411,34 +411,39 @@ EXPORT_SYMBOL_GPL(platform_profile_notify);
>>>
>>>   int platform_profile_cycle(void)
>>>   {
>>> +    enum platform_profile_option next = PLATFORM_PROFILE_LAST;
>>>       enum platform_profile_option profile;
>>> -    enum platform_profile_option next;
>>> +    unsigned long choices;
>>>       int err;
>>>
>>>       if (!class_is_registered(&platform_profile_class))
>>>           return -ENODEV;
>>>
>>>       scoped_cond_guard(mutex_intr, return -ERESTARTSYS, 
>>> &profile_lock) {
>>> -        if (!cur_profile)
>>> -            return -ENODEV;
>>> +        err = class_for_each_device(&platform_profile_class, NULL,
>>> +                        &profile, _aggregate_profiles);
>>> +        if (err)
>>> +            return err;
>>>
>>> -        err = cur_profile->profile_get(cur_profile, &profile);
>>> +        err = class_for_each_device(&platform_profile_class, NULL,
>>> +                        &choices, _aggregate_choices);
>>>           if (err)
>>>               return err;
>>>
>>> -        next = find_next_bit_wrap(cur_profile->choices, 
>>> PLATFORM_PROFILE_LAST,
>>> +        next = find_next_bit_wrap(&choices,
>>> +                      PLATFORM_PROFILE_LAST,
>>>                         profile + 1);
>>
>> Could it be that this would lead to be "custom" profile being 
>> selected under some conditions?
>
> Yeah, you're right.  If all drivers supported custom then this could 
> happen.  I'll clear custom like this:
>
>         choices &= ~BIT(PLATFORM_PROFILE_CUSTOM);
>
Sound good to me.

>> Also _aggregate_profiles() expects profile to be initialized with 
>> PLATFORM_PROFILE_LAST.
>
> Will correct initialization in platform_profile_cycle() to this.
>
>     enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
>
> But this also raises a good point.  If _aggregate_profiles() returns
> custom then this should be an error because next profile is undefined.
> So I'll catch that like this.
>         err = class_for_each_device()
>         if (err)
>             return err;
>         if (profile == PLATFORM_PROFILE_CUSTOM)
>             return -EINVAL;

Good point, please also check if profile == PLATFORM_PROFILE_LAST in case no platform profile handlers are currently installed.

Thanks,
Armin Wol

>>
>> Thanks,
>> Armin Wolf
>>
>>>
>>> -        if (WARN_ON(next == PLATFORM_PROFILE_LAST))
>>> -            return -EINVAL;
>>> +        err = class_for_each_device(&platform_profile_class, NULL, 
>>> &next,
>>> +                        _store_class_profile);
>>>
>>> -        err = cur_profile->profile_set(cur_profile, next);
>>>           if (err)
>>>               return err;
>>>       }
>>>
>>>       sysfs_notify(acpi_kobj, NULL, "platform_profile");
>>> -    return 0;
>>> +
>>> +    return err;
>>>   }
>>>   EXPORT_SYMBOL_GPL(platform_profile_cycle);
>>>
>
>
Armin Wolf Nov. 8, 2024, 6:13 p.m. UTC | #9
Am 07.11.24 um 22:45 schrieb Mario Limonciello:

> On 11/7/2024 03:06, Armin Wolf wrote:
>> Am 07.11.24 um 07:02 schrieb Mario Limonciello:
>>
>>> Currently there are a number of ASUS products on the market that
>>> happen to
>>> have ACPI objects for amd-pmf to bind to as well as an ACPI platform
>>> profile provided by asus-wmi.
>>>
>>> The ACPI platform profile support created by amd-pmf on these ASUS
>>> products is "Function 9" which is specifically for "BIOS or EC
>>> notification" of power slider position. This feature is actively used
>>> by some designs such as Framework 13 and Framework 16.
>>>
>>> On these ASUS designs we keep on quirking more and more of them to turn
>>> off this notification so that asus-wmi can bind.
>>>
>>> This however isn't how Windows works.  "Multiple" things are
>>> notified for
>>> the power slider position. This series adjusts Linux to behave
>>> similarly.
>>>
>>> Multiple drivers can now register an ACPI platform profile and will
>>> react
>>> to set requests.
>>>
>>> To avoid chaos, only positions that are common to both drivers are
>>> accepted when the legacy /sys/firmware/acpi/platform_profile interface
>>> is used.
>>>
>>> This series also adds a new concept of a "custom" profile. This allows
>>> userspace to discover that there are multiple driver handlers that are
>>> configured differently.
>>>
>>> This series also allows dropping all of the PMF quirks from amd-pmf.
>>
>> Thank you for this patch series. The overall design seems good to me,
>> but i think
>> you forgot to extend platform_profile_notify().
>
> What did you have in mind?  platform_profile_notify() is called from
> drivers and just used to notify the legacy sysfs in the event of a
> change.
>
> Were you thinking it also needs to notify the class device perhaps?
>
If platform_profile_notify() only notifies the legacy sysfs interface, then userspace applications are forced to continue using the legacy sysfs interface
for receiving notifications.

Thanks,
Armin Wolf

>>
>> Thanks,
>> Armin Wolf
>>
>>> ---
>>> v5:
>>>   * Adjust mutex handling
>>>   * Add missing error handling
>>>   * Drop dev member
>>>   * Add cleanup handling for module unload
>>>   * Fix crash on accessing legacy files after all drivers unloaded
>>>
>>> Mario Limonciello (20):
>>>    ACPI: platform-profile: Add a name member to handlers
>>>    platform/x86/dell: dell-pc: Create platform device
>>>    ACPI: platform_profile: Add platform handler argument to
>>>      platform_profile_remove()
>>>    ACPI: platform_profile: Move sanity check out of the mutex
>>>    ACPI: platform_profile: Move matching string for new profile out of
>>>      mutex
>>>    ACPI: platform_profile: Use guard(mutex) for register/unregister
>>>    ACPI: platform_profile: Use `scoped_cond_guard`
>>>    ACPI: platform_profile: Create class for ACPI platform profile
>>>    ACPI: platform_profile: Unregister class and sysfs group on module
>>>      unload
>>>    ACPI: platform_profile: Add name attribute to class interface
>>>    ACPI: platform_profile: Add choices attribute for class interface
>>>    ACPI: platform_profile: Add profile attribute for class interface
>>>    ACPI: platform_profile: Notify change events on register and
>>>      unregister
>>>    ACPI: platform_profile: Only show profiles common for all handlers
>>>    ACPI: platform_profile: Add concept of a "custom" profile
>>>    ACPI: platform_profile: Make sure all profile handlers agree on
>>>      profile
>>>    ACPI: platform_profile: Check all profile handler to calculate next
>>>    ACPI: platform_profile: Allow multiple handlers
>>>    platform/x86/amd: pmf: Drop all quirks
>>>    Documentation: Add documentation about class interface for platform
>>>      profiles
>>>
>>>   .../ABI/testing/sysfs-platform_profile        |   5 +
>>>   .../userspace-api/sysfs-platform_profile.rst  |  28 +
>>>   drivers/acpi/platform_profile.c               | 494
>>> ++++++++++++++----
>>>   .../surface/surface_platform_profile.c        |   7 +-
>>>   drivers/platform/x86/acer-wmi.c               |   5 +-
>>>   drivers/platform/x86/amd/pmf/Makefile         |   2 +-
>>>   drivers/platform/x86/amd/pmf/core.c           |   1 -
>>>   drivers/platform/x86/amd/pmf/pmf-quirks.c     |  66 ---
>>>   drivers/platform/x86/amd/pmf/pmf.h            |   3 -
>>>   drivers/platform/x86/amd/pmf/sps.c            |   3 +-
>>>   drivers/platform/x86/asus-wmi.c               |   5 +-
>>>   drivers/platform/x86/dell/alienware-wmi.c     |   3 +-
>>>   drivers/platform/x86/dell/dell-pc.c           |  35 +-
>>>   drivers/platform/x86/hp/hp-wmi.c              |   3 +-
>>>   drivers/platform/x86/ideapad-laptop.c         |   3 +-
>>>   .../platform/x86/inspur_platform_profile.c    |   6 +-
>>>   drivers/platform/x86/thinkpad_acpi.c          |   3 +-
>>>   include/linux/platform_profile.h              |   6 +-
>>>   18 files changed, 488 insertions(+), 190 deletions(-)
>>>   delete mode 100644 drivers/platform/x86/amd/pmf/pmf-quirks.c
>>>
>>>
>>> base-commit: d68cb6023356af3bd3193983ad4ec03954a0b3e2
>
>