[PATCH v6 10/22] ACPI: platform_profile: Create class for ACPI platform profile

Mario Limonciello posted 22 patches 2 weeks ago
There is a newer version of this series
[PATCH v6 10/22] ACPI: platform_profile: Create class for ACPI platform profile
Posted by Mario Limonciello 2 weeks ago
When registering a platform profile handler create a class device
that will allow changing a single platform profile handler.

The class and sysfs group are no longer needed when the platform profile
core is a module and unloaded, so remove them at that time as well.

Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
---
v6:
 * Catch failures in ida_alloc
 * Use 4th argument of device_create instead of dev_set_drvdata()
 * Squash unregister patch
 * Add module init callback
 * Move class creation to module init
 * Update visibility based on group presence
 * Add back parent device
v5:
 * Use ida instead of idr
 * Use device_unregister instead of device_destroy()
 * MKDEV (0, 0)
---
 drivers/acpi/platform_profile.c  | 88 ++++++++++++++++++++++++++++++--
 include/linux/platform_profile.h |  2 +
 2 files changed, 85 insertions(+), 5 deletions(-)

diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c
index 32affb75e782d..ef6af2c655524 100644
--- a/drivers/acpi/platform_profile.c
+++ b/drivers/acpi/platform_profile.c
@@ -5,6 +5,7 @@
 #include <linux/acpi.h>
 #include <linux/bits.h>
 #include <linux/init.h>
+#include <linux/kdev_t.h>
 #include <linux/mutex.h>
 #include <linux/platform_profile.h>
 #include <linux/sysfs.h>
@@ -22,6 +23,12 @@ static const char * const profile_names[] = {
 };
 static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
 
+static DEFINE_IDA(platform_profile_ida);
+
+static const struct class platform_profile_class = {
+	.name = "platform-profile",
+};
+
 static ssize_t platform_profile_choices_show(struct device *dev,
 					struct device_attribute *attr,
 					char *buf)
@@ -105,8 +112,25 @@ 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)
+{
+	if (!class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered))
+		return 0;
+	if (attr == &dev_attr_platform_profile_choices.attr)
+		return 0444;
+	if (attr == &dev_attr_platform_profile.attr)
+		return 0644;
+	return 0;
+}
+
 static const struct attribute_group platform_profile_group = {
-	.attrs = platform_profile_attrs
+	.attrs = platform_profile_attrs,
+	.is_visible = profile_class_is_visible,
 };
 
 void platform_profile_notify(struct platform_profile_handler *pprof)
@@ -123,6 +147,9 @@ int platform_profile_cycle(void)
 	enum platform_profile_option next;
 	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;
@@ -164,25 +191,76 @@ int platform_profile_register(struct platform_profile_handler *pprof)
 	if (cur_profile)
 		return -EEXIST;
 
-	err = sysfs_create_group(acpi_kobj, &platform_profile_group);
-	if (err)
-		return err;
+	/* create class interface for individual handler */
+	pprof->minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
+	if (pprof->minor < 0)
+		return pprof->minor;
+	pprof->class_dev = device_create(&platform_profile_class, pprof->dev,
+					 MKDEV(0, 0), pprof, "platform-profile-%d",
+					 pprof->minor);
+	if (IS_ERR(pprof->class_dev)) {
+		err = PTR_ERR(pprof->class_dev);
+		goto cleanup_ida;
+	}
 
 	cur_profile = pprof;
+
+	err = sysfs_update_group(acpi_kobj, &platform_profile_group);
+	if (err)
+		goto cleanup_cur;
+
 	return 0;
+
+cleanup_cur:
+	cur_profile = NULL;
+	device_unregister(pprof->class_dev);
+
+cleanup_ida:
+	ida_free(&platform_profile_ida, pprof->minor);
+
+	return err;
 }
 EXPORT_SYMBOL_GPL(platform_profile_register);
 
 int platform_profile_remove(struct platform_profile_handler *pprof)
 {
+	int id;
 	guard(mutex)(&profile_lock);
 
-	sysfs_remove_group(acpi_kobj, &platform_profile_group);
+	id = pprof->minor;
+	device_unregister(pprof->class_dev);
+	ida_free(&platform_profile_ida, id);
+
 	cur_profile = NULL;
+
+	sysfs_update_group(acpi_kobj, &platform_profile_group);
+
 	return 0;
 }
 EXPORT_SYMBOL_GPL(platform_profile_remove);
 
+static int __init platform_profile_init(void)
+{
+	int err;
+
+	err = class_register(&platform_profile_class);
+	if (err)
+		return err;
+
+	err = sysfs_create_group(acpi_kobj, &platform_profile_group);
+	if (err)
+		class_unregister(&platform_profile_class);
+
+	return err;
+}
+static void __exit platform_profile_exit(void)
+{
+	class_unregister(&platform_profile_class);
+	sysfs_remove_group(acpi_kobj, &platform_profile_group);
+}
+module_init(platform_profile_init);
+module_exit(platform_profile_exit);
+
 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
 MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
 MODULE_LICENSE("GPL");
diff --git a/include/linux/platform_profile.h b/include/linux/platform_profile.h
index 8ec0b8da56db5..a888fd085c513 100644
--- a/include/linux/platform_profile.h
+++ b/include/linux/platform_profile.h
@@ -29,6 +29,8 @@ enum platform_profile_option {
 struct platform_profile_handler {
 	const char *name;
 	struct device *dev;
+	struct device *class_dev;
+	int minor;
 	unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
 	int (*profile_get)(struct platform_profile_handler *pprof,
 				enum platform_profile_option *profile);
-- 
2.43.0
Re: [PATCH v6 10/22] ACPI: platform_profile: Create class for ACPI platform profile
Posted by Armin Wolf 6 days, 2 hours ago
Am 09.11.24 um 05:41 schrieb Mario Limonciello:

> When registering a platform profile handler create a class device
> that will allow changing a single platform profile handler.
>
> The class and sysfs group are no longer needed when the platform profile
> core is a module and unloaded, so remove them at that time as well.
>
> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
> ---
> v6:
>   * Catch failures in ida_alloc
>   * Use 4th argument of device_create instead of dev_set_drvdata()
>   * Squash unregister patch
>   * Add module init callback
>   * Move class creation to module init
>   * Update visibility based on group presence
>   * Add back parent device
> v5:
>   * Use ida instead of idr
>   * Use device_unregister instead of device_destroy()
>   * MKDEV (0, 0)
> ---
>   drivers/acpi/platform_profile.c  | 88 ++++++++++++++++++++++++++++++--
>   include/linux/platform_profile.h |  2 +
>   2 files changed, 85 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c
> index 32affb75e782d..ef6af2c655524 100644
> --- a/drivers/acpi/platform_profile.c
> +++ b/drivers/acpi/platform_profile.c
> @@ -5,6 +5,7 @@
>   #include <linux/acpi.h>
>   #include <linux/bits.h>
>   #include <linux/init.h>
> +#include <linux/kdev_t.h>
>   #include <linux/mutex.h>
>   #include <linux/platform_profile.h>
>   #include <linux/sysfs.h>
> @@ -22,6 +23,12 @@ static const char * const profile_names[] = {
>   };
>   static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
>
> +static DEFINE_IDA(platform_profile_ida);
> +
> +static const struct class platform_profile_class = {
> +	.name = "platform-profile",
> +};
> +
>   static ssize_t platform_profile_choices_show(struct device *dev,
>   					struct device_attribute *attr,
>   					char *buf)
> @@ -105,8 +112,25 @@ 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)
> +{
> +	if (!class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered))
> +		return 0;
> +	if (attr == &dev_attr_platform_profile_choices.attr)
> +		return 0444;
> +	if (attr == &dev_attr_platform_profile.attr)
> +		return 0644;
> +	return 0;
> +}
> +
>   static const struct attribute_group platform_profile_group = {
> -	.attrs = platform_profile_attrs
> +	.attrs = platform_profile_attrs,
> +	.is_visible = profile_class_is_visible,
>   };
>
>   void platform_profile_notify(struct platform_profile_handler *pprof)
> @@ -123,6 +147,9 @@ int platform_profile_cycle(void)
>   	enum platform_profile_option next;
>   	int err;
>
> +	if (!class_is_registered(&platform_profile_class))
> +		return -ENODEV;

This check is pointless since the platform profile class will always be registered during module initialization.
Please remove it.

> +
>   	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
>   		if (!cur_profile)
>   			return -ENODEV;
> @@ -164,25 +191,76 @@ int platform_profile_register(struct platform_profile_handler *pprof)
>   	if (cur_profile)
>   		return -EEXIST;
>
> -	err = sysfs_create_group(acpi_kobj, &platform_profile_group);
> -	if (err)
> -		return err;
> +	/* create class interface for individual handler */
> +	pprof->minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
> +	if (pprof->minor < 0)
> +		return pprof->minor;
> +	pprof->class_dev = device_create(&platform_profile_class, pprof->dev,
> +					 MKDEV(0, 0), pprof, "platform-profile-%d",
> +					 pprof->minor);
> +	if (IS_ERR(pprof->class_dev)) {
> +		err = PTR_ERR(pprof->class_dev);
> +		goto cleanup_ida;
> +	}
>
>   	cur_profile = pprof;
> +
> +	err = sysfs_update_group(acpi_kobj, &platform_profile_group);
> +	if (err)
> +		goto cleanup_cur;
> +
>   	return 0;
> +
> +cleanup_cur:
> +	cur_profile = NULL;
> +	device_unregister(pprof->class_dev);
> +
> +cleanup_ida:
> +	ida_free(&platform_profile_ida, pprof->minor);
> +
> +	return err;
>   }
>   EXPORT_SYMBOL_GPL(platform_profile_register);
>
>   int platform_profile_remove(struct platform_profile_handler *pprof)
>   {
> +	int id;
>   	guard(mutex)(&profile_lock);
>
> -	sysfs_remove_group(acpi_kobj, &platform_profile_group);
> +	id = pprof->minor;
> +	device_unregister(pprof->class_dev);
> +	ida_free(&platform_profile_ida, id);
> +
>   	cur_profile = NULL;
> +
> +	sysfs_update_group(acpi_kobj, &platform_profile_group);
> +
>   	return 0;
>   }
>   EXPORT_SYMBOL_GPL(platform_profile_remove);
>
> +static int __init platform_profile_init(void)
> +{
> +	int err;
> +
> +	err = class_register(&platform_profile_class);
> +	if (err)
> +		return err;
> +
> +	err = sysfs_create_group(acpi_kobj, &platform_profile_group);
> +	if (err)
> +		class_unregister(&platform_profile_class);
> +
> +	return err;
> +}

Please use a blank line after function/struct/union/enum declarations.

Apart from those minor issues the patch looks quite nice, so with those issues being fixed:

Reviewed-by: Armin Wolf <W_Armin@gmx.de>

> +static void __exit platform_profile_exit(void)
> +{
> +	class_unregister(&platform_profile_class);
> +	sysfs_remove_group(acpi_kobj, &platform_profile_group);
> +}
> +module_init(platform_profile_init);
> +module_exit(platform_profile_exit);
> +
>   MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
>   MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
>   MODULE_LICENSE("GPL");
> diff --git a/include/linux/platform_profile.h b/include/linux/platform_profile.h
> index 8ec0b8da56db5..a888fd085c513 100644
> --- a/include/linux/platform_profile.h
> +++ b/include/linux/platform_profile.h
> @@ -29,6 +29,8 @@ enum platform_profile_option {
>   struct platform_profile_handler {
>   	const char *name;
>   	struct device *dev;
> +	struct device *class_dev;
> +	int minor;
>   	unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
>   	int (*profile_get)(struct platform_profile_handler *pprof,
>   				enum platform_profile_option *profile);
Re: [PATCH v6 10/22] ACPI: platform_profile: Create class for ACPI platform profile
Posted by Armin Wolf 4 days, 9 hours ago
Am 17.11.24 um 20:11 schrieb Armin Wolf:

> Am 09.11.24 um 05:41 schrieb Mario Limonciello:
>
>> When registering a platform profile handler create a class device
>> that will allow changing a single platform profile handler.
>>
>> The class and sysfs group are no longer needed when the platform profile
>> core is a module and unloaded, so remove them at that time as well.
>>
>> Tested-by: Mark Pearson <mpearson-lenovo@squebb.ca>
>> Signed-off-by: Mario Limonciello <mario.limonciello@amd.com>
>> ---
>> v6:
>>   * Catch failures in ida_alloc
>>   * Use 4th argument of device_create instead of dev_set_drvdata()
>>   * Squash unregister patch
>>   * Add module init callback
>>   * Move class creation to module init
>>   * Update visibility based on group presence
>>   * Add back parent device
>> v5:
>>   * Use ida instead of idr
>>   * Use device_unregister instead of device_destroy()
>>   * MKDEV (0, 0)
>> ---
>>   drivers/acpi/platform_profile.c  | 88 ++++++++++++++++++++++++++++++--
>>   include/linux/platform_profile.h |  2 +
>>   2 files changed, 85 insertions(+), 5 deletions(-)
>>
>> diff --git a/drivers/acpi/platform_profile.c
>> b/drivers/acpi/platform_profile.c
>> index 32affb75e782d..ef6af2c655524 100644
>> --- a/drivers/acpi/platform_profile.c
>> +++ b/drivers/acpi/platform_profile.c
>> @@ -5,6 +5,7 @@
>>   #include <linux/acpi.h>
>>   #include <linux/bits.h>
>>   #include <linux/init.h>
>> +#include <linux/kdev_t.h>
>>   #include <linux/mutex.h>
>>   #include <linux/platform_profile.h>
>>   #include <linux/sysfs.h>
>> @@ -22,6 +23,12 @@ static const char * const profile_names[] = {
>>   };
>>   static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
>>
>> +static DEFINE_IDA(platform_profile_ida);
>> +
>> +static const struct class platform_profile_class = {
>> +    .name = "platform-profile",
>> +};
>> +
>>   static ssize_t platform_profile_choices_show(struct device *dev,
>>                       struct device_attribute *attr,
>>                       char *buf)
>> @@ -105,8 +112,25 @@ 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)
>> +{
>> +    if (!class_find_device(&platform_profile_class, NULL, NULL,
>> profile_class_registered))
>> +        return 0;
>> +    if (attr == &dev_attr_platform_profile_choices.attr)
>> +        return 0444;
>> +    if (attr == &dev_attr_platform_profile.attr)
>> +        return 0644;
>> +    return 0;
>> +}
>> +
>>   static const struct attribute_group platform_profile_group = {
>> -    .attrs = platform_profile_attrs
>> +    .attrs = platform_profile_attrs,
>> +    .is_visible = profile_class_is_visible,
>>   };
>>
>>   void platform_profile_notify(struct platform_profile_handler *pprof)
>> @@ -123,6 +147,9 @@ int platform_profile_cycle(void)
>>       enum platform_profile_option next;
>>       int err;
>>
>> +    if (!class_is_registered(&platform_profile_class))
>> +        return -ENODEV;
>
> This check is pointless since the platform profile class will always
> be registered during module initialization.
> Please remove it.
>
>> +
>>       scoped_cond_guard(mutex_intr, return -ERESTARTSYS,
>> &profile_lock) {
>>           if (!cur_profile)
>>               return -ENODEV;
>> @@ -164,25 +191,76 @@ int platform_profile_register(struct
>> platform_profile_handler *pprof)
>>       if (cur_profile)
>>           return -EEXIST;
>>
>> -    err = sysfs_create_group(acpi_kobj, &platform_profile_group);
>> -    if (err)
>> -        return err;
>> +    /* create class interface for individual handler */
>> +    pprof->minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
>> +    if (pprof->minor < 0)
>> +        return pprof->minor;
>> +    pprof->class_dev = device_create(&platform_profile_class,
>> pprof->dev,
>> +                     MKDEV(0, 0), pprof, "platform-profile-%d",
>> +                     pprof->minor);
>> +    if (IS_ERR(pprof->class_dev)) {
>> +        err = PTR_ERR(pprof->class_dev);
>> +        goto cleanup_ida;
>> +    }
>>
>>       cur_profile = pprof;
>> +
>> +    err = sysfs_update_group(acpi_kobj, &platform_profile_group);
>> +    if (err)
>> +        goto cleanup_cur;
>> +
>>       return 0;
>> +
>> +cleanup_cur:
>> +    cur_profile = NULL;
>> +    device_unregister(pprof->class_dev);
>> +
>> +cleanup_ida:
>> +    ida_free(&platform_profile_ida, pprof->minor);
>> +
>> +    return err;
>>   }
>>   EXPORT_SYMBOL_GPL(platform_profile_register);
>>
>>   int platform_profile_remove(struct platform_profile_handler *pprof)
>>   {
>> +    int id;
>>       guard(mutex)(&profile_lock);
>>
>> -    sysfs_remove_group(acpi_kobj, &platform_profile_group);
>> +    id = pprof->minor;
>> +    device_unregister(pprof->class_dev);
>> +    ida_free(&platform_profile_ida, id);
>> +
>>       cur_profile = NULL;
>> +
>> +    sysfs_update_group(acpi_kobj, &platform_profile_group);
>> +
>>       return 0;
>>   }
>>   EXPORT_SYMBOL_GPL(platform_profile_remove);
>>
>> +static int __init platform_profile_init(void)
>> +{
>> +    int err;
>> +
>> +    err = class_register(&platform_profile_class);
>> +    if (err)
>> +        return err;
>> +
>> +    err = sysfs_create_group(acpi_kobj, &platform_profile_group);
>> +    if (err)
>> +        class_unregister(&platform_profile_class);
>> +
>> +    return err;
>> +}
>
> Please use a blank line after function/struct/union/enum declarations.
>
> Apart from those minor issues the patch looks quite nice, so with
> those issues being fixed:
>
> Reviewed-by: Armin Wolf <W_Armin@gmx.de>
>
>> +static void __exit platform_profile_exit(void)
>> +{
>> +    class_unregister(&platform_profile_class);
>> +    sysfs_remove_group(acpi_kobj, &platform_profile_group);

I just noticed that the legacy sysfs group needs to be removed before the class, otherwise
there might be a short time window during module exit where the legacy sysfs interface might
try to use the already unregistered class.

Thanks,
Armin Wolf

>> +}
>> +module_init(platform_profile_init);
>> +module_exit(platform_profile_exit);
>> +
>>   MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
>>   MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
>>   MODULE_LICENSE("GPL");
>> diff --git a/include/linux/platform_profile.h
>> b/include/linux/platform_profile.h
>> index 8ec0b8da56db5..a888fd085c513 100644
>> --- a/include/linux/platform_profile.h
>> +++ b/include/linux/platform_profile.h
>> @@ -29,6 +29,8 @@ enum platform_profile_option {
>>   struct platform_profile_handler {
>>       const char *name;
>>       struct device *dev;
>> +    struct device *class_dev;
>> +    int minor;
>>       unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
>>       int (*profile_get)(struct platform_profile_handler *pprof,
>>                   enum platform_profile_option *profile);
>