* [PATCH v1 0/4] Make platform profiles firmware independent
@ 2026-10-14 13:24 Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 1/4] platform profile: Separate handler locking from sysfs lifetime Daniel Lezcano
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Daniel Lezcano @ 2026-10-14 13:24 UTC (permalink / raw)
To: rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman,
Armin Wolf, Jack Wu, Mario Limonciello (AMD), linux-acpi,
linux-kernel, linux-doc, linux-pm, platform-driver-x86
Make platform profile selection independent of ACPI firmware.
The provider API already uses struct device, profile enums and callbacks;
it does not require an ACPI handle or firmware node. However, the core is
built under drivers/acpi/, its Kconfig symbol depends implicitly on ACPI,
initialization fails when ACPI is disabled, and the system-wide profile
interface is only exposed under /sys/firmware/acpi/.
This series moves the core to drivers/platform/profile/ and introduces
CONFIG_PLATFORM_PROFILE, updating all existing provider selectors. It
retains the tristate configuration and platform_profile module name.
Firmware-specific discovery and profile operations remain with providers.
The core can also be selected directly through its Kconfig prompt.
An optional ACPI adapter obtains the legacy sysfs kobject and is linked
into the same module. The generic core contains no ACPI types or symbols
and initializes without ACPI support or with ACPI disabled at boot.
The existing /sys/firmware/acpi/platform_profile and
/sys/firmware/acpi/platform_profile_choices attributes remain available
on active ACPI systems.
The new system-wide interface is:
/sys/kernel/platform_profile/choices
/sys/kernel/platform_profile/profile
Both aggregate frontends use the same callbacks, handler set and profile
semantics. The per-provider /sys/class/platform-profile/ interface is
preserved. Notifications cover both aggregate frontends and direct
per-provider writes. The new sysfs location is proposed ABI.
The first patch fixes the lock/lifetime boundary before adding another
frontend: sysfs teardown must not drain callbacks while holding the mutex
those callbacks need. A separate registration mutex serializes lifecycle
changes, while a profile-lock-protected handler list determines aggregate
membership independently of the class device's teardown state.
The second patch extracts the core and uses subsys_initcall_sync() so it
initializes after ACPI and before built-in thermal and CPUFreq consumers.
The third adds the generic aggregate interface, and the fourth documents
the interfaces and extends maintainer coverage.
Validation:
- ARM64 built-in core with CONFIG_ACPI=n: W=1 build and sparse.
- x86 modular core with CONFIG_ACPI=y: W=1 object build and sparse.
- Full x86 kernel link and QEMU boot with lockdep and list debugging,
with active ACPI and with acpi=off.
- Firmware-less providers exercising common and hidden choices,
profile reads/writes, invalid input, mixed profiles, cycling,
partial callback failures, POLLPRI and legacy/generic shared state.
- Concurrent sysfs I/O with two persistent providers while a third
provider is registered and removed 200 times, last-provider attribute
removal and repeated provider module reloads.
The provider API and profile names are unchanged. Aggregate writes retain
their sequential, non-transactional behavior: a failing callback does not
roll back providers already updated.
Daniel Lezcano (4):
platform profile: Separate handler locking from sysfs lifetime
platform profile: Extract the core from ACPI
platform profile: Add firmware-neutral aggregate sysfs interface
platform profile: Document firmware-independent profile selection
.../ABI/testing/sysfs-class-platform-profile | 2 +-
.../ABI/testing/sysfs-kernel-platform_profile | 30 +++
.../ABI/testing/sysfs-platform_profile | 7 +-
.../userspace-api/sysfs-platform_profile.rst | 41 +++-
MAINTAINERS | 6 +
drivers/acpi/Kconfig | 3 -
drivers/acpi/Makefile | 1 -
drivers/cpufreq/Kconfig.x86 | 2 +-
drivers/platform/Kconfig | 16 ++
drivers/platform/Makefile | 2 +
drivers/platform/profile/Makefile | 5 +
drivers/platform/profile/acpi.c | 15 ++
.../profile/core.c} | 195 +++++++++++++-----
drivers/platform/profile/profile.h | 17 ++
drivers/platform/surface/Kconfig | 6 +-
drivers/platform/x86/Kconfig | 10 +-
drivers/platform/x86/amd/pmf/Kconfig | 2 +-
drivers/platform/x86/dell/Kconfig | 4 +-
drivers/platform/x86/hp/Kconfig | 2 +-
drivers/platform/x86/lenovo/Kconfig | 6 +-
drivers/thermal/intel/int340x_thermal/Kconfig | 2 +-
include/linux/platform_profile.h | 3 +
22 files changed, 288 insertions(+), 89 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-platform_profile
create mode 100644 drivers/platform/profile/Makefile
create mode 100644 drivers/platform/profile/acpi.c
rename drivers/{acpi/platform_profile.c => platform/profile/core.c} (79%)
create mode 100644 drivers/platform/profile/profile.h
base-commit: d0cd1e850e1602729dd60dc3c5fd96f80e0cdc5d
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v1 1/4] platform profile: Separate handler locking from sysfs lifetime
2026-10-14 13:24 [PATCH v1 0/4] Make platform profiles firmware independent Daniel Lezcano
@ 2026-10-14 13:24 ` Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 2/4] platform profile: Extract the core from ACPI Daniel Lezcano
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Lezcano @ 2026-10-14 13:24 UTC (permalink / raw)
To: rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman,
Armin Wolf, Jack Wu, Mario Limonciello (AMD), linux-acpi,
linux-kernel, linux-doc, linux-pm, platform-driver-x86
Registration and removal currently hold profile_lock across
device_unregister() and sysfs_update_group(). These operations can remove
sysfs attributes and wait for their active callbacks to finish. The
profile read and write callbacks acquire profile_lock themselves, so a
concurrent access can deadlock:
Removal/update path Active sysfs callback
------------------- ---------------------
acquire profile_lock hold kernfs active reference
remove a sysfs attribute wait for profile_lock
wait for its active references
The removal path cannot finish until the callback returns, and the
callback cannot return until the removal path releases profile_lock.
This applies both to per-provider device teardown and to updates of the
legacy aggregate attribute group.
Simply dropping profile_lock around these operations is insufficient.
Aggregate operations currently discover providers through the class
device list. A provider being unregistered could therefore still be
selected for an aggregate read, write or cycle while its sysfs files are
being torn down. Concurrent registrations and removals would also race
when updating aggregate attribute visibility.
Introduce profile_registration_lock to serialize registration, removal
and aggregate sysfs updates. Sysfs callbacks never acquire this mutex.
Retain profile_lock for profile operations and use it to protect an
explicit list of handlers participating in the aggregate interface.
Add a handler to that list only after device_register() succeeds. On
removal, unlink it under profile_lock before calling device_unregister().
Acquiring profile_lock first waits for any aggregate operation already
using the handler to complete; unlinking prevents later aggregate
operations from finding it. Release profile_lock before draining sysfs
callbacks, allowing active per-provider callbacks to complete before the
device is released. Apply the same unlink-before-unregister ordering to
registration error cleanup.
Walk this list for aggregate reads, writes and profile cycling, and use
its emptiness to determine aggregate attribute visibility. The class
devices remain the per-provider userspace interface.
There can now be an interval between removing the last handler from the
list and removing the aggregate attributes. Explicitly reject choices
reads and writes during that interval: the intersection bitmap starts
with every bit set, so an empty traversal must not advertise all profiles
or report a successful write without updating a provider.
This also establishes the locking and lifetime boundary needed to expose
the aggregate operations through a second, firmware-independent sysfs
interface later in the series.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
---
drivers/acpi/platform_profile.c | 75 ++++++++++++++++++---------------
1 file changed, 42 insertions(+), 33 deletions(-)
diff --git a/drivers/acpi/platform_profile.c b/drivers/acpi/platform_profile.c
index 79d2e92e5c28..f98ce4cda02c 100644
--- a/drivers/acpi/platform_profile.c
+++ b/drivers/acpi/platform_profile.c
@@ -8,6 +8,7 @@
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/init.h>
+#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/platform_profile.h>
#include <linux/sysfs.h>
@@ -15,8 +16,12 @@
#define to_pprof_handler(d) (container_of(d, struct platform_profile_handler, dev))
static DEFINE_MUTEX(profile_lock);
+/* Serialize registration and sysfs updates without blocking sysfs callbacks. */
+static DEFINE_MUTEX(profile_registration_lock);
+static LIST_HEAD(profile_handlers);
struct platform_profile_handler {
+ struct list_head node;
const char *name;
struct device dev;
int minor;
@@ -44,6 +49,22 @@ static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
static DEFINE_IDA(platform_profile_ida);
+static int profile_walk_handlers(void *data,
+ int (*fn)(struct device *dev, void *data))
+{
+ struct platform_profile_handler *handler;
+ int ret;
+
+ lockdep_assert_held(&profile_lock);
+ list_for_each_entry(handler, &profile_handlers, node) {
+ ret = fn(&handler->dev, data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
/**
* _commmon_choices_show - Show the available profile choices
* @choices: The available profile choices
@@ -306,20 +327,18 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj,
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ err = profile_walk_handlers(&data, _aggregate_choices);
if (err)
return err;
if (data.count == 1) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _remove_hidden_choices);
+ err = profile_walk_handlers(&data, _remove_hidden_choices);
if (err)
return err;
}
}
/* no profile handler registered any more */
- if (bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
+ if (!data.count || bitmap_empty(data.aggregate, PLATFORM_PROFILE_LAST))
return -EINVAL;
return _commmon_choices_show(data.aggregate, buf);
@@ -384,8 +403,7 @@ static ssize_t platform_profile_show(struct kobject *kobj,
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &profile, _aggregate_profiles);
+ err = profile_walk_handlers(&profile, _aggregate_profiles);
if (err)
return err;
}
@@ -423,15 +441,13 @@ static ssize_t platform_profile_store(struct kobject *kobj,
return -EINVAL;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- ret = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ ret = profile_walk_handlers(&data, _aggregate_choices);
if (ret)
return ret;
- if (!test_bit(i, data.aggregate))
+ if (!data.count || !test_bit(i, data.aggregate))
return -EOPNOTSUPP;
- ret = class_for_each_device(&platform_profile_class, NULL, &i,
- _store_and_notify);
+ ret = profile_walk_handlers(&i, _store_and_notify);
if (ret)
return ret;
}
@@ -450,22 +466,11 @@ 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)
{
- struct device *dev;
-
- dev = class_find_device(&platform_profile_class, NULL, NULL, profile_class_registered);
- if (!dev)
- return 0;
-
- put_device(dev);
+ guard(mutex)(&profile_lock);
- return attr->mode;
+ return list_empty(&profile_handlers) ? 0 : attr->mode;
}
static const struct attribute_group platform_profile_group = {
@@ -502,8 +507,7 @@ int platform_profile_cycle(void)
int err;
scoped_cond_guard(mutex_intr, return -ERESTARTSYS, &profile_lock) {
- err = class_for_each_device(&platform_profile_class, NULL,
- &profile, _aggregate_profiles);
+ err = profile_walk_handlers(&profile, _aggregate_profiles);
if (err)
return err;
@@ -512,8 +516,7 @@ int platform_profile_cycle(void)
profile == PLATFORM_PROFILE_LAST)
return -EINVAL;
- err = class_for_each_device(&platform_profile_class, NULL,
- &data, _aggregate_choices);
+ err = profile_walk_handlers(&data, _aggregate_choices);
if (err)
return err;
@@ -525,8 +528,7 @@ int platform_profile_cycle(void)
PLATFORM_PROFILE_LAST,
profile + 1);
- err = class_for_each_device(&platform_profile_class, NULL, &next,
- _store_and_notify);
+ err = profile_walk_handlers(&next, _store_and_notify);
if (err)
return err;
@@ -583,7 +585,7 @@ struct device *platform_profile_register(struct device *dev, const char *name,
}
}
- guard(mutex)(&profile_lock);
+ guard(mutex)(&profile_registration_lock);
/* create class interface for individual handler */
minor = ida_alloc(&platform_profile_ida, GFP_KERNEL);
@@ -604,6 +606,8 @@ struct device *platform_profile_register(struct device *dev, const char *name,
put_device(ppdev);
goto cleanup_ida;
}
+ scoped_guard(mutex, &profile_lock)
+ list_add_tail(&to_pprof_handler(ppdev)->node, &profile_handlers);
sysfs_notify(acpi_kobj, NULL, "platform_profile");
@@ -614,6 +618,8 @@ struct device *platform_profile_register(struct device *dev, const char *name,
return ppdev;
cleanup_cur:
+ scoped_guard(mutex, &profile_lock)
+ list_del(&to_pprof_handler(ppdev)->node);
device_unregister(ppdev);
cleanup_ida:
@@ -636,7 +642,10 @@ void platform_profile_remove(struct device *dev)
pprof = to_pprof_handler(dev);
- guard(mutex)(&profile_lock);
+ guard(mutex)(&profile_registration_lock);
+
+ scoped_guard(mutex, &profile_lock)
+ list_del(&pprof->node);
ida_free(&platform_profile_ida, pprof->minor);
device_unregister(&pprof->dev);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 2/4] platform profile: Extract the core from ACPI
2026-10-14 13:24 [PATCH v1 0/4] Make platform profiles firmware independent Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 1/4] platform profile: Separate handler locking from sysfs lifetime Daniel Lezcano
@ 2026-10-14 13:24 ` Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 4/4] platform profile: Document firmware-independent profile selection Daniel Lezcano
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Lezcano @ 2026-10-14 13:24 UTC (permalink / raw)
To: rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman,
Armin Wolf, Jack Wu, Mario Limonciello (AMD), linux-acpi,
linux-kernel, linux-doc, linux-pm, platform-driver-x86
Platform profile providers already use device-based callbacks and do not
need ACPI types. The core is nevertheless confined to the ACPI build and
refuses to initialize when ACPI is disabled.
Move the implementation to drivers/platform/profile and introduce the
firmware-independent PLATFORM_PROFILE symbol. Update all provider
selectors. Keep provider-specific ACPI dependencies and the
platform_profile module name.
Isolate access to the legacy ACPI kobject in an optional compatibility
object linked into the same module. Retain the legacy ABI on active ACPI
systems, and initialize the class even when ACPI is absent or disabled.
Use subsys_initcall_sync so the class is ready before built-in thermal
and CPUFreq providers, but after ACPI has initialized its compatibility
kobject.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
---
drivers/acpi/Kconfig | 3 -
drivers/acpi/Makefile | 1 -
drivers/cpufreq/Kconfig.x86 | 2 +-
drivers/platform/Kconfig | 16 +++++
drivers/platform/Makefile | 2 +
drivers/platform/profile/Makefile | 5 ++
drivers/platform/profile/acpi.c | 15 +++++
.../profile/core.c} | 64 +++++++++++++------
drivers/platform/profile/profile.h | 17 +++++
drivers/platform/surface/Kconfig | 6 +-
drivers/platform/x86/Kconfig | 10 +--
drivers/platform/x86/amd/pmf/Kconfig | 2 +-
drivers/platform/x86/dell/Kconfig | 4 +-
drivers/platform/x86/hp/Kconfig | 2 +-
drivers/platform/x86/lenovo/Kconfig | 6 +-
drivers/thermal/intel/int340x_thermal/Kconfig | 2 +-
16 files changed, 118 insertions(+), 39 deletions(-)
create mode 100644 drivers/platform/profile/Makefile
create mode 100644 drivers/platform/profile/acpi.c
rename drivers/{acpi/platform_profile.c => platform/profile/core.c} (94%)
create mode 100644 drivers/platform/profile/profile.h
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig
index f165d14cf61a..dd00312217de 100644
--- a/drivers/acpi/Kconfig
+++ b/drivers/acpi/Kconfig
@@ -351,9 +351,6 @@ config ACPI_THERMAL
To compile this driver as a module, choose M here:
the module will be called thermal.
-config ACPI_PLATFORM_PROFILE
- tristate
-
config ACPI_CUSTOM_DSDT_FILE
string "Custom DSDT Table file to include"
default ""
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile
index d1b0affb844f..f8af27341805 100644
--- a/drivers/acpi/Makefile
+++ b/drivers/acpi/Makefile
@@ -91,7 +91,6 @@ obj-$(CONFIG_ACPI_PROCESSOR) += processor.o
obj-$(CONFIG_ACPI) += container.o
obj-$(CONFIG_ACPI_THERMAL_LIB) += thermal_lib.o
obj-$(CONFIG_ACPI_THERMAL) += thermal.o
-obj-$(CONFIG_ACPI_PLATFORM_PROFILE) += platform_profile.o
obj-$(CONFIG_ACPI_NFIT) += nfit/
obj-$(CONFIG_ACPI_NHLT) += nhlt.o
obj-$(CONFIG_ACPI_NUMA) += numa/
diff --git a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86
index d6190b8dde4b..9e1e3391a084 100644
--- a/drivers/cpufreq/Kconfig.x86
+++ b/drivers/cpufreq/Kconfig.x86
@@ -39,7 +39,7 @@ config X86_AMD_PSTATE
select ACPI_PROCESSOR
select ACPI_CPPC_LIB if X86_64
select CPU_FREQ_GOV_SCHEDUTIL if SMP
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select POWER_SUPPLY
help
This driver adds a CPUFreq driver which utilizes a fine grain
diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 312788f249c9..bc5acd3ebd2f 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -1,4 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
+config PLATFORM_PROFILE
+ tristate "Platform profile support"
+ help
+ Provide a common sysfs interface for selecting platform power,
+ performance and cooling profiles. Platform-specific provider drivers
+ map their operating modes onto standard profile names, independently
+ of the firmware interface they use.
+
+ The core exposes per-provider controls and a system-wide interface
+ for profiles supported by all registered providers. A provider driver
+ must register before profile attributes become available. ACPI is not
+ required; on ACPI systems the legacy sysfs interface is also provided.
+
+ To compile this as a module, choose M here: the module will be called
+ platform_profile.
+
source "drivers/platform/mips/Kconfig"
source "drivers/platform/loongarch/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index fa322e7f8716..b6732f2f3c85 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -3,6 +3,8 @@
# Makefile for linux/drivers/platform
#
+obj-$(CONFIG_PLATFORM_PROFILE) += profile/
+
obj-$(CONFIG_X86) += x86/
obj-$(CONFIG_LOONGARCH) += loongarch/
obj-$(CONFIG_MELLANOX_PLATFORM) += mellanox/
diff --git a/drivers/platform/profile/Makefile b/drivers/platform/profile/Makefile
new file mode 100644
index 000000000000..ae95ba09383c
--- /dev/null
+++ b/drivers/platform/profile/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_PLATFORM_PROFILE) += platform_profile.o
+
+platform_profile-y := core.o
+platform_profile-$(CONFIG_ACPI) += acpi.o
diff --git a/drivers/platform/profile/acpi.c b/drivers/platform/profile/acpi.c
new file mode 100644
index 000000000000..ff925dedacaf
--- /dev/null
+++ b/drivers/platform/profile/acpi.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* Compatibility with the original ACPI platform profile sysfs ABI. */
+
+#include <linux/acpi.h>
+#include <linux/kobject.h>
+
+#include "profile.h"
+
+struct kobject *platform_profile_compat_kobj(void)
+{
+ if (acpi_disabled)
+ return NULL;
+
+ return kobject_get(acpi_kobj);
+}
diff --git a/drivers/acpi/platform_profile.c b/drivers/platform/profile/core.c
similarity index 94%
rename from drivers/acpi/platform_profile.c
rename to drivers/platform/profile/core.c
index f98ce4cda02c..f18fd96b942c 100644
--- a/drivers/acpi/platform_profile.c
+++ b/drivers/platform/profile/core.c
@@ -4,21 +4,36 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <linux/acpi.h>
+#include <linux/bitmap.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
#include <linux/init.h>
+#include <linux/idr.h>
+#include <linux/kobject.h>
#include <linux/list.h>
+#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_profile.h>
+#include <linux/slab.h>
#include <linux/sysfs.h>
+#include "profile.h"
+
#define to_pprof_handler(d) (container_of(d, struct platform_profile_handler, dev))
static DEFINE_MUTEX(profile_lock);
/* Serialize registration and sysfs updates without blocking sysfs callbacks. */
static DEFINE_MUTEX(profile_registration_lock);
static LIST_HEAD(profile_handlers);
+static struct kobject *compat_kobj;
+
+static void profile_notify(void)
+{
+ if (compat_kobj)
+ sysfs_notify(compat_kobj, NULL, "platform_profile");
+}
+
+static int profile_update(void);
struct platform_profile_handler {
struct list_head node;
@@ -237,7 +252,7 @@ static ssize_t profile_store(struct device *dev,
return ret;
}
- sysfs_notify(acpi_kobj, NULL, "platform_profile");
+ profile_notify();
return count;
}
@@ -452,7 +467,7 @@ static ssize_t platform_profile_store(struct kobject *kobj,
return ret;
}
- sysfs_notify(acpi_kobj, NULL, "platform_profile");
+ profile_notify();
return count;
}
@@ -478,6 +493,14 @@ static const struct attribute_group platform_profile_group = {
.is_visible = profile_class_is_visible,
};
+static int profile_update(void)
+{
+ if (compat_kobj)
+ return sysfs_update_group(compat_kobj, &platform_profile_group);
+
+ return 0;
+}
+
/**
* platform_profile_notify - Notify class device and legacy sysfs interface
* @dev: The class device
@@ -487,7 +510,7 @@ 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");
+ profile_notify();
}
EXPORT_SYMBOL_GPL(platform_profile_notify);
@@ -534,7 +557,7 @@ int platform_profile_cycle(void)
return err;
}
- sysfs_notify(acpi_kobj, NULL, "platform_profile");
+ profile_notify();
return 0;
}
@@ -609,9 +632,9 @@ struct device *platform_profile_register(struct device *dev, const char *name,
scoped_guard(mutex, &profile_lock)
list_add_tail(&to_pprof_handler(ppdev)->node, &profile_handlers);
- sysfs_notify(acpi_kobj, NULL, "platform_profile");
+ profile_notify();
- err = sysfs_update_group(acpi_kobj, &platform_profile_group);
+ err = profile_update();
if (err)
goto cleanup_cur;
@@ -650,8 +673,8 @@ void platform_profile_remove(struct device *dev)
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);
+ profile_notify();
+ profile_update();
}
EXPORT_SYMBOL_GPL(platform_profile_remove);
@@ -699,28 +722,33 @@ 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);
+ compat_kobj = platform_profile_compat_kobj();
+ if (compat_kobj) {
+ err = sysfs_create_group(compat_kobj, &platform_profile_group);
+ if (err) {
+ kobject_put(compat_kobj);
+ class_unregister(&platform_profile_class);
+ }
+ }
return err;
}
static void __exit platform_profile_exit(void)
{
- sysfs_remove_group(acpi_kobj, &platform_profile_group);
+ if (compat_kobj) {
+ sysfs_remove_group(compat_kobj, &platform_profile_group);
+ kobject_put(compat_kobj);
+ }
class_unregister(&platform_profile_class);
}
-module_init(platform_profile_init);
+subsys_initcall_sync(platform_profile_init);
module_exit(platform_profile_exit);
MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
-MODULE_DESCRIPTION("ACPI platform profile sysfs interface");
+MODULE_DESCRIPTION("Platform profile sysfs interface");
MODULE_LICENSE("GPL");
diff --git a/drivers/platform/profile/profile.h b/drivers/platform/profile/profile.h
new file mode 100644
index 000000000000..910c9f7da6a0
--- /dev/null
+++ b/drivers/platform/profile/profile.h
@@ -0,0 +1,17 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _PLATFORM_PROFILE_INTERNAL_H
+#define _PLATFORM_PROFILE_INTERNAL_H
+
+#include <linux/kconfig.h>
+#include <linux/kobject.h>
+
+#ifdef CONFIG_ACPI
+struct kobject *platform_profile_compat_kobj(void);
+#else
+static inline struct kobject *platform_profile_compat_kobj(void)
+{
+ return NULL;
+}
+#endif
+
+#endif
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
index f775c6ca1ec1..08f15f72f8ed 100644
--- a/drivers/platform/surface/Kconfig
+++ b/drivers/platform/surface/Kconfig
@@ -203,12 +203,12 @@ config SURFACE_PLATFORM_PROFILE
tristate "Surface Platform Profile Driver"
depends on ACPI
depends on SURFACE_AGGREGATOR_REGISTRY
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
- Provides support for the ACPI platform profile on 5th- and later
+ Provides support for platform profiles on 5th- and later
generation Microsoft Surface devices.
- More specifically, this driver provides ACPI platform profile support
+ More specifically, this driver provides platform profile support
on Microsoft Surface devices with a Surface System Aggregator Module
(SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In
other words, this driver provides platform profile support on the
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index b54b5212b204..76b42f68c9cc 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -120,7 +120,7 @@ config BITLAND_MIFS_WMI
depends on INPUT
depends on LEDS_CLASS
depends on POWER_SUPPLY
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select INPUT_SPARSEKMAP
help
This is a driver for Bitland MiInterface based laptops.
@@ -180,7 +180,7 @@ config ACER_WMI
select INPUT_SPARSEKMAP
select LEDS_CLASS
select NEW_LEDS
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
This is a driver for newer Acer (and Wistron) laptops. It adds
wireless radio and bluetooth control, and on some laptops,
@@ -279,7 +279,7 @@ config ASUS_WMI
select INPUT_SPARSEKMAP
select LEDS_CLASS
select NEW_LEDS
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
Say Y here if you have a WMI aware Asus laptop (like Eee PCs or new
Asus Notebooks).
@@ -646,7 +646,7 @@ config SAMSUNG_GALAXYBOOK
depends on INPUT
depends on LEDS_CLASS
depends on SERIO_I8042
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select FW_ATTR_CLASS
help
This is a driver for Samsung Galaxy Book series notebooks. It adds
@@ -906,7 +906,7 @@ config TOUCHSCREEN_DMI
config INSPUR_PLATFORM_PROFILE
tristate "Inspur WMI platform profile driver"
depends on ACPI_WMI
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
This will allow users to determine and control the platform modes
between low-power, balanced and performance modes.
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
index 25b8f7ae3abd..a0bdd85db6a0 100644
--- a/drivers/platform/x86/amd/pmf/Kconfig
+++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -8,7 +8,7 @@ config AMD_PMF
depends on ACPI && PCI
depends on POWER_SUPPLY
depends on AMD_NODE
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
depends on TEE && AMDTEE
depends on AMD_SFH_HID
depends on HAS_IOMEM
diff --git a/drivers/platform/x86/dell/Kconfig b/drivers/platform/x86/dell/Kconfig
index 9f0016056d7e..f6acf1cc2a2f 100644
--- a/drivers/platform/x86/dell/Kconfig
+++ b/drivers/platform/x86/dell/Kconfig
@@ -44,7 +44,7 @@ config ALIENWARE_WMI_WMAX
bool "Alienware WMAX WMI device driver"
default y
depends on ALIENWARE_WMI
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
Alienware WMI driver with AlienFX LED, HDMI, amplifier, deep sleep and
AWCC thermal control capabilities.
@@ -120,7 +120,7 @@ config DELL_PC
depends on ACPI
depends on DMI
depends on DELL_SMBIOS
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
help
This driver adds support for controlling the fan modes via platform_profile
on supported Dell systems regardless of formfactor.
diff --git a/drivers/platform/x86/hp/Kconfig b/drivers/platform/x86/hp/Kconfig
index dd51491b9bcd..81a52194ff42 100644
--- a/drivers/platform/x86/hp/Kconfig
+++ b/drivers/platform/x86/hp/Kconfig
@@ -43,7 +43,7 @@ config HP_WMI
depends on RFKILL || RFKILL = n
select POWER_SUPPLY
select INPUT_SPARSEKMAP
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select HWMON
help
Say Y here if you want to support WMI-based hotkeys on HP laptops and
diff --git a/drivers/platform/x86/lenovo/Kconfig b/drivers/platform/x86/lenovo/Kconfig
index 4443f40ef8aa..c8606ad56139 100644
--- a/drivers/platform/x86/lenovo/Kconfig
+++ b/drivers/platform/x86/lenovo/Kconfig
@@ -12,7 +12,7 @@ config IDEAPAD_LAPTOP
depends on BACKLIGHT_CLASS_DEVICE
depends on ACPI_VIDEO || ACPI_VIDEO = n
depends on ACPI_WMI || ACPI_WMI = n
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select INPUT_SPARSEKMAP
select NEW_LEDS
select LEDS_CLASS
@@ -63,7 +63,7 @@ config THINKPAD_ACPI
depends on BACKLIGHT_CLASS_DEVICE
depends on I2C
depends on DRM
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select DRM_PRIVACY_SCREEN
select HWMON
select NVRAM
@@ -250,7 +250,7 @@ config LENOVO_WMI_GAMEZONE
tristate "Lenovo GameZone WMI Driver"
depends on ACPI_WMI
depends on DMI
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select LENOVO_WMI_EVENTS
select LENOVO_WMI_HELPERS
help
diff --git a/drivers/thermal/intel/int340x_thermal/Kconfig b/drivers/thermal/intel/int340x_thermal/Kconfig
index 4ced7bdcd62c..3026a0a8d073 100644
--- a/drivers/thermal/intel/int340x_thermal/Kconfig
+++ b/drivers/thermal/intel/int340x_thermal/Kconfig
@@ -12,7 +12,7 @@ config INT340X_THERMAL
select ACPI_THERMAL_LIB
select INTEL_SOC_DTS_IOSF_CORE
select INTEL_TCC
- select ACPI_PLATFORM_PROFILE
+ select PLATFORM_PROFILE
select PROC_THERMAL_MMIO_RAPL if POWERCAP
help
Newer laptops and tablets that use ACPI may have thermal sensors and
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface
2026-10-14 13:24 [PATCH v1 0/4] Make platform profiles firmware independent Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 1/4] platform profile: Separate handler locking from sysfs lifetime Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 2/4] platform profile: Extract the core from ACPI Daniel Lezcano
@ 2026-10-14 13:24 ` Daniel Lezcano
2026-10-14 21:31 ` Armin Wolf
2026-10-14 13:24 ` [PATCH v1 4/4] platform profile: Document firmware-independent profile selection Daniel Lezcano
3 siblings, 1 reply; 6+ messages in thread
From: Daniel Lezcano @ 2026-10-14 13:24 UTC (permalink / raw)
To: rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman,
Armin Wolf, Jack Wu, Mario Limonciello (AMD), linux-acpi,
linux-kernel, linux-doc, linux-pm, platform-driver-x86
The per-provider class interface is firmware-independent, but system-
wide profile selection is only exposed under /sys/firmware/acpi.
Add /sys/kernel/platform_profile/{choices,profile}, sharing the existing
aggregate callbacks and semantics with the legacy interface. Keep
attributes hidden until a provider is registered and remove them after
the last provider.
Notify both aggregate frontends on profile changes. Also notify the
provider class device on a direct profile write, matching its documented
POLLPRI ABI. Restore aggregate visibility after a failed registration.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
---
drivers/platform/profile/core.c | 60 +++++++++++++++++++++++++++++----
1 file changed, 53 insertions(+), 7 deletions(-)
diff --git a/drivers/platform/profile/core.c b/drivers/platform/profile/core.c
index f18fd96b942c..507b96bdfdb1 100644
--- a/drivers/platform/profile/core.c
+++ b/drivers/platform/profile/core.c
@@ -26,9 +26,11 @@ static DEFINE_MUTEX(profile_lock);
static DEFINE_MUTEX(profile_registration_lock);
static LIST_HEAD(profile_handlers);
static struct kobject *compat_kobj;
+static struct kobject *profile_kobj;
static void profile_notify(void)
{
+ sysfs_notify(profile_kobj, NULL, "profile");
if (compat_kobj)
sysfs_notify(compat_kobj, NULL, "platform_profile");
}
@@ -250,6 +252,7 @@ static ssize_t profile_store(struct device *dev,
ret = _store_class_profile(dev, &index);
if (ret)
return ret;
+ _notify_class_profile(dev, NULL);
}
profile_notify();
@@ -324,7 +327,7 @@ static int _remove_hidden_choices(struct device *dev, void *arg)
}
/**
- * platform_profile_choices_show - Show the available profile choices for legacy sysfs interface
+ * platform_profile_choices_show - Show the aggregate profile choices
* @kobj: The kobject
* @attr: The attribute
* @buf: The buffer to write to
@@ -360,7 +363,7 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj,
}
/**
- * _aggregate_profiles - Aggregate the profiles for legacy sysfs interface
+ * _aggregate_profiles - Aggregate the profiles across providers
* @dev: The device
* @data: The profile to return
*
@@ -385,7 +388,7 @@ static int _aggregate_profiles(struct device *dev, void *data)
}
/**
- * _store_and_notify - Store and notify a class from legacy sysfs interface
+ * _store_and_notify - Store and notify a provider from an aggregate write
* @dev: The device
* @data: The profile to return
*
@@ -403,7 +406,7 @@ static int _store_and_notify(struct device *dev, void *data)
}
/**
- * platform_profile_show - Show the current profile for legacy sysfs interface
+ * platform_profile_show - Show the current aggregate profile
* @kobj: The kobject
* @attr: The attribute
* @buf: The buffer to write to
@@ -431,7 +434,7 @@ static ssize_t platform_profile_show(struct kobject *kobj,
}
/**
- * platform_profile_store - Set the profile for legacy sysfs interface
+ * platform_profile_store - Set the profile across all providers
* @kobj: The kobject
* @attr: The attribute
* @buf: The buffer to read from
@@ -493,8 +496,30 @@ static const struct attribute_group platform_profile_group = {
.is_visible = profile_class_is_visible,
};
+static struct kobj_attribute aggregate_choices =
+ __ATTR(choices, 0444, platform_profile_choices_show, NULL);
+static struct kobj_attribute aggregate_profile =
+ __ATTR(profile, 0644, platform_profile_show, platform_profile_store);
+
+static struct attribute *aggregate_attrs[] = {
+ &aggregate_choices.attr,
+ &aggregate_profile.attr,
+ NULL
+};
+
+static const struct attribute_group aggregate_group = {
+ .attrs = aggregate_attrs,
+ .is_visible = profile_class_is_visible,
+};
+
static int profile_update(void)
{
+ int err;
+
+ err = sysfs_update_group(profile_kobj, &aggregate_group);
+ if (err)
+ return err;
+
if (compat_kobj)
return sysfs_update_group(compat_kobj, &platform_profile_group);
@@ -502,7 +527,7 @@ static int profile_update(void)
}
/**
- * platform_profile_notify - Notify class device and legacy sysfs interface
+ * platform_profile_notify - Notify the provider and aggregate sysfs interfaces
* @dev: The class device
*/
void platform_profile_notify(struct device *dev)
@@ -643,6 +668,7 @@ struct device *platform_profile_register(struct device *dev, const char *name,
cleanup_cur:
scoped_guard(mutex, &profile_lock)
list_del(&to_pprof_handler(ppdev)->node);
+ profile_update();
device_unregister(ppdev);
cleanup_ida:
@@ -726,15 +752,33 @@ static int __init platform_profile_init(void)
if (err)
return err;
+ profile_kobj = kobject_create_and_add("platform_profile", kernel_kobj);
+ if (!profile_kobj) {
+ err = -ENOMEM;
+ goto unregister_class;
+ }
+
+ err = sysfs_create_group(profile_kobj, &aggregate_group);
+ if (err)
+ goto put_kobj;
+
compat_kobj = platform_profile_compat_kobj();
if (compat_kobj) {
err = sysfs_create_group(compat_kobj, &platform_profile_group);
if (err) {
kobject_put(compat_kobj);
- class_unregister(&platform_profile_class);
+ goto remove_group;
}
}
+ return 0;
+
+remove_group:
+ sysfs_remove_group(profile_kobj, &aggregate_group);
+put_kobj:
+ kobject_put(profile_kobj);
+unregister_class:
+ class_unregister(&platform_profile_class);
return err;
}
@@ -744,6 +788,8 @@ static void __exit platform_profile_exit(void)
sysfs_remove_group(compat_kobj, &platform_profile_group);
kobject_put(compat_kobj);
}
+ sysfs_remove_group(profile_kobj, &aggregate_group);
+ kobject_put(profile_kobj);
class_unregister(&platform_profile_class);
}
subsys_initcall_sync(platform_profile_init);
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH v1 4/4] platform profile: Document firmware-independent profile selection
2026-10-14 13:24 [PATCH v1 0/4] Make platform profiles firmware independent Daniel Lezcano
` (2 preceding siblings ...)
2026-10-14 13:24 ` [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface Daniel Lezcano
@ 2026-10-14 13:24 ` Daniel Lezcano
3 siblings, 0 replies; 6+ messages in thread
From: Daniel Lezcano @ 2026-10-14 13:24 UTC (permalink / raw)
To: rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman,
Armin Wolf, Jack Wu, Mario Limonciello (AMD), linux-acpi,
linux-kernel, linux-doc, linux-pm, platform-driver-x86,
Andrew Morton, Jakub Kicinski, Jonathan Cameron,
Krzysztof Kozlowski
Describe the firmware-neutral aggregate interface, retained ACPI
compatibility paths and unchanged device-based provider API. Document
hidden choices, mixed profiles and the sequential application of
aggregate writes.
Correct the per-provider attribute reference and extend existing ACPI
maintainer coverage to the extracted core and its interfaces.
Assisted-by: OpenCode:gpt-6.1-sol
Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
---
.../ABI/testing/sysfs-class-platform-profile | 2 +-
.../ABI/testing/sysfs-kernel-platform_profile | 30 ++++++++++++++
.../ABI/testing/sysfs-platform_profile | 7 +++-
.../userspace-api/sysfs-platform_profile.rst | 41 ++++++++++++++-----
MAINTAINERS | 6 +++
include/linux/platform_profile.h | 3 ++
6 files changed, 77 insertions(+), 12 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-kernel-platform_profile
diff --git a/Documentation/ABI/testing/sysfs-class-platform-profile b/Documentation/ABI/testing/sysfs-class-platform-profile
index fcab26894ec3..75e0f6e2c128 100644
--- a/Documentation/ABI/testing/sysfs-class-platform-profile
+++ b/Documentation/ABI/testing/sysfs-class-platform-profile
@@ -35,7 +35,7 @@ Date: March 2025
KernelVersion: 6.14
Description: Reading this file gives the current selected profile for this
device. Writing this file with one of the strings from
- platform_profile_choices changes the profile to the new value.
+ choices changes the profile to the new value.
This file can be monitored for changes by polling for POLLPRI,
POLLPRI will be signaled on any changes, independent of those
diff --git a/Documentation/ABI/testing/sysfs-kernel-platform_profile b/Documentation/ABI/testing/sysfs-kernel-platform_profile
new file mode 100644
index 000000000000..c64a175873e2
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-kernel-platform_profile
@@ -0,0 +1,30 @@
+What: /sys/kernel/platform_profile/choices
+Date: October 2026
+Description: Space-separated list of standard profiles supported across all
+ registered platform profile providers. Profile names have the
+ meanings documented in sysfs-class-platform-profile.
+
+ The file is read-only and is present while at least one provider
+ is registered. Reading fails with EINVAL when providers have no
+ common choices. Hidden choices may contribute to the common
+ choices when multiple providers are registered; with a single
+ provider only its visible choices are listed.
+
+What: /sys/kernel/platform_profile/profile
+Date: October 2026
+Description: Reading returns the current profile when all providers agree,
+ or "custom" when they have different profiles or a provider
+ reports a custom configuration.
+
+ Writing a profile listed in choices applies it to all providers.
+ "custom" cannot be written. Changes are applied sequentially;
+ an error does not roll back providers already updated.
+
+ The file is read-write and is present while at least one provider
+ is registered. It can be monitored with poll() for POLLPRI.
+ Notifications cover successful userspace writes and changes
+ reported by providers, for example after a firmware hotkey event.
+
+ On ACPI systems this interface shares its state with the legacy
+ /sys/firmware/acpi/platform_profile interface. The interface is
+ also available without ACPI support or with ACPI disabled.
diff --git a/Documentation/ABI/testing/sysfs-platform_profile b/Documentation/ABI/testing/sysfs-platform_profile
index 125324ab53a9..ee32e222af64 100644
--- a/Documentation/ABI/testing/sysfs-platform_profile
+++ b/Documentation/ABI/testing/sysfs-platform_profile
@@ -2,6 +2,8 @@ What: /sys/firmware/acpi/platform_profile_choices
Date: October 2020
Contact: Hans de Goede <hdegoede@redhat.com>
Description: This file contains a space-separated list of profiles supported for this device.
+ This is the ACPI compatibility interface for the system-wide
+ /sys/kernel/platform_profile/choices attribute.
Drivers must use the following standard profile-names:
@@ -23,7 +25,10 @@ Description: This file contains a space-separated list of profiles supported for
What: /sys/firmware/acpi/platform_profile
Date: October 2020
Contact: Hans de Goede <hdegoede@redhat.com>
-Description: Reading this file gives the current selected profile for this
+Description: This interface shares its state with
+ /sys/kernel/platform_profile/profile.
+
+ Reading this file gives the current selected profile for this
device. Writing this file with one of the strings from
platform_profile_choices changes the profile to the new value.
diff --git a/Documentation/userspace-api/sysfs-platform_profile.rst b/Documentation/userspace-api/sysfs-platform_profile.rst
index 6613e188242a..37b98e5075db 100644
--- a/Documentation/userspace-api/sysfs-platform_profile.rst
+++ b/Documentation/userspace-api/sysfs-platform_profile.rst
@@ -1,6 +1,8 @@
-=====================================================================
-Platform Profile Selection (e.g. /sys/firmware/acpi/platform_profile)
-=====================================================================
+.. SPDX-License-Identifier: GPL-2.0
+
+==========================
+Platform Profile Selection
+==========================
On modern systems the platform performance, temperature, fan and other
hardware related characteristics are often dynamically configurable. The
@@ -15,6 +17,21 @@ operation or towards performance.
The purpose of the platform_profile attribute is to offer a generic sysfs
API for selecting the platform profile of these automatic mechanisms.
+The framework is independent of the firmware interface used by a provider.
+Drivers can implement profile selection using ACPI, an embedded controller,
+or another platform-specific mechanism. The core does not discover providers
+or interpret firmware descriptions.
+
+The system-wide interface is ``/sys/kernel/platform_profile/``, containing
+``choices`` and ``profile`` attributes. These attributes are present while
+at least one provider is registered. Per-provider interfaces are available
+under ``/sys/class/platform-profile/``.
+
+On ACPI systems, ``/sys/firmware/acpi/platform_profile_choices`` and
+``/sys/firmware/acpi/platform_profile`` remain available as compatibility
+interfaces. They operate on the same providers as the system-wide interface,
+and changes are notified through both interfaces.
+
Note that this API is only for selecting the platform profile, it is
NOT a goal of this API to allow monitoring the resulting performance
characteristics. Monitoring performance is best done with device/vendor
@@ -52,13 +69,14 @@ Multiple driver support
=======================
When multiple drivers on a system advertise a platform profile handler, the
platform profile handler core will only advertise the profiles that are
-common between all drivers to the ``/sys/firmware/acpi`` interfaces.
+common between all drivers to the system-wide interface and its ACPI
+compatibility interface.
This is to ensure there is no ambiguity on what the profile names mean when
all handlers don't support a profile.
-Individual drivers will register a 'platform_profile' class device that has
-similar semantics as the ``/sys/firmware/acpi/platform_profile`` interface.
+Individual drivers register a ``platform-profile`` class device with
+``name``, ``choices`` and ``profile`` attributes.
To discover which driver is associated with a platform profile handler the
user can read the ``name`` attribute of the class device.
@@ -74,7 +92,10 @@ same system. If the selected profile by individual drivers differs the
platform profile handler core will display the profile 'custom' to indicate
that the profiles are not the same.
-While the ``platform_profile`` attribute has the value ``custom``, writing a
-common profile from ``platform_profile_choices`` to the platform_profile
-attribute of the platform profile handler core will set the profile for all
-drivers.
+While the system-wide ``profile`` attribute has the value ``custom``, writing
+a common profile from ``choices`` to it will set the profile for all drivers.
+``custom`` cannot be written to the system-wide interface.
+
+Profile changes are applied to providers sequentially. If a provider returns
+an error, providers updated earlier are not rolled back. Userspace can read
+the per-provider interfaces to determine their resulting state.
diff --git a/MAINTAINERS b/MAINTAINERS
index 8014b9f8253e..778275ff20b6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -285,10 +285,15 @@ B: https://bugzilla.kernel.org
T: git git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm
F: Documentation/ABI/testing/configfs-acpi
F: Documentation/ABI/testing/sysfs-bus-acpi
+F: Documentation/ABI/testing/sysfs-class-platform-profile
+F: Documentation/ABI/testing/sysfs-kernel-platform_profile
+F: Documentation/ABI/testing/sysfs-platform_profile
F: Documentation/firmware-guide/acpi/
+F: Documentation/userspace-api/sysfs-platform_profile.rst
F: arch/x86/kernel/acpi/
F: arch/x86/pci/acpi.c
F: drivers/acpi/
+F: drivers/platform/profile/
F: drivers/pci/*/*acpi*
F: drivers/pci/*acpi*
F: drivers/pnp/pnpacpi/
@@ -296,6 +301,7 @@ F: include/acpi/
F: include/linux/acpi.h
F: include/linux/fwnode.h
F: include/linux/fw_table.h
+F: include/linux/platform_profile.h
F: lib/fw_table.c
F: rust/helpers/acpi.c
F: rust/kernel/acpi.rs
diff --git a/include/linux/platform_profile.h b/include/linux/platform_profile.h
index 855b28340e95..f23f9827eac6 100644
--- a/include/linux/platform_profile.h
+++ b/include/linux/platform_profile.h
@@ -2,6 +2,9 @@
/*
* Platform profile sysfs interface
*
+ * Providers may use any firmware interface or implement profiles in software.
+ * The parent device does not need an ACPI companion or a firmware node.
+ *
* See Documentation/userspace-api/sysfs-platform_profile.rst for more
* information.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 6+ messages in thread
* Re: [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface
2026-10-14 13:24 ` [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface Daniel Lezcano
@ 2026-10-14 21:31 ` Armin Wolf
0 siblings, 0 replies; 6+ messages in thread
From: Armin Wolf @ 2026-10-14 21:31 UTC (permalink / raw)
To: Daniel Lezcano, rafael
Cc: Len Brown, Jonathan Corbet, Shuah Khan, Viresh Kumar,
Hans de Goede, Ilpo Järvinen, Maximilian Luz,
Shyam Sundar S K, Mark Pearson, Derek J. Clark, Daniel Lezcano,
Zhang Rui, Lukasz Luba, Jai Luthra, Greg Kroah-Hartman, Jack Wu,
Mario Limonciello (AMD), linux-acpi, linux-kernel, linux-doc,
linux-pm, platform-driver-x86
Am 14.10.26 um 15:24 schrieb Daniel Lezcano:
> The per-provider class interface is firmware-independent, but system-
> wide profile selection is only exposed under /sys/firmware/acpi.
>
> Add /sys/kernel/platform_profile/{choices,profile}, sharing the existing
> aggregate callbacks and semantics with the legacy interface. Keep
> attributes hidden until a provider is registered and remove them after
> the last provider.
>
> Notify both aggregate frontends on profile changes. Also notify the
> provider class device on a direct profile write, matching its documented
> POLLPRI ABI. Restore aggregate visibility after a failed registration.
IMHO the aggregate sysfs interface only exists for backwards-compatibility,
so i would be very happy if you could just make the associated code optional
and instead teach platform-profiles-daemon to use the individual class devices
instead.
Thanks,
Armin Wolf
> Assisted-by: OpenCode:gpt-6.1-sol
> Signed-off-by: Daniel Lezcano <daniel.lezcano@oss.qualcomm.com>
> ---
> drivers/platform/profile/core.c | 60 +++++++++++++++++++++++++++++----
> 1 file changed, 53 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/platform/profile/core.c b/drivers/platform/profile/core.c
> index f18fd96b942c..507b96bdfdb1 100644
> --- a/drivers/platform/profile/core.c
> +++ b/drivers/platform/profile/core.c
> @@ -26,9 +26,11 @@ static DEFINE_MUTEX(profile_lock);
> static DEFINE_MUTEX(profile_registration_lock);
> static LIST_HEAD(profile_handlers);
> static struct kobject *compat_kobj;
> +static struct kobject *profile_kobj;
>
> static void profile_notify(void)
> {
> + sysfs_notify(profile_kobj, NULL, "profile");
> if (compat_kobj)
> sysfs_notify(compat_kobj, NULL, "platform_profile");
> }
> @@ -250,6 +252,7 @@ static ssize_t profile_store(struct device *dev,
> ret = _store_class_profile(dev, &index);
> if (ret)
> return ret;
> + _notify_class_profile(dev, NULL);
> }
>
> profile_notify();
> @@ -324,7 +327,7 @@ static int _remove_hidden_choices(struct device *dev, void *arg)
> }
>
> /**
> - * platform_profile_choices_show - Show the available profile choices for legacy sysfs interface
> + * platform_profile_choices_show - Show the aggregate profile choices
> * @kobj: The kobject
> * @attr: The attribute
> * @buf: The buffer to write to
> @@ -360,7 +363,7 @@ static ssize_t platform_profile_choices_show(struct kobject *kobj,
> }
>
> /**
> - * _aggregate_profiles - Aggregate the profiles for legacy sysfs interface
> + * _aggregate_profiles - Aggregate the profiles across providers
> * @dev: The device
> * @data: The profile to return
> *
> @@ -385,7 +388,7 @@ static int _aggregate_profiles(struct device *dev, void *data)
> }
>
> /**
> - * _store_and_notify - Store and notify a class from legacy sysfs interface
> + * _store_and_notify - Store and notify a provider from an aggregate write
> * @dev: The device
> * @data: The profile to return
> *
> @@ -403,7 +406,7 @@ static int _store_and_notify(struct device *dev, void *data)
> }
>
> /**
> - * platform_profile_show - Show the current profile for legacy sysfs interface
> + * platform_profile_show - Show the current aggregate profile
> * @kobj: The kobject
> * @attr: The attribute
> * @buf: The buffer to write to
> @@ -431,7 +434,7 @@ static ssize_t platform_profile_show(struct kobject *kobj,
> }
>
> /**
> - * platform_profile_store - Set the profile for legacy sysfs interface
> + * platform_profile_store - Set the profile across all providers
> * @kobj: The kobject
> * @attr: The attribute
> * @buf: The buffer to read from
> @@ -493,8 +496,30 @@ static const struct attribute_group platform_profile_group = {
> .is_visible = profile_class_is_visible,
> };
>
> +static struct kobj_attribute aggregate_choices =
> + __ATTR(choices, 0444, platform_profile_choices_show, NULL);
> +static struct kobj_attribute aggregate_profile =
> + __ATTR(profile, 0644, platform_profile_show, platform_profile_store);
> +
> +static struct attribute *aggregate_attrs[] = {
> + &aggregate_choices.attr,
> + &aggregate_profile.attr,
> + NULL
> +};
> +
> +static const struct attribute_group aggregate_group = {
> + .attrs = aggregate_attrs,
> + .is_visible = profile_class_is_visible,
> +};
> +
> static int profile_update(void)
> {
> + int err;
> +
> + err = sysfs_update_group(profile_kobj, &aggregate_group);
> + if (err)
> + return err;
> +
> if (compat_kobj)
> return sysfs_update_group(compat_kobj, &platform_profile_group);
>
> @@ -502,7 +527,7 @@ static int profile_update(void)
> }
>
> /**
> - * platform_profile_notify - Notify class device and legacy sysfs interface
> + * platform_profile_notify - Notify the provider and aggregate sysfs interfaces
> * @dev: The class device
> */
> void platform_profile_notify(struct device *dev)
> @@ -643,6 +668,7 @@ struct device *platform_profile_register(struct device *dev, const char *name,
> cleanup_cur:
> scoped_guard(mutex, &profile_lock)
> list_del(&to_pprof_handler(ppdev)->node);
> + profile_update();
> device_unregister(ppdev);
>
> cleanup_ida:
> @@ -726,15 +752,33 @@ static int __init platform_profile_init(void)
> if (err)
> return err;
>
> + profile_kobj = kobject_create_and_add("platform_profile", kernel_kobj);
> + if (!profile_kobj) {
> + err = -ENOMEM;
> + goto unregister_class;
> + }
> +
> + err = sysfs_create_group(profile_kobj, &aggregate_group);
> + if (err)
> + goto put_kobj;
> +
> compat_kobj = platform_profile_compat_kobj();
> if (compat_kobj) {
> err = sysfs_create_group(compat_kobj, &platform_profile_group);
> if (err) {
> kobject_put(compat_kobj);
> - class_unregister(&platform_profile_class);
> + goto remove_group;
> }
> }
>
> + return 0;
> +
> +remove_group:
> + sysfs_remove_group(profile_kobj, &aggregate_group);
> +put_kobj:
> + kobject_put(profile_kobj);
> +unregister_class:
> + class_unregister(&platform_profile_class);
> return err;
> }
>
> @@ -744,6 +788,8 @@ static void __exit platform_profile_exit(void)
> sysfs_remove_group(compat_kobj, &platform_profile_group);
> kobject_put(compat_kobj);
> }
> + sysfs_remove_group(profile_kobj, &aggregate_group);
> + kobject_put(profile_kobj);
> class_unregister(&platform_profile_class);
> }
> subsys_initcall_sync(platform_profile_init);
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-10-14 21:31 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 13:24 [PATCH v1 0/4] Make platform profiles firmware independent Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 1/4] platform profile: Separate handler locking from sysfs lifetime Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 2/4] platform profile: Extract the core from ACPI Daniel Lezcano
2026-10-14 13:24 ` [PATCH v1 3/4] platform profile: Add firmware-neutral aggregate sysfs interface Daniel Lezcano
2026-10-14 21:31 ` Armin Wolf
2026-10-14 13:24 ` [PATCH v1 4/4] platform profile: Document firmware-independent profile selection Daniel Lezcano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox