mirror of
				https://github.com/torvalds/linux.git
				synced 2025-10-31 16:48:26 +02:00 
			
		
		
		
	 dd133162c9
			
		
	
	
		dd133162c9
		
	
	
	
	
		
			
			The platform profile driver is loaded even on platforms that do not have
ACPI enabled. The initialization of the sysfs entries was recently moved
from platform_profile_register() to the module init call, and those
entries need acpi_kobj to be initialized which is not the case when ACPI
is disabled.
This results in the following warning:
 WARNING: CPU: 5 PID: 1 at fs/sysfs/group.c:131 internal_create_group+0xa22/0xdd8
 Modules linked in:
 CPU: 5 UID: 0 PID: 1 Comm: swapper/0 Tainted: G        W           6.15.0-rc7-dirty #6 PREEMPT
 Tainted: [W]=WARN
 Hardware name: riscv-virtio,qemu (DT)
 epc : internal_create_group+0xa22/0xdd8
  ra : internal_create_group+0xa22/0xdd8
 Call Trace:
 internal_create_group+0xa22/0xdd8
 sysfs_create_group+0x22/0x2e
 platform_profile_init+0x74/0xb2
 do_one_initcall+0x198/0xa9e
 kernel_init_freeable+0x6d8/0x780
 kernel_init+0x28/0x24c
 ret_from_fork+0xe/0x18
Fix this by checking if ACPI is enabled before trying to create sysfs
entries.
Fixes: 77be5cacb2 ("ACPI: platform_profile: Create class for ACPI platform profile")
Signed-off-by: Alexandre Ghiti <alexghiti@rivosinc.com>
Reviewed-by: Arnd Bergmann <arnd@arndb.de>
Reviewed-by: Mark Pearson <mpearson-lenovo@squebb.ca>
Link: https://patch.msgid.link/20250522141410.31315-1-alexghiti@rivosinc.com
[ rjw: Subject and changelog edits ]
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
		
	
			
		
			
				
	
	
		
			715 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			715 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| /* Platform profile sysfs interface */
 | |
| 
 | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 | |
| 
 | |
| #include <linux/acpi.h>
 | |
| #include <linux/bits.h>
 | |
| #include <linux/cleanup.h>
 | |
| #include <linux/init.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))
 | |
| 
 | |
| static DEFINE_MUTEX(profile_lock);
 | |
| 
 | |
| struct platform_profile_handler {
 | |
| 	const char *name;
 | |
| 	struct device dev;
 | |
| 	int minor;
 | |
| 	unsigned long choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
 | |
| 	unsigned long hidden_choices[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
 | |
| 	const struct platform_profile_ops *ops;
 | |
| };
 | |
| 
 | |
| struct aggregate_choices_data {
 | |
| 	unsigned long aggregate[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
 | |
| 	int count;
 | |
| };
 | |
| 
 | |
| static const char * const profile_names[] = {
 | |
| 	[PLATFORM_PROFILE_LOW_POWER] = "low-power",
 | |
| 	[PLATFORM_PROFILE_COOL] = "cool",
 | |
| 	[PLATFORM_PROFILE_QUIET] = "quiet",
 | |
| 	[PLATFORM_PROFILE_BALANCED] = "balanced",
 | |
| 	[PLATFORM_PROFILE_BALANCED_PERFORMANCE] = "balanced-performance",
 | |
| 	[PLATFORM_PROFILE_PERFORMANCE] = "performance",
 | |
| 	[PLATFORM_PROFILE_CUSTOM] = "custom",
 | |
| };
 | |
| static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
 | |
| 
 | |
| static DEFINE_IDA(platform_profile_ida);
 | |
| 
 | |
| /**
 | |
|  * _commmon_choices_show - Show the available profile choices
 | |
|  * @choices: The available profile choices
 | |
|  * @buf: The buffer to write to
 | |
|  *
 | |
|  * Return: The number of bytes written
 | |
|  */
 | |
| static ssize_t _commmon_choices_show(unsigned long *choices, char *buf)
 | |
| {
 | |
| 	int i, len = 0;
 | |
| 
 | |
| 	for_each_set_bit(i, 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");
 | |
| 
 | |
| 	return len;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _store_class_profile - Set the profile for a class device
 | |
|  * @dev: The class device
 | |
|  * @data: The profile to set
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _store_class_profile(struct device *dev, void *data)
 | |
| {
 | |
| 	struct platform_profile_handler *handler;
 | |
| 	int *bit = (int *)data;
 | |
| 
 | |
| 	lockdep_assert_held(&profile_lock);
 | |
| 	handler = to_pprof_handler(dev);
 | |
| 	if (!test_bit(*bit, handler->choices) && !test_bit(*bit, handler->hidden_choices))
 | |
| 		return -EOPNOTSUPP;
 | |
| 
 | |
| 	return handler->ops->profile_set(dev, *bit);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _notify_class_profile - Notify the class device of a profile change
 | |
|  * @dev: The class device
 | |
|  * @data: Unused
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _notify_class_profile(struct device *dev, void *data)
 | |
| {
 | |
| 	struct platform_profile_handler *handler = to_pprof_handler(dev);
 | |
| 
 | |
| 	lockdep_assert_held(&profile_lock);
 | |
| 	sysfs_notify(&handler->dev.kobj, NULL, "profile");
 | |
| 	kobject_uevent(&handler->dev.kobj, KOBJ_CHANGE);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 = to_pprof_handler(dev);
 | |
| 	err = handler->ops->profile_get(dev, &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
 | |
|  * @attr: The attribute
 | |
|  * @buf: The buffer to write to
 | |
|  *
 | |
|  * Return: The number of bytes written
 | |
|  */
 | |
| static ssize_t name_show(struct device *dev, struct device_attribute *attr, char *buf)
 | |
| {
 | |
| 	struct platform_profile_handler *handler = to_pprof_handler(dev);
 | |
| 
 | |
| 	return sysfs_emit(buf, "%s\n", handler->name);
 | |
| }
 | |
| static DEVICE_ATTR_RO(name);
 | |
| 
 | |
| /**
 | |
|  * choices_show - Show the available profile choices
 | |
|  * @dev: The device
 | |
|  * @attr: The attribute
 | |
|  * @buf: The buffer to write to
 | |
|  *
 | |
|  * Return: The number of bytes written
 | |
|  */
 | |
| static ssize_t choices_show(struct device *dev,
 | |
| 			    struct device_attribute *attr,
 | |
| 			    char *buf)
 | |
| {
 | |
| 	struct platform_profile_handler *handler = to_pprof_handler(dev);
 | |
| 
 | |
| 	return _commmon_choices_show(handler->choices, buf);
 | |
| }
 | |
| static DEVICE_ATTR_RO(choices);
 | |
| 
 | |
| /**
 | |
|  * 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]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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 index, ret;
 | |
| 
 | |
| 	index = sysfs_match_string(profile_names, buf);
 | |
| 	if (index < 0)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
 | |
| 		ret = _store_class_profile(dev, &index);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 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);
 | |
| 
 | |
| static void pprof_device_release(struct device *dev)
 | |
| {
 | |
| 	struct platform_profile_handler *pprof = to_pprof_handler(dev);
 | |
| 
 | |
| 	kfree(pprof);
 | |
| }
 | |
| 
 | |
| static const struct class platform_profile_class = {
 | |
| 	.name = "platform-profile",
 | |
| 	.dev_groups = profile_groups,
 | |
| 	.dev_release = pprof_device_release,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * _aggregate_choices - Aggregate the available profile choices
 | |
|  * @dev: The device
 | |
|  * @arg: struct aggregate_choices_data, with it's aggregate member bitmap
 | |
|  *	 initially filled with ones
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _aggregate_choices(struct device *dev, void *arg)
 | |
| {
 | |
| 	unsigned long tmp[BITS_TO_LONGS(PLATFORM_PROFILE_LAST)];
 | |
| 	struct aggregate_choices_data *data = arg;
 | |
| 	struct platform_profile_handler *handler;
 | |
| 
 | |
| 	lockdep_assert_held(&profile_lock);
 | |
| 
 | |
| 	handler = to_pprof_handler(dev);
 | |
| 	bitmap_or(tmp, handler->choices, handler->hidden_choices, PLATFORM_PROFILE_LAST);
 | |
| 	bitmap_and(data->aggregate, tmp, data->aggregate, PLATFORM_PROFILE_LAST);
 | |
| 	data->count++;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _remove_hidden_choices - Remove hidden choices from aggregate data
 | |
|  * @dev: The device
 | |
|  * @arg: struct aggregate_choices_data
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _remove_hidden_choices(struct device *dev, void *arg)
 | |
| {
 | |
| 	struct aggregate_choices_data *data = arg;
 | |
| 	struct platform_profile_handler *handler;
 | |
| 
 | |
| 	lockdep_assert_held(&profile_lock);
 | |
| 	handler = to_pprof_handler(dev);
 | |
| 	bitmap_andnot(data->aggregate, handler->choices,
 | |
| 		      handler->hidden_choices, PLATFORM_PROFILE_LAST);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * platform_profile_choices_show - Show the available profile choices for legacy sysfs interface
 | |
|  * @kobj: The kobject
 | |
|  * @attr: The attribute
 | |
|  * @buf: The buffer to write to
 | |
|  *
 | |
|  * Return: The number of bytes written
 | |
|  */
 | |
| static ssize_t platform_profile_choices_show(struct kobject *kobj,
 | |
| 					     struct kobj_attribute *attr,
 | |
| 					     char *buf)
 | |
| {
 | |
| 	struct aggregate_choices_data data = {
 | |
| 		.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
 | |
| 		.count = 0,
 | |
| 	};
 | |
| 	int err;
 | |
| 
 | |
| 	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
 | |
| 		err = class_for_each_device(&platform_profile_class, NULL,
 | |
| 					    &data, _aggregate_choices);
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 		if (data.count == 1) {
 | |
| 			err = class_for_each_device(&platform_profile_class, NULL,
 | |
| 						    &data, _remove_hidden_choices);
 | |
| 			if (err)
 | |
| 				return err;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* no profile handler registered any more */
 | |
| 	if (bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return _commmon_choices_show(data.aggregate, buf);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _aggregate_profiles - Aggregate the profiles for legacy sysfs interface
 | |
|  * @dev: The device
 | |
|  * @data: The profile to return
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _aggregate_profiles(struct device *dev, void *data)
 | |
| {
 | |
| 	enum platform_profile_option *profile = data;
 | |
| 	enum platform_profile_option val;
 | |
| 	int err;
 | |
| 
 | |
| 	err = get_class_profile(dev, &val);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 
 | |
| 	if (*profile != PLATFORM_PROFILE_LAST && *profile != val)
 | |
| 		*profile = PLATFORM_PROFILE_CUSTOM;
 | |
| 	else
 | |
| 		*profile = val;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * _store_and_notify - Store and notify a class from legacy sysfs interface
 | |
|  * @dev: The device
 | |
|  * @data: The profile to return
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| static int _store_and_notify(struct device *dev, void *data)
 | |
| {
 | |
| 	enum platform_profile_option *profile = data;
 | |
| 	int err;
 | |
| 
 | |
| 	err = _store_class_profile(dev, profile);
 | |
| 	if (err)
 | |
| 		return err;
 | |
| 	return _notify_class_profile(dev, NULL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * platform_profile_show - Show the current profile for legacy sysfs interface
 | |
|  * @kobj: The kobject
 | |
|  * @attr: The attribute
 | |
|  * @buf: The buffer to write to
 | |
|  *
 | |
|  * Return: The number of bytes written
 | |
|  */
 | |
| static ssize_t platform_profile_show(struct kobject *kobj,
 | |
| 				     struct kobj_attribute *attr,
 | |
| 				     char *buf)
 | |
| {
 | |
| 	enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
 | |
| 	int err;
 | |
| 
 | |
| 	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
 | |
| 		err = class_for_each_device(&platform_profile_class, NULL,
 | |
| 					    &profile, _aggregate_profiles);
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 	}
 | |
| 
 | |
| 	/* no profile handler registered any more */
 | |
| 	if (profile == PLATFORM_PROFILE_LAST)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return sysfs_emit(buf, "%s\n", profile_names[profile]);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * platform_profile_store - Set the profile for legacy sysfs interface
 | |
|  * @kobj: The kobject
 | |
|  * @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 platform_profile_store(struct kobject *kobj,
 | |
| 				      struct kobj_attribute *attr,
 | |
| 				      const char *buf, size_t count)
 | |
| {
 | |
| 	struct aggregate_choices_data data = {
 | |
| 		.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
 | |
| 		.count = 0,
 | |
| 	};
 | |
| 	int ret;
 | |
| 	int i;
 | |
| 
 | |
| 	/* Scan for a matching profile */
 | |
| 	i = sysfs_match_string(profile_names, buf);
 | |
| 	if (i < 0 || i == PLATFORM_PROFILE_CUSTOM)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
 | |
| 		ret = class_for_each_device(&platform_profile_class, NULL,
 | |
| 					    &data, _aggregate_choices);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 		if (!test_bit(i, data.aggregate))
 | |
| 			return -EOPNOTSUPP;
 | |
| 
 | |
| 		ret = class_for_each_device(&platform_profile_class, NULL, &i,
 | |
| 					    _store_and_notify);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static struct kobj_attribute attr_platform_profile_choices = __ATTR_RO(platform_profile_choices);
 | |
| static struct kobj_attribute attr_platform_profile = __ATTR_RW(platform_profile);
 | |
| 
 | |
| static struct attribute *platform_profile_attrs[] = {
 | |
| 	&attr_platform_profile_choices.attr,
 | |
| 	&attr_platform_profile.attr,
 | |
| 	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;
 | |
| 
 | |
| 	dev = class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered);
 | |
| 	if (!dev)
 | |
| 		return 0;
 | |
| 
 | |
| 	put_device(dev);
 | |
| 
 | |
| 	return attr->mode;
 | |
| }
 | |
| 
 | |
| static const struct attribute_group platform_profile_group = {
 | |
| 	.attrs = platform_profile_attrs,
 | |
| 	.is_visible = profile_class_is_visible,
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * platform_profile_notify - Notify class device and legacy sysfs interface
 | |
|  * @dev: The class device
 | |
|  */
 | |
| void platform_profile_notify(struct device *dev)
 | |
| {
 | |
| 	scoped_cond_guard(mutex_intr, return, &profile_lock) {
 | |
| 		_notify_class_profile(dev, NULL);
 | |
| 	}
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(platform_profile_notify);
 | |
| 
 | |
| /**
 | |
|  * platform_profile_cycle - Cycles profiles available on all registered class devices
 | |
|  *
 | |
|  * Return: 0 on success, -errno on failure
 | |
|  */
 | |
| int platform_profile_cycle(void)
 | |
| {
 | |
| 	struct aggregate_choices_data data = {
 | |
| 		.aggregate = { [0 ... BITS_TO_LONGS(PLATFORM_PROFILE_LAST) - 1] = ~0UL },
 | |
| 		.count = 0,
 | |
| 	};
 | |
| 	enum platform_profile_option next = PLATFORM_PROFILE_LAST;
 | |
| 	enum platform_profile_option profile = PLATFORM_PROFILE_LAST;
 | |
| 	int err;
 | |
| 
 | |
| 	scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
 | |
| 		err = class_for_each_device(&platform_profile_class, NULL,
 | |
| 					    &profile, _aggregate_profiles);
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 
 | |
| 		if (profile == PLATFORM_PROFILE_CUSTOM ||
 | |
| 		    profile == PLATFORM_PROFILE_LAST)
 | |
| 			return -EINVAL;
 | |
| 
 | |
| 		err = class_for_each_device(&platform_profile_class, NULL,
 | |
| 					    &data, _aggregate_choices);
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 
 | |
| 		/* never iterate into a custom if all drivers supported it */
 | |
| 		clear_bit(PLATFORM_PROFILE_CUSTOM, data.aggregate);
 | |
| 
 | |
| 		next = find_next_bit_wrap(data.aggregate,
 | |
| 					  PLATFORM_PROFILE_LAST,
 | |
| 					  profile + 1);
 | |
| 
 | |
| 		err = class_for_each_device(&platform_profile_class, NULL, &next,
 | |
| 					    _store_and_notify);
 | |
| 
 | |
| 		if (err)
 | |
| 			return err;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(platform_profile_cycle);
 | |
| 
 | |
| /**
 | |
|  * platform_profile_register - Creates and registers a platform profile class device
 | |
|  * @dev: Parent device
 | |
|  * @name: Name of the class device
 | |
|  * @drvdata: Driver data that will be attached to the class device
 | |
|  * @ops: Platform profile's mandatory operations
 | |
|  *
 | |
|  * Return: pointer to the new class device on success, ERR_PTR on failure
 | |
|  */
 | |
| struct device *platform_profile_register(struct device *dev, const char *name,
 | |
| 					 void *drvdata,
 | |
| 					 const struct platform_profile_ops *ops)
 | |
| {
 | |
| 	struct device *ppdev;
 | |
| 	int minor;
 | |
| 	int err;
 | |
| 
 | |
| 	/* Sanity check */
 | |
| 	if (WARN_ON_ONCE(!dev || !name || !ops || !ops->profile_get ||
 | |
| 	    !ops->profile_set || !ops->probe))
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 
 | |
| 	struct platform_profile_handler *pprof __free(kfree) = kzalloc(
 | |
| 		sizeof(*pprof), GFP_KERNEL);
 | |
| 	if (!pprof)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	err = ops->probe(drvdata, pprof->choices);
 | |
| 	if (err) {
 | |
| 		dev_err(dev, "platform_profile probe failed\n");
 | |
| 		return ERR_PTR(err);
 | |
| 	}
 | |
| 
 | |
| 	if (bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST)) {
 | |
| 		dev_err(dev, "Failed to register platform_profile class device with empty choices\n");
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 	}
 | |
| 
 | |
| 	if (ops->hidden_choices) {
 | |
| 		err = ops->hidden_choices(drvdata, pprof->hidden_choices);
 | |
| 		if (err) {
 | |
| 			dev_err(dev, "platform_profile hidden_choices failed\n");
 | |
| 			return ERR_PTR(err);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	guard(mutex)(&profile_lock);
 | |
| 
 | |
| 	/* create class interface for individual handler */
 | |
| 	minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
 | |
| 	if (minor < 0)
 | |
| 		return ERR_PTR(minor);
 | |
| 
 | |
| 	pprof->name = name;
 | |
| 	pprof->ops = ops;
 | |
| 	pprof->minor = minor;
 | |
| 	pprof->dev.class = &platform_profile_class;
 | |
| 	pprof->dev.parent = dev;
 | |
| 	dev_set_drvdata(&pprof->dev, drvdata);
 | |
| 	dev_set_name(&pprof->dev, "platform-profile-%d", pprof->minor);
 | |
| 	/* device_register() takes ownership of pprof/ppdev */
 | |
| 	ppdev = &no_free_ptr(pprof)->dev;
 | |
| 	err = device_register(ppdev);
 | |
| 	if (err) {
 | |
| 		put_device(ppdev);
 | |
| 		goto cleanup_ida;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| 
 | |
| 	err = sysfs_update_group(acpi_kobj, &platform_profile_group);
 | |
| 	if (err)
 | |
| 		goto cleanup_cur;
 | |
| 
 | |
| 	return ppdev;
 | |
| 
 | |
| cleanup_cur:
 | |
| 	device_unregister(ppdev);
 | |
| 
 | |
| cleanup_ida:
 | |
| 	ida_free(&platform_profile_ida, minor);
 | |
| 
 | |
| 	return ERR_PTR(err);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(platform_profile_register);
 | |
| 
 | |
| /**
 | |
|  * platform_profile_remove - Unregisters a platform profile class device
 | |
|  * @dev: Class device
 | |
|  */
 | |
| void platform_profile_remove(struct device *dev)
 | |
| {
 | |
| 	struct platform_profile_handler *pprof;
 | |
| 
 | |
| 	if (IS_ERR_OR_NULL(dev))
 | |
| 		return;
 | |
| 
 | |
| 	pprof = to_pprof_handler(dev);
 | |
| 
 | |
| 	guard(mutex)(&profile_lock);
 | |
| 
 | |
| 	ida_free(&platform_profile_ida, pprof->minor);
 | |
| 	device_unregister(&pprof->dev);
 | |
| 
 | |
| 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
 | |
| 	sysfs_update_group(acpi_kobj, &platform_profile_group);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(platform_profile_remove);
 | |
| 
 | |
| static void devm_platform_profile_release(struct device *dev, void *res)
 | |
| {
 | |
| 	struct device **ppdev = res;
 | |
| 
 | |
| 	platform_profile_remove(*ppdev);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * devm_platform_profile_register - Device managed version of platform_profile_register
 | |
|  * @dev: Parent device
 | |
|  * @name: Name of the class device
 | |
|  * @drvdata: Driver data that will be attached to the class device
 | |
|  * @ops: Platform profile's mandatory operations
 | |
|  *
 | |
|  * Return: pointer to the new class device on success, ERR_PTR on failure
 | |
|  */
 | |
| struct device *devm_platform_profile_register(struct device *dev, const char *name,
 | |
| 					      void *drvdata,
 | |
| 					      const struct platform_profile_ops *ops)
 | |
| {
 | |
| 	struct device *ppdev;
 | |
| 	struct device **dr;
 | |
| 
 | |
| 	dr = devres_alloc(devm_platform_profile_release, sizeof(*dr), GFP_KERNEL);
 | |
| 	if (!dr)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	ppdev = platform_profile_register(dev, name, drvdata, ops);
 | |
| 	if (IS_ERR(ppdev)) {
 | |
| 		devres_free(dr);
 | |
| 		return ppdev;
 | |
| 	}
 | |
| 
 | |
| 	*dr = ppdev;
 | |
| 	devres_add(dev, dr);
 | |
| 
 | |
| 	return ppdev;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(devm_platform_profile_register);
 | |
| 
 | |
| static int __init platform_profile_init(void)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	if (acpi_disabled)
 | |
| 		return -EOPNOTSUPP;
 | |
| 
 | |
| 	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)
 | |
| {
 | |
| 	sysfs_remove_group(acpi_kobj, &platform_profile_group);
 | |
| 	class_unregister(&platform_profile_class);
 | |
| }
 | |
| 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");
 |