* [PATCH v3 06/12] amd-pstate: Add sysfs support for floor_freq and floor_count
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
When Floor Performance feature is supported by the platform, expose
two sysfs files:
* amd_pstate_floor_freq to allow userspace to request the floor
frequency for each CPU.
* amd_pstate_floor_count which advertises the number of distinct
levels of floor frequencies supported on this platform.
Reset the floor_perf to bios_floor_perf in the suspend, offline, and
exit paths, and restore the value to the cached user-request
floor_freq on the resume and online paths mirroring how bios_min_perf
is handled for MSR_AMD_CPPC_REQ.
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
drivers/cpufreq/amd-pstate.c | 93 +++++++++++++++++++++++++++++++++---
drivers/cpufreq/amd-pstate.h | 2 +
2 files changed, 89 insertions(+), 6 deletions(-)
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index 53b8173ff183..a068c4457a8f 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -383,8 +383,10 @@ static int amd_pstate_init_floor_perf(struct cpufreq_policy *policy)
return ret;
}
- cpudata->bios_floor_perf = floor_perf;
+ cpudata->bios_floor_perf = floor_perf;
+ cpudata->floor_freq = perf_to_freq(cpudata->perf, cpudata->nominal_freq,
+ floor_perf);
return 0;
}
@@ -1288,6 +1290,46 @@ static ssize_t show_energy_performance_preference(
return sysfs_emit(buf, "%s\n", energy_perf_strings[preference]);
}
+static ssize_t store_amd_pstate_floor_freq(struct cpufreq_policy *policy,
+ const char *buf, size_t count)
+{
+ struct amd_cpudata *cpudata = policy->driver_data;
+ union perf_cached perf = READ_ONCE(cpudata->perf);
+ unsigned int freq;
+ u8 floor_perf;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &freq);
+ if (ret)
+ return ret;
+
+ if (freq < policy->cpuinfo.min_freq || freq > policy->max)
+ return -EINVAL;
+
+ floor_perf = freq_to_perf(perf, cpudata->nominal_freq, freq);
+ ret = amd_pstate_set_floor_perf(policy, floor_perf);
+
+ if (!ret)
+ cpudata->floor_freq = freq;
+
+ return ret ?: count;
+}
+
+static ssize_t show_amd_pstate_floor_freq(struct cpufreq_policy *policy, char *buf)
+{
+ struct amd_cpudata *cpudata = policy->driver_data;
+
+ return sysfs_emit(buf, "%u\n", cpudata->floor_freq);
+}
+
+static ssize_t show_amd_pstate_floor_count(struct cpufreq_policy *policy, char *buf)
+{
+ struct amd_cpudata *cpudata = policy->driver_data;
+ u8 count = cpudata->floor_perf_cnt;
+
+ return sysfs_emit(buf, "%u\n", count);
+}
+
cpufreq_freq_attr_ro(amd_pstate_max_freq);
cpufreq_freq_attr_ro(amd_pstate_lowest_nonlinear_freq);
@@ -1296,6 +1338,8 @@ cpufreq_freq_attr_ro(amd_pstate_prefcore_ranking);
cpufreq_freq_attr_ro(amd_pstate_hw_prefcore);
cpufreq_freq_attr_rw(energy_performance_preference);
cpufreq_freq_attr_ro(energy_performance_available_preferences);
+cpufreq_freq_attr_rw(amd_pstate_floor_freq);
+cpufreq_freq_attr_ro(amd_pstate_floor_count);
struct freq_attr_visibility {
struct freq_attr *attr;
@@ -1320,6 +1364,12 @@ static bool epp_visibility(void)
return cppc_state == AMD_PSTATE_ACTIVE;
}
+/* Determines whether amd_pstate_floor_freq related attributes should be visible */
+static bool floor_freq_visibility(void)
+{
+ return cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO);
+}
+
static struct freq_attr_visibility amd_pstate_attr_visibility[] = {
{&amd_pstate_max_freq, always_visible},
{&amd_pstate_lowest_nonlinear_freq, always_visible},
@@ -1328,6 +1378,8 @@ static struct freq_attr_visibility amd_pstate_attr_visibility[] = {
{&amd_pstate_hw_prefcore, prefcore_visibility},
{&energy_performance_preference, epp_visibility},
{&energy_performance_available_preferences, epp_visibility},
+ {&amd_pstate_floor_freq, floor_freq_visibility},
+ {&amd_pstate_floor_count, floor_freq_visibility},
};
static struct freq_attr **get_freq_attrs(void)
@@ -1748,24 +1800,39 @@ static int amd_pstate_epp_set_policy(struct cpufreq_policy *policy)
static int amd_pstate_cpu_online(struct cpufreq_policy *policy)
{
- return amd_pstate_cppc_enable(policy);
+ struct amd_cpudata *cpudata = policy->driver_data;
+ union perf_cached perf = READ_ONCE(cpudata->perf);
+ u8 cached_floor_perf;
+ int ret;
+
+ ret = amd_pstate_cppc_enable(policy);
+ if (ret)
+ return ret;
+
+ cached_floor_perf = freq_to_perf(perf, cpudata->nominal_freq, cpudata->floor_freq);
+ return amd_pstate_set_floor_perf(policy, cached_floor_perf);
}
static int amd_pstate_cpu_offline(struct cpufreq_policy *policy)
{
struct amd_cpudata *cpudata = policy->driver_data;
union perf_cached perf = READ_ONCE(cpudata->perf);
+ int ret;
/*
* Reset CPPC_REQ MSR to the BIOS value, this will allow us to retain the BIOS specified
* min_perf value across kexec reboots. If this CPU is just onlined normally after this, the
* limits, epp and desired perf will get reset to the cached values in cpudata struct
*/
- return amd_pstate_update_perf(policy, perf.bios_min_perf,
+ ret = amd_pstate_update_perf(policy, perf.bios_min_perf,
FIELD_GET(AMD_CPPC_DES_PERF_MASK, cpudata->cppc_req_cached),
FIELD_GET(AMD_CPPC_MAX_PERF_MASK, cpudata->cppc_req_cached),
FIELD_GET(AMD_CPPC_EPP_PERF_MASK, cpudata->cppc_req_cached),
false);
+ if (ret)
+ return ret;
+
+ return amd_pstate_set_floor_perf(policy, cpudata->bios_floor_perf);
}
static int amd_pstate_suspend(struct cpufreq_policy *policy)
@@ -1787,6 +1854,10 @@ static int amd_pstate_suspend(struct cpufreq_policy *policy)
if (ret)
return ret;
+ ret = amd_pstate_set_floor_perf(policy, cpudata->bios_floor_perf);
+ if (ret)
+ return ret;
+
/* set this flag to avoid setting core offline*/
cpudata->suspended = true;
@@ -1798,15 +1869,24 @@ static int amd_pstate_resume(struct cpufreq_policy *policy)
struct amd_cpudata *cpudata = policy->driver_data;
union perf_cached perf = READ_ONCE(cpudata->perf);
int cur_perf = freq_to_perf(perf, cpudata->nominal_freq, policy->cur);
+ u8 cached_floor_perf;
+ int ret;
/* Set CPPC_REQ to last sane value until the governor updates it */
- return amd_pstate_update_perf(policy, perf.min_limit_perf, cur_perf, perf.max_limit_perf,
- 0U, false);
+ ret = amd_pstate_update_perf(policy, perf.min_limit_perf, cur_perf, perf.max_limit_perf,
+ 0U, false);
+ if (ret)
+ return ret;
+
+ cached_floor_perf = freq_to_perf(perf, cpudata->nominal_freq, cpudata->floor_freq);
+ return amd_pstate_set_floor_perf(policy, cached_floor_perf);
}
static int amd_pstate_epp_resume(struct cpufreq_policy *policy)
{
struct amd_cpudata *cpudata = policy->driver_data;
+ union perf_cached perf = READ_ONCE(cpudata->perf);
+ u8 cached_floor_perf;
if (cpudata->suspended) {
int ret;
@@ -1819,7 +1899,8 @@ static int amd_pstate_epp_resume(struct cpufreq_policy *policy)
cpudata->suspended = false;
}
- return 0;
+ cached_floor_perf = freq_to_perf(perf, cpudata->nominal_freq, cpudata->floor_freq);
+ return amd_pstate_set_floor_perf(policy, cached_floor_perf);
}
static struct cpufreq_driver amd_pstate_driver = {
diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h
index f04561da4518..2f7a96836fcd 100644
--- a/drivers/cpufreq/amd-pstate.h
+++ b/drivers/cpufreq/amd-pstate.h
@@ -72,6 +72,7 @@ struct amd_aperf_mperf {
* @max_limit_freq: Cached value of policy->max (in khz)
* @nominal_freq: the frequency (in khz) that mapped to nominal_perf
* @lowest_nonlinear_freq: the frequency (in khz) that mapped to lowest_nonlinear_perf
+ * @floor_freq: Cached value of the user requested floor_freq
* @cur: Difference of Aperf/Mperf/tsc count between last and current sample
* @prev: Last Aperf/Mperf/tsc count value read from register
* @freq: current cpu frequency value (in khz)
@@ -101,6 +102,7 @@ struct amd_cpudata {
u32 max_limit_freq;
u32 nominal_freq;
u32 lowest_nonlinear_freq;
+ u32 floor_freq;
struct amd_aperf_mperf cur;
struct amd_aperf_mperf prev;
--
2.34.1
^ permalink raw reply related
* [PATCH v3 07/12] amd-pstate: Introduce a tracepoint trace_amd_pstate_cppc_req2()
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
Introduce a new tracepoint trace_amd_pstate_cppc_req2() to track
updates to MSR_AMD_CPPC_REQ2.
Invoke this while changing the Floor Perf.
Reviewed-by: Mario Limonciello <mario.limonciello@amd.com>
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
drivers/cpufreq/amd-pstate-trace.h | 35 ++++++++++++++++++++++++++++++
drivers/cpufreq/amd-pstate.c | 14 +++++++++---
2 files changed, 46 insertions(+), 3 deletions(-)
diff --git a/drivers/cpufreq/amd-pstate-trace.h b/drivers/cpufreq/amd-pstate-trace.h
index 32e1bdc588c5..91fa073b2be4 100644
--- a/drivers/cpufreq/amd-pstate-trace.h
+++ b/drivers/cpufreq/amd-pstate-trace.h
@@ -133,6 +133,41 @@ TRACE_EVENT(amd_pstate_epp_perf,
)
);
+TRACE_EVENT(amd_pstate_cppc_req2,
+
+ TP_PROTO(unsigned int cpu_id,
+ u8 floor_perf,
+ bool changed,
+ int err_code
+ ),
+
+ TP_ARGS(cpu_id,
+ floor_perf,
+ changed,
+ err_code),
+
+ TP_STRUCT__entry(
+ __field(unsigned int, cpu_id)
+ __field(u8, floor_perf)
+ __field(bool, changed)
+ __field(int, err_code)
+ ),
+
+ TP_fast_assign(
+ __entry->cpu_id = cpu_id;
+ __entry->floor_perf = floor_perf;
+ __entry->changed = changed;
+ __entry->err_code = err_code;
+ ),
+
+ TP_printk("cpu%u: floor_perf=%u, changed=%u (error = %d)",
+ __entry->cpu_id,
+ __entry->floor_perf,
+ __entry->changed,
+ __entry->err_code
+ )
+);
+
#endif /* _AMD_PSTATE_TRACE_H */
/* This part must be outside protection */
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index a068c4457a8f..5eae74a67aeb 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -333,6 +333,7 @@ static int amd_pstate_set_floor_perf(struct cpufreq_policy *policy, u8 perf)
{
struct amd_cpudata *cpudata = policy->driver_data;
u64 value, prev;
+ bool changed;
int ret;
if (!cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO))
@@ -341,17 +342,24 @@ static int amd_pstate_set_floor_perf(struct cpufreq_policy *policy, u8 perf)
value = prev = READ_ONCE(cpudata->cppc_req2_cached);
FIELD_MODIFY(AMD_CPPC_FLOOR_PERF_MASK, &value, perf);
- if (value == prev)
- return 0;
+ changed = value != prev;
+ if (!changed) {
+ ret = 0;
+ goto out_trace;
+ }
ret = wrmsrq_on_cpu(cpudata->cpu, MSR_AMD_CPPC_REQ2, value);
if (ret) {
+ changed = false;
pr_err("failed to set CPPC REQ2 value. Error (%d)\n", ret);
- return ret;
+ goto out_trace;
}
WRITE_ONCE(cpudata->cppc_req2_cached, value);
+out_trace:
+ if (trace_amd_pstate_cppc_req2_enabled())
+ trace_amd_pstate_cppc_req2(cpudata->cpu, perf, changed, ret);
return ret;
}
--
2.34.1
^ permalink raw reply related
* [PATCH v3 08/12] amd-pstate-ut: Add ability to run a single testcase
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
Currently when amd-pstate-ut test module is loaded, it runs all the
tests from amd_pstate_ut_cases[] array.
Add a module parameter named "run_only" that allows users to run a
single test from the array by specifying the test name string.
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
drivers/cpufreq/amd-pstate-ut.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c
index 447b9aa5ce40..35e453a49c0f 100644
--- a/drivers/cpufreq/amd-pstate-ut.c
+++ b/drivers/cpufreq/amd-pstate-ut.c
@@ -35,6 +35,10 @@
#include "amd-pstate.h"
+static char *run_only;
+module_param(run_only, charp, 0444);
+MODULE_PARM_DESC(run_only,
+ "Run only the named test case (default: run all)");
struct amd_pstate_ut_struct {
const char *name;
@@ -275,7 +279,12 @@ static int __init amd_pstate_ut_init(void)
u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases);
for (i = 0; i < arr_size; i++) {
- int ret = amd_pstate_ut_cases[i].func(i);
+ int ret;
+
+ if (run_only && strcmp(run_only, amd_pstate_ut_cases[i].name))
+ continue;
+
+ ret = amd_pstate_ut_cases[i].func(i);
if (ret)
pr_err("%-4d %-20s\t fail: %d!\n", i+1, amd_pstate_ut_cases[i].name, ret);
--
2.34.1
^ permalink raw reply related
* [PATCH v3 09/12] amd-pstate-ut: Add a testcase to validate the visibility of driver attributes
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
amd-pstate driver has per-attribute visibility functions to
dynamically control which sysfs freq_attrs are exposed based on the
platform capabilities and the current amd_pstate mode. However, there
is no test coverage to validate that the driver's live attribute list
matches the expected visibility for each mode.
Add amd_pstate_ut_check_freq_attrs() to the amd-pstate unit test
module. For each enabled mode (passive, active, guided), the test
independently derives the expected visibility of each attribute:
- Core attributes (max_freq, lowest_nonlinear_freq, highest_perf)
are always expected.
- Prefcore attributes (prefcore_ranking, hw_prefcore) are expected
only when cpudata->hw_prefcore indicates platform support.
- EPP attributes (energy_performance_preference,
energy_performance_available_preferences) are expected only in
active mode.
- Floor frequency attributes (floor_freq, floor_count) are expected
only when X86_FEATURE_CPPC_PERF_PRIO is present.
Compare these independent expectations against the live driver's attr
array, catching bugs such as attributes leaking into wrong modes or
visibility functions checking incorrect conditions.
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
drivers/cpufreq/amd-pstate-ut.c | 139 ++++++++++++++++++++++++++++++--
drivers/cpufreq/amd-pstate.c | 8 ++
drivers/cpufreq/amd-pstate.h | 4 +
3 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/cpufreq/amd-pstate-ut.c b/drivers/cpufreq/amd-pstate-ut.c
index 35e453a49c0f..5d87fb8a26df 100644
--- a/drivers/cpufreq/amd-pstate-ut.c
+++ b/drivers/cpufreq/amd-pstate-ut.c
@@ -23,6 +23,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitfield.h>
+#include <linux/cpufeature.h>
+#include <linux/cpufreq.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
@@ -53,13 +55,15 @@ static int amd_pstate_ut_check_enabled(u32 index);
static int amd_pstate_ut_check_perf(u32 index);
static int amd_pstate_ut_check_freq(u32 index);
static int amd_pstate_ut_check_driver(u32 index);
+static int amd_pstate_ut_check_freq_attrs(u32 index);
static struct amd_pstate_ut_struct amd_pstate_ut_cases[] = {
- {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid },
- {"amd_pstate_ut_check_enabled", amd_pstate_ut_check_enabled },
- {"amd_pstate_ut_check_perf", amd_pstate_ut_check_perf },
- {"amd_pstate_ut_check_freq", amd_pstate_ut_check_freq },
- {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver }
+ {"amd_pstate_ut_acpi_cpc_valid", amd_pstate_ut_acpi_cpc_valid },
+ {"amd_pstate_ut_check_enabled", amd_pstate_ut_check_enabled },
+ {"amd_pstate_ut_check_perf", amd_pstate_ut_check_perf },
+ {"amd_pstate_ut_check_freq", amd_pstate_ut_check_freq },
+ {"amd_pstate_ut_check_driver", amd_pstate_ut_check_driver },
+ {"amd_pstate_ut_check_freq_attrs", amd_pstate_ut_check_freq_attrs },
};
static bool get_shared_mem(void)
@@ -274,6 +278,131 @@ static int amd_pstate_ut_check_driver(u32 index)
return ret;
}
+enum attr_category {
+ ATTR_ALWAYS,
+ ATTR_PREFCORE,
+ ATTR_EPP,
+ ATTR_FLOOR_FREQ,
+};
+
+static const struct {
+ const char *name;
+ enum attr_category category;
+} expected_freq_attrs[] = {
+ {"amd_pstate_max_freq", ATTR_ALWAYS},
+ {"amd_pstate_lowest_nonlinear_freq", ATTR_ALWAYS},
+ {"amd_pstate_highest_perf", ATTR_ALWAYS},
+ {"amd_pstate_prefcore_ranking", ATTR_PREFCORE},
+ {"amd_pstate_hw_prefcore", ATTR_PREFCORE},
+ {"energy_performance_preference", ATTR_EPP},
+ {"energy_performance_available_preferences", ATTR_EPP},
+ {"amd_pstate_floor_freq", ATTR_FLOOR_FREQ},
+ {"amd_pstate_floor_count", ATTR_FLOOR_FREQ},
+};
+
+static bool attr_in_driver(struct freq_attr **driver_attrs, const char *name)
+{
+ int j;
+
+ for (j = 0; driver_attrs[j]; j++) {
+ if (!strcmp(driver_attrs[j]->attr.name, name))
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Verify that for each mode the driver's live ->attr array contains exactly
+ * the attributes that should be visible. Expected visibility is derived
+ * independently from hw_prefcore, cpu features, and the current mode —
+ * not from the driver's own visibility functions.
+ */
+static int amd_pstate_ut_check_freq_attrs(u32 index)
+{
+ enum amd_pstate_mode orig_mode = amd_pstate_get_status();
+ static const enum amd_pstate_mode modes[] = {
+ AMD_PSTATE_PASSIVE, AMD_PSTATE_ACTIVE, AMD_PSTATE_GUIDED,
+ };
+ bool has_prefcore, has_floor_freq;
+ int m, i, ret;
+
+ has_floor_freq = cpu_feature_enabled(X86_FEATURE_CPPC_PERF_PRIO);
+
+ /*
+ * Determine prefcore support from any online CPU's cpudata.
+ * hw_prefcore reflects the platform-wide decision made at init.
+ */
+ has_prefcore = false;
+ for_each_online_cpu(i) {
+ struct cpufreq_policy *policy __free(put_cpufreq_policy) = NULL;
+ struct amd_cpudata *cpudata;
+
+ policy = cpufreq_cpu_get(i);
+ if (!policy)
+ continue;
+ cpudata = policy->driver_data;
+ has_prefcore = cpudata->hw_prefcore;
+ break;
+ }
+
+ for (m = 0; m < ARRAY_SIZE(modes); m++) {
+ struct freq_attr **driver_attrs;
+
+ ret = amd_pstate_set_mode(modes[m]);
+ if (ret)
+ goto out;
+
+ driver_attrs = amd_pstate_get_current_attrs();
+ if (!driver_attrs) {
+ pr_err("%s: no driver attrs in mode %s\n",
+ __func__, amd_pstate_get_mode_string(modes[m]));
+ ret = -EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(expected_freq_attrs); i++) {
+ bool expected, found;
+
+ switch (expected_freq_attrs[i].category) {
+ case ATTR_ALWAYS:
+ expected = true;
+ break;
+ case ATTR_PREFCORE:
+ expected = has_prefcore;
+ break;
+ case ATTR_EPP:
+ expected = (modes[m] == AMD_PSTATE_ACTIVE);
+ break;
+ case ATTR_FLOOR_FREQ:
+ expected = has_floor_freq;
+ break;
+ default:
+ expected = false;
+ break;
+ }
+
+ found = attr_in_driver(driver_attrs,
+ expected_freq_attrs[i].name);
+
+ if (expected != found) {
+ pr_err("%s: mode %s: attr %s expected %s but is %s\n",
+ __func__,
+ amd_pstate_get_mode_string(modes[m]),
+ expected_freq_attrs[i].name,
+ expected ? "visible" : "hidden",
+ found ? "visible" : "hidden");
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ ret = 0;
+out:
+ amd_pstate_set_mode(orig_mode);
+ return ret;
+}
+
static int __init amd_pstate_ut_init(void)
{
u32 i = 0, arr_size = ARRAY_SIZE(amd_pstate_ut_cases);
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index 5eae74a67aeb..ed9fd4155a25 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -1390,6 +1390,14 @@ static struct freq_attr_visibility amd_pstate_attr_visibility[] = {
{&amd_pstate_floor_count, floor_freq_visibility},
};
+struct freq_attr **amd_pstate_get_current_attrs(void)
+{
+ if (!current_pstate_driver)
+ return NULL;
+ return current_pstate_driver->attr;
+}
+EXPORT_SYMBOL_GPL(amd_pstate_get_current_attrs);
+
static struct freq_attr **get_freq_attrs(void)
{
bool attr_visible[ARRAY_SIZE(amd_pstate_attr_visibility)];
diff --git a/drivers/cpufreq/amd-pstate.h b/drivers/cpufreq/amd-pstate.h
index 2f7a96836fcd..6551f559edf1 100644
--- a/drivers/cpufreq/amd-pstate.h
+++ b/drivers/cpufreq/amd-pstate.h
@@ -132,4 +132,8 @@ const char *amd_pstate_get_mode_string(enum amd_pstate_mode mode);
int amd_pstate_get_status(void);
int amd_pstate_update_status(const char *buf, size_t size);
+struct freq_attr;
+
+struct freq_attr **amd_pstate_get_current_attrs(void);
+
#endif /* _LINUX_AMD_PSTATE_H */
--
2.34.1
^ permalink raw reply related
* [PATCH v3 10/12] Documentation/amd-pstate: List amd_pstate_hw_prefcore sysfs file
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy, Jonathan Corbet,
Shuah Khan, Mario Limonciello
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
Add the missing amd_pstate_hw_prefcore filenames in the sysfs listing
example leading to the descriptions of these parameters. Clarify when
will the file be visible.
Fixes: b96b82d1af7f ("cpufreq: amd-pstate: Add documentation for `amd_pstate_hw_prefcore`")
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
Documentation/admin-guide/pm/amd-pstate.rst | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst
index e1771f2225d5..b8c846cbf301 100644
--- a/Documentation/admin-guide/pm/amd-pstate.rst
+++ b/Documentation/admin-guide/pm/amd-pstate.rst
@@ -239,6 +239,7 @@ control its functionality at the system level. They are located in the
root@hr-test1:/home/ray# ls /sys/devices/system/cpu/cpufreq/policy0/*amd*
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_highest_perf
+ /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_hw_prefcore
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_lowest_nonlinear_freq
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_max_freq
@@ -264,8 +265,9 @@ This attribute is read-only.
``amd_pstate_hw_prefcore``
-Whether the platform supports the preferred core feature and it has been
-enabled. This attribute is read-only.
+Whether the platform supports the preferred core feature and it has
+been enabled. This attribute is read-only. This file is only visible
+on platforms which support the preferred core feature.
``amd_pstate_prefcore_ranking``
--
2.34.1
^ permalink raw reply related
* [PATCH v3 11/12] Documentation/amd-pstate: List amd_pstate_prefcore_ranking sysfs file
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy, Jonathan Corbet,
Shuah Khan, Mario Limonciello
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
Add the missing amd_pstate_prefcore_ranking filenames in the sysfs
listing example leading to the descriptions of these
parameters. Clarify when will the file be visible.
Fixes: 15a2b764ea7c ("amd-pstate: Add missing documentation for `amd_pstate_prefcore_ranking`")
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
Documentation/admin-guide/pm/amd-pstate.rst | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst
index b8c846cbf301..b31a478c28ba 100644
--- a/Documentation/admin-guide/pm/amd-pstate.rst
+++ b/Documentation/admin-guide/pm/amd-pstate.rst
@@ -242,6 +242,7 @@ control its functionality at the system level. They are located in the
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_hw_prefcore
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_lowest_nonlinear_freq
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_max_freq
+ /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_prefcore_ranking
``amd_pstate_highest_perf / amd_pstate_max_freq``
@@ -273,7 +274,8 @@ on platforms which support the preferred core feature.
The performance ranking of the core. This number doesn't have any unit, but
larger numbers are preferred at the time of reading. This can change at
-runtime based on platform conditions. This attribute is read-only.
+runtime based on platform conditions. This attribute is read-only. This file
+is only visible on platforms which support the preferred core feature.
``energy_performance_available_preferences``
--
2.34.1
^ permalink raw reply related
* [PATCH v3 12/12] Documentation/amd-pstate: Add documentation for amd_pstate_floor_{freq,count}
From: Gautham R. Shenoy @ 2026-03-20 14:43 UTC (permalink / raw)
To: Mario Limonciello, Rafael J . Wysocki, Viresh Kumar,
K Prateek Nayak
Cc: linux-kernel, linux-pm, Gautham R. Shenoy, Jonathan Corbet,
Shuah Khan, Mario Limonciello
In-Reply-To: <20260320144321.18543-1-gautham.shenoy@amd.com>
Add documentation for the sysfs files
/sys/devices/system/cpu/cpufreq/policy*/amd_pstate_floor_freq
and
/sys/devices/system/cpu/cpufreq/policy*/amd_pstate_floor_count.
Reviewed-by: Mario Limonciello (AMD) <superm1@kernel.org>
Signed-off-by: Gautham R. Shenoy <gautham.shenoy@amd.com>
---
Documentation/admin-guide/pm/amd-pstate.rst | 32 +++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/Documentation/admin-guide/pm/amd-pstate.rst b/Documentation/admin-guide/pm/amd-pstate.rst
index b31a478c28ba..d6c2f233ab23 100644
--- a/Documentation/admin-guide/pm/amd-pstate.rst
+++ b/Documentation/admin-guide/pm/amd-pstate.rst
@@ -242,6 +242,8 @@ control its functionality at the system level. They are located in the
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_hw_prefcore
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_lowest_nonlinear_freq
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_max_freq
+ /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_floor_freq
+ /sys/devices/system/cpu/cpufreq/policy0/amd_pstate_floor_count
/sys/devices/system/cpu/cpufreq/policy0/amd_pstate_prefcore_ranking
@@ -277,6 +279,36 @@ larger numbers are preferred at the time of reading. This can change at
runtime based on platform conditions. This attribute is read-only. This file
is only visible on platforms which support the preferred core feature.
+``amd_pstate_floor_freq``
+
+The floor frequency associated with each CPU. Userspace can write any
+value between ``cpuinfo_min_freq`` and ``scaling_max_freq`` into this
+file. When the system is under power or thermal constraints, the
+platform firmware will attempt to throttle the CPU frequency to the
+value specified in ``amd_pstate_floor_freq`` before throttling it
+further. This allows userspace to specify different floor frequencies
+to different CPUs. For optimal results, threads of the same core
+should have the same floor frequency value. This file is only visible
+on platforms that support the CPPC Performance Priority feature.
+
+
+``amd_pstate_floor_count``
+
+The number of distinct Floor Performance levels supported by the
+platform. For example, if this value is 2, then the number of unique
+values obtained from the command ``cat
+/sys/devices/system/cpu/cpufreq/policy*/amd_pstate_floor_freq |
+sort -n | uniq`` should be at most this number for the behavior
+described in ``amd_pstate_floor_freq`` to take effect. A zero value
+implies that the platform supports unlimited floor performance levels.
+This file is only visible on platforms that support the CPPC
+Performance Priority feature.
+
+**Note**: When ``amd_pstate_floor_count`` is non-zero, the frequency to
+which the CPU is throttled under power or thermal constraints is
+undefined when the number of unique values of ``amd_pstate_floor_freq``
+across all CPUs in the system exceeds ``amd_pstate_floor_count``.
+
``energy_performance_available_preferences``
A list of all the supported EPP preferences that could be used for
--
2.34.1
^ permalink raw reply related
* [PATCH v1 0/2] *** Support BPF traversal of wakeup sources ***
From: Samuel Wu @ 2026-03-20 16:00 UTC (permalink / raw)
To: Rafael J. Wysocki, Pavel Machek, Len Brown, Greg Kroah-Hartman,
Danilo Krummrich
Cc: andrii, memxor, bpf, Samuel Wu, kernel-team, linux-pm,
driver-core, linux-kernel
This patchset adds requisite kfuncs for BPF programs to safely traverse
wakeup_sources, and puts a config flag around the sysfs interface.
Currently, a traversal of wakeup sources require going through
/sys/class/wakeup/* or /d/wakeup_sources/*. The repeated syscalls to query
sysfs is inefficient, as there can be hundreds of wakeup_sources, with each
wakeup source also having multiple attributes. debugfs is unstable and
insecure.
Adding kfuncs to lock/unlock wakeup sources allows BPF program to safely
traverse the wakeup sources list. The head address of wakeup_sources can
safely be resolved through BPF helper functions or variable attributes.
On a quiescent Pixel 6 traversing 150 wakeup_sources, I am seeing ~34x
speedup (sampled 75 times in table below). For a device under load, the
speedup is greater.
+-------+----+----------+----------+
| | n | AVG (ms) | STD (ms) |
+-------+----+----------+----------+
| sysfs | 75 | 44.9 | 12.6 |
+-------+----+----------+----------+
| BPF | 75 | 1.3 | 0.7 |
+-------+----+----------+----------+
On the memory side, between kernfs, dentry, and kmalloc, each wakeup source
removed from sysfs saves at least 10kB.
The initial attempts for BPF traversal of wakeup_sources was with BPF
iterators [1]. However, BPF already allows for traversing of a simple list
with bpf_for(), and this current patchset has the added benefit of being
~2-3x more performant than BPF iterators.
[1]: https://lore.kernel.org/all/20260225210820.177674-1-wusamuel@google.com/
Samuel Wu (2):
PM: wakeup: Add kfuncs to lock/unlock wakeup_sources
PM: Add config flag to gate sysfs wakeup_sources
drivers/base/power/Makefile | 3 +-
drivers/base/power/power.h | 14 +++++++++
drivers/base/power/wakeup.c | 63 +++++++++++++++++++++++++++++++++++--
kernel/power/Kconfig | 13 ++++++++
4 files changed, 90 insertions(+), 3 deletions(-)
--
2.53.0.959.g497ff81fa9-goog
^ permalink raw reply
* [PATCH v1 1/2] PM: wakeup: Add kfuncs to lock/unlock wakeup_sources
From: Samuel Wu @ 2026-03-20 16:00 UTC (permalink / raw)
To: Rafael J. Wysocki, Len Brown, Pavel Machek, Greg Kroah-Hartman,
Danilo Krummrich
Cc: andrii, memxor, bpf, Samuel Wu, kernel-team, linux-pm,
driver-core, linux-kernel
In-Reply-To: <20260320160055.4114055-1-wusamuel@google.com>
Add kfuncs to lock/unlock for safe traversal of wakeup sources.
Currently, a traversal of wakeup sources require going through
/sys/class/wakeup/* or /d/wakeup_sources/*. The repeated syscalls to
query sysfs is inefficient, as there can be hundreds of wakeup_sources,
with each wakeup source also having multiple attributes. debugfs is
unstable and insecure.
Adding kfuncs to lock/unlock wakeup sources allows BPF program to safely
traverse the wakeup sources list. A new structure, bpf_ws_lock, acts as
an opaque wrapper for the SRCU index. The head address of wakeup_sources
can be safely resolved through BPF helper functions or variable
attributes.
Doing the traversal in BPF is significantly more performant, and has an
output in a format that the user specifies; this solves all the
drawbacks of current interfaces.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
drivers/base/power/wakeup.c | 63 +++++++++++++++++++++++++++++++++++--
1 file changed, 61 insertions(+), 2 deletions(-)
diff --git a/drivers/base/power/wakeup.c b/drivers/base/power/wakeup.c
index b8e48a023bf0..7fc12ce125bc 100644
--- a/drivers/base/power/wakeup.c
+++ b/drivers/base/power/wakeup.c
@@ -1168,11 +1168,70 @@ static const struct file_operations wakeup_sources_stats_fops = {
.release = seq_release_private,
};
-static int __init wakeup_sources_debugfs_init(void)
+#ifdef CONFIG_BPF_SYSCALL
+#include <linux/btf.h>
+
+struct bpf_ws_lock { };
+
+__bpf_kfunc_start_defs();
+
+/**
+ * bpf_wakeup_sources_read_lock - Acquire the SRCU lock for wakeup sources
+ *
+ * The underlying SRCU lock returns an integer index. However, the BPF verifier
+ * requires a pointer (PTR_TO_BTF_ID) to strictly track the state of acquired
+ * resources using KF_ACQUIRE and KF_RELEASE semantics. We use an opaque
+ * structure pointer (struct bpf_ws_lock *) to satisfy the verifier while
+ * safely encoding the integer index within the pointer address itself.
+ *
+ * Return: An opaque pointer encoding the SRCU lock index + 1 (to avoid NULL).
+ */
+__bpf_kfunc struct bpf_ws_lock *bpf_wakeup_sources_read_lock(void)
+{
+ return (struct bpf_ws_lock *)(long)(wakeup_sources_read_lock() + 1);
+}
+
+/**
+ * bpf_wakeup_sources_read_unlock - Release the SRCU lock for wakeup sources
+ * @lock: The opaque pointer returned by bpf_wakeup_sources_read_lock()
+ *
+ * The BPF verifier guarantees that @lock is a valid, unreleased pointer from
+ * the acquire function. We decode the pointer back into the integer SRCU index
+ * by subtracting 1 and release the lock.
+ */
+__bpf_kfunc void bpf_wakeup_sources_read_unlock(struct bpf_ws_lock *lock)
+{
+ wakeup_sources_read_unlock((int)(long)lock - 1);
+}
+
+__bpf_kfunc_end_defs();
+
+BTF_KFUNCS_START(wakeup_source_kfunc_ids)
+BTF_ID_FLAGS(func, bpf_wakeup_sources_read_lock, KF_ACQUIRE)
+BTF_ID_FLAGS(func, bpf_wakeup_sources_read_unlock, KF_RELEASE)
+BTF_KFUNCS_END(wakeup_source_kfunc_ids)
+
+static const struct btf_kfunc_id_set wakeup_source_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &wakeup_source_kfunc_ids,
+};
+
+static void __init wakeup_sources_bpf_init(void)
+{
+ if (register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &wakeup_source_kfunc_set))
+ pm_pr_dbg("Wakeup: failed to register BTF kfuncs\n");
+}
+#else
+static inline void wakeup_sources_bpf_init(void) {}
+#endif /* CONFIG_BPF_SYSCALL */
+
+static int __init wakeup_sources_init(void)
{
debugfs_create_file("wakeup_sources", 0444, NULL, NULL,
&wakeup_sources_stats_fops);
+ wakeup_sources_bpf_init();
+
return 0;
}
-postcore_initcall(wakeup_sources_debugfs_init);
+postcore_initcall(wakeup_sources_init);
--
2.53.0.959.g497ff81fa9-goog
^ permalink raw reply related
* [PATCH v1 2/2] PM: Add config flag to gate sysfs wakeup_sources
From: Samuel Wu @ 2026-03-20 16:00 UTC (permalink / raw)
To: Rafael J. Wysocki, Pavel Machek, Len Brown, Greg Kroah-Hartman,
Danilo Krummrich
Cc: andrii, memxor, bpf, Samuel Wu, kernel-team, linux-pm,
driver-core, linux-kernel
In-Reply-To: <20260320160055.4114055-1-wusamuel@google.com>
Add a config flag that gates the creation of
/sys/class/wakeup_sources/*. This has the benefit of eliminating the
work needed to create the nodes and corresponding attributes; and
between kernfs, dentry, and malloc, there are some memory savings
depending on the number of wakeup sources.
Signed-off-by: Samuel Wu <wusamuel@google.com>
---
drivers/base/power/Makefile | 3 ++-
drivers/base/power/power.h | 14 ++++++++++++++
kernel/power/Kconfig | 13 +++++++++++++
3 files changed, 29 insertions(+), 1 deletion(-)
diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
index 2989e42d0161..5933dadc0dd9 100644
--- a/drivers/base/power/Makefile
+++ b/drivers/base/power/Makefile
@@ -1,6 +1,7 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
-obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
+obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
+obj-$(CONFIG_PM_WAKEUP_STATS_SYSFS) += wakeup_stats.o
obj-$(CONFIG_PM_TRACE_RTC) += trace.o
obj-$(CONFIG_HAVE_CLK) += clock_ops.o
obj-$(CONFIG_PM_QOS_KUNIT_TEST) += qos-test.o
diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
index 922ed457db19..364ca5512b4f 100644
--- a/drivers/base/power/power.h
+++ b/drivers/base/power/power.h
@@ -125,11 +125,25 @@ static inline bool device_pm_initialized(struct device *dev)
}
/* drivers/base/power/wakeup_stats.c */
+#ifdef CONFIG_PM_WAKEUP_STATS_SYSFS
extern int wakeup_source_sysfs_add(struct device *parent,
struct wakeup_source *ws);
extern void wakeup_source_sysfs_remove(struct wakeup_source *ws);
extern int pm_wakeup_source_sysfs_add(struct device *parent);
+#else /* !CONFIG_PM_WAKEUP_STATS_SYSFS */
+static inline int wakeup_source_sysfs_add(struct device *parent,
+ struct wakeup_source *ws)
+{
+ return 0;
+}
+static inline void wakeup_source_sysfs_remove(struct wakeup_source *ws) {}
+
+static inline int pm_wakeup_source_sysfs_add(struct device *parent)
+{
+ return 0;
+}
+#endif /* !CONFIG_PM_WAKEUP_STATS_SYSFS */
#else /* !CONFIG_PM_SLEEP */
diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
index 05337f437cca..6945083ab053 100644
--- a/kernel/power/Kconfig
+++ b/kernel/power/Kconfig
@@ -202,6 +202,19 @@ config PM_WAKELOCKS_GC
depends on PM_WAKELOCKS
default y
+config PM_WAKEUP_STATS_SYSFS
+ bool "Sysfs wakeup statistics"
+ depends on PM_SLEEP
+ default y
+ help
+ Enable this for wakeup statistics in sysfs under /sys/class/wakeup/
+
+ Disabling this option eliminates the work of creating the wakeup
+ sources and each of their attributes in sysfs. Depending on the
+ number of wakeup sources, this can also have a non-negligible memory
+ impact. Regardless of this config option's value, wakeup statistics
+ are still available via debugfs and BPF.
+
config PM_QOS_CPU_SYSTEM_WAKEUP
bool "User space interface for CPU system wakeup QoS"
depends on CPU_IDLE
--
2.53.0.959.g497ff81fa9-goog
^ permalink raw reply related
* Re: [PATCH v1 2/2] PM: Add config flag to gate sysfs wakeup_sources
From: Rafael J. Wysocki @ 2026-03-20 16:09 UTC (permalink / raw)
To: Samuel Wu
Cc: Rafael J. Wysocki, Pavel Machek, Len Brown, Greg Kroah-Hartman,
Danilo Krummrich, andrii, memxor, bpf, kernel-team, linux-pm,
driver-core, linux-kernel
In-Reply-To: <20260320160055.4114055-3-wusamuel@google.com>
On Fri, Mar 20, 2026 at 5:01 PM Samuel Wu <wusamuel@google.com> wrote:
>
> Add a config flag that gates the creation of
> /sys/class/wakeup_sources/*. This has the benefit of eliminating the
> work needed to create the nodes and corresponding attributes; and
> between kernfs, dentry, and malloc, there are some memory savings
> depending on the number of wakeup sources.
>
> Signed-off-by: Samuel Wu <wusamuel@google.com>
> ---
> drivers/base/power/Makefile | 3 ++-
> drivers/base/power/power.h | 14 ++++++++++++++
> kernel/power/Kconfig | 13 +++++++++++++
> 3 files changed, 29 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/base/power/Makefile b/drivers/base/power/Makefile
> index 2989e42d0161..5933dadc0dd9 100644
> --- a/drivers/base/power/Makefile
> +++ b/drivers/base/power/Makefile
> @@ -1,6 +1,7 @@
> # SPDX-License-Identifier: GPL-2.0
> obj-$(CONFIG_PM) += sysfs.o generic_ops.o common.o qos.o runtime.o wakeirq.o
> -obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o wakeup_stats.o
> +obj-$(CONFIG_PM_SLEEP) += main.o wakeup.o
> +obj-$(CONFIG_PM_WAKEUP_STATS_SYSFS) += wakeup_stats.o
> obj-$(CONFIG_PM_TRACE_RTC) += trace.o
> obj-$(CONFIG_HAVE_CLK) += clock_ops.o
> obj-$(CONFIG_PM_QOS_KUNIT_TEST) += qos-test.o
> diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h
> index 922ed457db19..364ca5512b4f 100644
> --- a/drivers/base/power/power.h
> +++ b/drivers/base/power/power.h
> @@ -125,11 +125,25 @@ static inline bool device_pm_initialized(struct device *dev)
> }
>
> /* drivers/base/power/wakeup_stats.c */
> +#ifdef CONFIG_PM_WAKEUP_STATS_SYSFS
> extern int wakeup_source_sysfs_add(struct device *parent,
> struct wakeup_source *ws);
> extern void wakeup_source_sysfs_remove(struct wakeup_source *ws);
>
> extern int pm_wakeup_source_sysfs_add(struct device *parent);
> +#else /* !CONFIG_PM_WAKEUP_STATS_SYSFS */
> +static inline int wakeup_source_sysfs_add(struct device *parent,
> + struct wakeup_source *ws)
> +{
> + return 0;
> +}
> +static inline void wakeup_source_sysfs_remove(struct wakeup_source *ws) {}
> +
> +static inline int pm_wakeup_source_sysfs_add(struct device *parent)
> +{
> + return 0;
> +}
> +#endif /* !CONFIG_PM_WAKEUP_STATS_SYSFS */
>
> #else /* !CONFIG_PM_SLEEP */
>
> diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig
> index 05337f437cca..6945083ab053 100644
> --- a/kernel/power/Kconfig
> +++ b/kernel/power/Kconfig
> @@ -202,6 +202,19 @@ config PM_WAKELOCKS_GC
> depends on PM_WAKELOCKS
> default y
>
> +config PM_WAKEUP_STATS_SYSFS
> + bool "Sysfs wakeup statistics"
> + depends on PM_SLEEP
> + default y
> + help
> + Enable this for wakeup statistics in sysfs under /sys/class/wakeup/
> +
> + Disabling this option eliminates the work of creating the wakeup
> + sources and each of their attributes in sysfs. Depending on the
> + number of wakeup sources, this can also have a non-negligible memory
> + impact. Regardless of this config option's value, wakeup statistics
> + are still available via debugfs and BPF.
Well, except that otherwise it may be hard to figure out which device
the given wakeup source belongs to, so I'd rather not do that.
> +
> config PM_QOS_CPU_SYSTEM_WAKEUP
> bool "User space interface for CPU system wakeup QoS"
> depends on CPU_IDLE
> --
^ permalink raw reply
* Re: [PATCH v3 2/4] serdev: add rust private data to serdev_device
From: Markus Probst @ 2026-03-20 16:53 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Rob Herring, Jiri Slaby, Miguel Ojeda, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, linux-serial, linux-kernel, rust-for-linux,
linux-pm, driver-core, dri-devel
In-Reply-To: <2026031447-margarita-untamed-e976@gregkh>
[-- Attachment #1: Type: text/plain, Size: 3925 bytes --]
On Sat, 2026-03-14 at 14:31 +0100, Greg Kroah-Hartman wrote:
> On Sat, Mar 14, 2026 at 12:08:09PM +0000, Markus Probst wrote:
> > On Sat, 2026-03-14 at 12:52 +0100, Greg Kroah-Hartman wrote:
> > > On Sat, Mar 14, 2026 at 11:42:02AM +0000, Markus Probst wrote:
> > > > On Sat, 2026-03-14 at 09:07 +0100, Greg Kroah-Hartman wrote:
> > > > > On Fri, Mar 13, 2026 at 06:12:31PM +0000, Markus Probst wrote:
> > > > > > Add rust private data to `struct serdev_device`, as it is required by the
> > > > > > rust abstraction added in the following commit
> > > > > > (rust: add basic serial device bus abstractions).
> > > > >
> > > > > why is rust "special" here? What's wrong with the existing private
> > > > > pointer in this structure? Why must we add another one?
> > > > Because in rust, the device drvdata will be set after probe has run. In
> > > > serdev, once the device has been opened, it can receive data. It must
> > > > be opened either inside probe or before probe, because it can only be
> > > > configured (baudrate, flow control etc.) and data written to after it
> > > > has been opened. Because it can receive data before drvdata has been
> > > > set yet, we need to ensure it waits on data receival for the probe to
> > > > be finished. Otherwise this would be a null pointer dereference. To do
> > > > this, we need to store a `Completion` for it to wait and a `bool` in
> > > > case the probe exits with an error. We cannot store this data in the
> > > > device drvdata, because this is where the drivers drvdata goes. We also
> > > > cannot create a wrapper of the drivers drvdata, because
> > > > `Device::drvdata::<T>()` would always fail in that case. That is why we
> > > > need a "rust_private_data" for this abstraction to store the
> > > > `Completion` and `bool`.
> > >
> > > So why is this any different from any other bus type? I don't see the
> > > "uniqueness" here that has not required this to happen for PCI or USB or
> > > anything else.
> > >
> > > What am I missing?
> > In Short:
> > In serdev, we have to handle incoming device data (serdev calls on a
> > function pointer we provide in advance), even in the case that the
> > driver hasn't completed probe yet.
>
> But how is that any different from a USB or PCI driver doing the same
> thing? Why is serdev so unique here?
>
In PCI or USB we don't need to provide function pointers for callbacks
in advance, which will be can be called any time (even while probe).
> What specific serdev function
> causes this
>
drivers/tty/serdev/serdev-ttyport.c basically only wraps the serdev
calls to tty calls. This isn't directly caused by a serdev function,
but by the tty part.
> why isn't it an issue with the C api?
>
In C you can set the drvdata inside the probe and even with it not
being fully initialized.
With Rust the drvdata is only available after the probe.
But there is the posibility of serdev calling the provided callback
inside probe.
> Can we change the
> C code to not require this?
Serdev is very closely linked to tty.
There are 3 options:
1. Add a `rust_serdev_device_open` and `rust_serdev_device_ready`
function. `rust_serdev_device_open` would do the same thing as
`serdev_device_open`, but with calling `tty_buffer_lock_exclusive`
before opening the underlying tty port. `rust_serdev_device_ready`
would call `tty_buffer_unlock_exclusive`. Such functions would then
need to exist for every serdev controller (currently there is only
ttyport as serdev controller).
2. Rewrite parts of the tty subsystem.
Not sure what would need to be changed there yet. But this could also
affect the existing tty drivers, which are a lot in comparision to
serdev.
3. Keep the `rust_private_data` pointer in `serdev_device`.
This seems to be the simplest option to me.
Thanks
- Markus Probst
>
> thanks,
>
> greg k-h
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]
^ permalink raw reply
* Re: [PATCH v7 4/7] dax: Track all dax_region allocations under a global resource tree
From: Koralahalli Channabasappa, Smita @ 2026-03-20 16:58 UTC (permalink / raw)
To: Jonathan Cameron, Smita Koralahalli
Cc: linux-cxl, linux-kernel, nvdimm, linux-fsdevel, linux-pm,
Ard Biesheuvel, Alison Schofield, Vishal Verma, Ira Weiny,
Dan Williams, Yazen Ghannam, Dave Jiang, Davidlohr Bueso,
Matthew Wilcox, Jan Kara, Rafael J . Wysocki, Len Brown,
Pavel Machek, Li Ming, Jeff Johnson, Ying Huang, Yao Xingtao,
Peter Zijlstra, Greg Kroah-Hartman, Nathan Fontenot, Terry Bowman,
Robert Richter, Benjamin Cheatham, Zhijian Li, Borislav Petkov,
Tomasz Wolski
In-Reply-To: <20260319135924.000060c2@huawei.com>
On 3/19/2026 6:59 AM, Jonathan Cameron wrote:
> On Thu, 19 Mar 2026 01:14:57 +0000
> Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com> wrote:
>
>> Introduce a global "DAX Regions" resource root and register each
>> dax_region->res under it via request_resource(). Release the resource on
>> dax_region teardown.
>>
>> By enforcing a single global namespace for dax_region allocations, this
>> ensures only one of dax_hmem or dax_cxl can successfully register a
>> dax_region for a given range.
>>
>> Suggested-by: Dan Williams <dan.j.williams@intel.com>
>> Signed-off-by: Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com>
>
> The comment below is about the existing code. If we decide not to tidy that
> up for now and you swap the ordering of release_resource() and sysfs_remove_groups()
> in unregister.
Okay I think I can do both.
>
> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
>
>> ---
>> drivers/dax/bus.c | 20 +++++++++++++++++---
>> 1 file changed, 17 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/dax/bus.c b/drivers/dax/bus.c
>> index c94c09622516..448e2bc285c3 100644
>> --- a/drivers/dax/bus.c
>> +++ b/drivers/dax/bus.c
>> @@ -10,6 +10,7 @@
>> #include "dax-private.h"
>> #include "bus.h"
>>
>> +static struct resource dax_regions = DEFINE_RES_MEM_NAMED(0, -1, "DAX Regions");
>> static DEFINE_MUTEX(dax_bus_lock);
>>
>> /*
>> @@ -625,6 +626,7 @@ static void dax_region_unregister(void *region)
>> {
>> struct dax_region *dax_region = region;
>>
>> + release_resource(&dax_region->res);
>
> Should reverse the line above and the line below so we unwind in reverse of
> setup. I doubt it matters in practice today but keeping ordering like that
> makes it much easier to see if a future patch messes things up.
Okay.
>
>> sysfs_remove_groups(&dax_region->dev->kobj,
>> dax_region_attribute_groups);
>> dax_region_put(dax_region);
>> @@ -635,6 +637,7 @@ struct dax_region *alloc_dax_region(struct device *parent, int region_id,
>> unsigned long flags)
>> {
>> struct dax_region *dax_region;
>> + int rc;
>>
>> /*
>> * The DAX core assumes that it can store its private data in
>> @@ -667,14 +670,25 @@ struct dax_region *alloc_dax_region(struct device *parent, int region_id,
>> .flags = IORESOURCE_MEM | flags,
>> };
>>
>> - if (sysfs_create_groups(&parent->kobj, dax_region_attribute_groups)) {
>> - kfree(dax_region);
>> - return NULL;
>> + rc = request_resource(&dax_regions, &dax_region->res);
>> + if (rc) {
>> + dev_dbg(parent, "dax_region resource conflict for %pR\n",
>> + &dax_region->res);
>> + goto err_res;
>> }
>>
>> + if (sysfs_create_groups(&parent->kobj, dax_region_attribute_groups))
>> + goto err_sysfs;
>> +
>> if (devm_add_action_or_reset(parent, dax_region_unregister, dax_region))
>
> This is curious. The code flips over to a kref_put() based release but we didn't
> do anything with the kref in the previous call. So whilst not 'buggy' as such
> it's definitely inconsistent and we should clean it up.
>
> This should really have been doing the release via dax_region_put() from the
> kref_init(). In practice that means never calling kfree(dax_regions) error paths
> because the kref_init() is just after the allocation. Instead call dax_region_put()
> in all those error paths.
>
>
>
>> return NULL;
>> return dax_region;
>> +
>> +err_sysfs:
>> + release_resource(&dax_region->res);
>> +err_res:
>> + kfree(dax_region);
>
> From above I think this should be
> dax_region_put(dax_region);
Thank you for pointing this out. I will have a separate patch for this
change first in the series.
Thanks
Smita
>
>> + return NULL;
>> }
>> EXPORT_SYMBOL_GPL(alloc_dax_region);
>>
>
^ permalink raw reply
* Re: [PATCH v7 7/7] dax/hmem: Reintroduce Soft Reserved ranges back into the iomem tree
From: Koralahalli Channabasappa, Smita @ 2026-03-20 17:00 UTC (permalink / raw)
To: Jonathan Cameron, Smita Koralahalli
Cc: linux-cxl, linux-kernel, nvdimm, linux-fsdevel, linux-pm,
Ard Biesheuvel, Alison Schofield, Vishal Verma, Ira Weiny,
Dan Williams, Yazen Ghannam, Dave Jiang, Davidlohr Bueso,
Matthew Wilcox, Jan Kara, Rafael J . Wysocki, Len Brown,
Pavel Machek, Li Ming, Jeff Johnson, Ying Huang, Yao Xingtao,
Peter Zijlstra, Greg Kroah-Hartman, Nathan Fontenot, Terry Bowman,
Robert Richter, Benjamin Cheatham, Zhijian Li, Borislav Petkov,
Tomasz Wolski
In-Reply-To: <20260319143549.00005394@huawei.com>
On 3/19/2026 7:35 AM, Jonathan Cameron wrote:
> On Thu, 19 Mar 2026 01:15:00 +0000
> Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com> wrote:
>
>> Reworked from a patch by Alison Schofield <alison.schofield@intel.com>
>>
>> Reintroduce Soft Reserved range into the iomem_resource tree for HMEM
>> to consume.
>>
>> This restores visibility in /proc/iomem for ranges actively in use, while
>> avoiding the early-boot conflicts that occurred when Soft Reserved was
>> published into iomem before CXL window and region discovery.
>>
>> Link: https://lore.kernel.org/linux-cxl/29312c0765224ae76862d59a17748c8188fb95f1.1692638817.git.alison.schofield@intel.com/
>> Co-developed-by: Alison Schofield <alison.schofield@intel.com>
>> Signed-off-by: Alison Schofield <alison.schofield@intel.com>
>> Co-developed-by: Zhijian Li <lizhijian@fujitsu.com>
>> Signed-off-by: Zhijian Li <lizhijian@fujitsu.com>
>> Signed-off-by: Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com>
>> Reviewed-by: Dave Jiang <dave.jiang@intel.com>
>> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
>> Reviewed-by: Dan Williams <dan.j.williams@intel.com>
> One minor update needed as kmalloc_obj() has shown up in meantime.
>
> Thanks
>
> Jonathan
>> ---
>> drivers/dax/hmem/hmem.c | 32 +++++++++++++++++++++++++++++++-
>> 1 file changed, 31 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c
>> index 8c574123bd3b..15e462589b92 100644
>> --- a/drivers/dax/hmem/hmem.c
>> +++ b/drivers/dax/hmem/hmem.c
>> @@ -72,6 +72,34 @@ void dax_hmem_flush_work(void)
>> }
>> EXPORT_SYMBOL_GPL(dax_hmem_flush_work);
>>
>> +static void remove_soft_reserved(void *r)
>> +{
>> + remove_resource(r);
>> + kfree(r);
>> +}
>> +
>> +static int add_soft_reserve_into_iomem(struct device *host,
>> + const struct resource *res)
>> +{
>> + int rc;
>> +
>> + struct resource *soft __free(kfree) =
>> + kmalloc(sizeof(*res), GFP_KERNEL);
>
> Update to
>
> struct resource *soft __free(kfree) = kmalloc_obj(*soft);
>
> Got added in 7.0 with lots of call sites updated via scripting.
>
> Not sure why this had sizeof(*res) rather than sizeof(*soft).
> Same type but should have been soft! If nothing else that would
> probably have broken the scripts looking for where we should
> be using kmalloc_obj().
Okay I will update it. sizeof(*res) was a typo from my end. Sorry.
Will change to kmalloc_obj().
Thanks
Smita
>
>
>
>> + if (!soft)
>> + return -ENOMEM;
>> +
>> + *soft = DEFINE_RES_NAMED_DESC(res->start, (res->end - res->start + 1),
>> + "Soft Reserved", IORESOURCE_MEM,
>> + IORES_DESC_SOFT_RESERVED);
>> +
>> + rc = insert_resource(&iomem_resource, soft);
>> + if (rc)
>> + return rc;
>> +
>> + return devm_add_action_or_reset(host, remove_soft_reserved,
>> + no_free_ptr(soft));
>> +}
>> +
>> static int hmem_register_device(struct device *host, int target_nid,
>> const struct resource *res)
>> {
>> @@ -94,7 +122,9 @@ static int hmem_register_device(struct device *host, int target_nid,
>> if (rc != REGION_INTERSECTS)
>> return 0;
>>
>> - /* TODO: Add Soft-Reserved memory back to iomem */
>> + rc = add_soft_reserve_into_iomem(host, res);
>> + if (rc)
>> + return rc;
>>
>> id = memregion_alloc(GFP_KERNEL);
>> if (id < 0) {
>
^ permalink raw reply
* Re: [PATCH v3 2/4] serdev: add rust private data to serdev_device
From: Greg Kroah-Hartman @ 2026-03-20 16:59 UTC (permalink / raw)
To: Markus Probst
Cc: Rob Herring, Jiri Slaby, Miguel Ojeda, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, linux-serial, linux-kernel, rust-for-linux,
linux-pm, driver-core, dri-devel
In-Reply-To: <9e07a53053d1dd75a4ebe94aaa06e2279ef3701d.camel@posteo.de>
On Fri, Mar 20, 2026 at 04:53:09PM +0000, Markus Probst wrote:
> On Sat, 2026-03-14 at 14:31 +0100, Greg Kroah-Hartman wrote:
> > On Sat, Mar 14, 2026 at 12:08:09PM +0000, Markus Probst wrote:
> > > On Sat, 2026-03-14 at 12:52 +0100, Greg Kroah-Hartman wrote:
> > > > On Sat, Mar 14, 2026 at 11:42:02AM +0000, Markus Probst wrote:
> > > > > On Sat, 2026-03-14 at 09:07 +0100, Greg Kroah-Hartman wrote:
> > > > > > On Fri, Mar 13, 2026 at 06:12:31PM +0000, Markus Probst wrote:
> > > > > > > Add rust private data to `struct serdev_device`, as it is required by the
> > > > > > > rust abstraction added in the following commit
> > > > > > > (rust: add basic serial device bus abstractions).
> > > > > >
> > > > > > why is rust "special" here? What's wrong with the existing private
> > > > > > pointer in this structure? Why must we add another one?
> > > > > Because in rust, the device drvdata will be set after probe has run. In
> > > > > serdev, once the device has been opened, it can receive data. It must
> > > > > be opened either inside probe or before probe, because it can only be
> > > > > configured (baudrate, flow control etc.) and data written to after it
> > > > > has been opened. Because it can receive data before drvdata has been
> > > > > set yet, we need to ensure it waits on data receival for the probe to
> > > > > be finished. Otherwise this would be a null pointer dereference. To do
> > > > > this, we need to store a `Completion` for it to wait and a `bool` in
> > > > > case the probe exits with an error. We cannot store this data in the
> > > > > device drvdata, because this is where the drivers drvdata goes. We also
> > > > > cannot create a wrapper of the drivers drvdata, because
> > > > > `Device::drvdata::<T>()` would always fail in that case. That is why we
> > > > > need a "rust_private_data" for this abstraction to store the
> > > > > `Completion` and `bool`.
> > > >
> > > > So why is this any different from any other bus type? I don't see the
> > > > "uniqueness" here that has not required this to happen for PCI or USB or
> > > > anything else.
> > > >
> > > > What am I missing?
> > > In Short:
> > > In serdev, we have to handle incoming device data (serdev calls on a
> > > function pointer we provide in advance), even in the case that the
> > > driver hasn't completed probe yet.
> >
> > But how is that any different from a USB or PCI driver doing the same
> > thing? Why is serdev so unique here?
> >
> In PCI or USB we don't need to provide function pointers for callbacks
> in advance, which will be can be called any time (even while probe).
All class drivers are like this, once you register with them, the
function pointers you provide can be called before your probe call ends.
So this isn't unique as far as I can tell.
> > What specific serdev function
> > causes this
> >
> drivers/tty/serdev/serdev-ttyport.c basically only wraps the serdev
> calls to tty calls. This isn't directly caused by a serdev function,
> but by the tty part.
>
> > why isn't it an issue with the C api?
> >
> In C you can set the drvdata inside the probe and even with it not
> being fully initialized.
>
> With Rust the drvdata is only available after the probe.
> But there is the posibility of serdev calling the provided callback
> inside probe.
Other classes have this same issue, so why isn't this a problem for HID
and input and the like?
thanks,
greg k-h
^ permalink raw reply
* Re: [PATCH v1 0/6] powercap: intel_rapl: Move primitives to interface drivers
From: srinivas pandruvada @ 2026-03-20 17:02 UTC (permalink / raw)
To: Kuppuswamy Sathyanarayanan, Rafael J . Wysocki, Daniel Lezcano
Cc: Zhang Rui, Lukasz Luba, linux-pm, linux-kernel
In-Reply-To: <20260313185333.2370733-1-sathyanarayanan.kuppuswamy@linux.intel.com>
On Fri, 2026-03-13 at 11:53 -0700, Kuppuswamy Sathyanarayanan wrote:
> RAPL supports multiple interfaces (MSR, MMIO, and TPMI), each with
> their
> own register layouts and bitmask definitions. Currently, the
> primitive
> tables and bitmasks for all three interfaces live in the common
> driver,
> which requires interface-specific handling logic in shared code and
> makes
> the common layer unnecessarily complex.
>
> This series cleans this up by moving each interface's primitive
> definitions into its own driver, so the common driver no longer needs
> to
> know anything about per-interface register layouts.
>
> All patches are no-functional-change refactoring.
>
> This series is based on top of the earlier RAPL cleanup series:
>
>
> https://lore.kernel.org/linux-pm/20260212233044.329790-1-sathyanarayanan.kuppuswamy@linux.intel.com/T/#t
>
For whole series:
Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
> Patch Summary:
> Patch 1/6: Remove unused AVERAGE_POWER primitive and
> RAPL_PRIMITIVE_DERIVED
> flag from the common driver.
> Patch 2/6: Remove other unused macro definitions from the common
> driver.
> Patch 3/6: Move struct rapl_primitive_info, enum unit_type, and
> PRIMITIVE_INFO_INIT macro to intel_rapl.h to allow
> interface
> drivers to define their own primitive tables.
> Patch 4/6: Move TPMI primitive definitions and associated bitmasks
> into
> the TPMI interface driver.
> Patch 5/6: Move MMIO primitive definitions and associated bitmasks
> into
> the MMIO interface driver.
> Patch 6/6: Move MSR primitive definitions and associated bitmasks
> into
> the MSR interface driver.
>
> Kuppuswamy Sathyanarayanan (6):
> powercap: intel_rapl: Remove unused AVERAGE_POWER primitive
> powercap: intel_rapl: Remove unused macro definitions
> powercap: intel_rapl: Move primitive info to header for interface
> drivers
> powercap: intel_rapl: Move TPMI primitives to TPMI driver
> thermal: intel: int340x: processor: Move MMIO primitives to MMIO
> driver
> powercap: intel_rapl: Move MSR primitives to MSR driver
>
> drivers/powercap/intel_rapl_common.c | 206 ----------------
> --
> drivers/powercap/intel_rapl_msr.c | 99 +++++++++
> drivers/powercap/intel_rapl_tpmi.c | 53 +++++
> .../int340x_thermal/processor_thermal_rapl.c | 72 ++++++
> include/linux/intel_rapl.h | 33 ++-
> 5 files changed, 254 insertions(+), 209 deletions(-)
^ permalink raw reply
* [PATCH v6 3/3] PM: hibernate: fix spurious GFP mask WARNING in uswsusp path
From: Youngjun Park @ 2026-03-20 17:03 UTC (permalink / raw)
To: rafael, akpm
Cc: chrisl, kasong, pavel, shikemeng, nphamcs, bhe, baohua,
youngjun.park, usama.arif, linux-pm, linux-mm
In-Reply-To: <20260320170313.163386-1-youngjun.park@lge.com>
Commit 35e4a69b2003f ("PM: sleep: Allow pm_restrict_gfp_mask()
stacking") introduced refcount-based GFP mask management that warns
when pm_restore_gfp_mask() is called with saved_gfp_count == 0:
WARNING: kernel/power/main.c:44 at pm_restore_gfp_mask+0xd7/0xf0
CPU: 0 UID: 0 PID: 373 Comm: s2disk
Call Trace:
snapshot_ioctl+0x964/0xbd0
__x64_sys_ioctl+0x724/0x1320
...
The uswsusp path calls pm_restore_gfp_mask() defensively in
SNAPSHOT_CREATE_IMAGE, SNAPSHOT_UNFREEZE, and snapshot_release(),
where the GFP mask may or may not be restricted depending on the
execution path.
Before the stacking change this was a silent no-op; it now triggers
a WARNING when saved_gfp_count is 0.
Introduce pm_restore_gfp_mask_safe(), which skips the call if
saved_gfp_count is 0. This avoids the warning without requiring
state tracking in snapshot_ioctl, which could otherwise leave the
GFP mask permanently restricted if mismanaged.
Fixes: 35e4a69b2003f ("PM: sleep: Allow pm_restrict_gfp_mask() stacking")
Signed-off-by: Youngjun Park <youngjun.park@lge.com>
---
include/linux/suspend.h | 1 +
kernel/power/main.c | 18 ++++++++++++++++++
kernel/power/user.c | 6 +++---
3 files changed, 22 insertions(+), 3 deletions(-)
diff --git a/include/linux/suspend.h b/include/linux/suspend.h
index b02876f1ae38..7777931d88a5 100644
--- a/include/linux/suspend.h
+++ b/include/linux/suspend.h
@@ -454,6 +454,7 @@ extern void pm_report_hw_sleep_time(u64 t);
extern void pm_report_max_hw_sleep(u64 t);
void pm_restrict_gfp_mask(void);
void pm_restore_gfp_mask(void);
+void pm_restore_gfp_mask_safe(void);
#define pm_notifier(fn, pri) { \
static struct notifier_block fn##_nb = \
diff --git a/kernel/power/main.c b/kernel/power/main.c
index 5f8c9e12eaec..90e9bd56a433 100644
--- a/kernel/power/main.c
+++ b/kernel/power/main.c
@@ -36,6 +36,24 @@
static unsigned int saved_gfp_count;
static gfp_t saved_gfp_mask;
+/**
+ * pm_restore_gfp_mask_safe - Conditionally restore the GFP mask
+ *
+ * Call pm_restore_gfp_mask() only if a GFP restriction is active.
+ *
+ * After GFP mask stacking was introduced, calling
+ * pm_restore_gfp_mask() without a matching restriction triggers a
+ * warning. Some hibernation paths invoke restore defensively, so this
+ * helper avoids spurious warnings when no restriction is in place.
+ */
+void pm_restore_gfp_mask_safe(void)
+{
+ WARN_ON(!mutex_is_locked(&system_transition_mutex));
+ if (!saved_gfp_count)
+ return;
+ pm_restore_gfp_mask();
+}
+
void pm_restore_gfp_mask(void)
{
WARN_ON(!mutex_is_locked(&system_transition_mutex));
diff --git a/kernel/power/user.c b/kernel/power/user.c
index aab9aece1009..88de4b76a9dc 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -119,7 +119,7 @@ static int snapshot_release(struct inode *inode, struct file *filp)
free_all_swap_pages(data->swap);
unpin_hibernation_swap_type(data->swap);
if (data->frozen) {
- pm_restore_gfp_mask();
+ pm_restore_gfp_mask_safe();
free_basic_memory_bitmaps();
thaw_processes();
} else if (data->free_bitmaps) {
@@ -306,7 +306,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
case SNAPSHOT_UNFREEZE:
if (!data->frozen || data->ready)
break;
- pm_restore_gfp_mask();
+ pm_restore_gfp_mask_safe();
free_basic_memory_bitmaps();
data->free_bitmaps = false;
thaw_processes();
@@ -318,7 +318,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd,
error = -EPERM;
break;
}
- pm_restore_gfp_mask();
+ pm_restore_gfp_mask_safe();
error = hibernation_snapshot(data->platform_support);
if (!error) {
error = put_user(in_suspend, (int __user *)arg);
--
2.34.1
^ permalink raw reply related
* [PATCH v6 0/3] mm/swap, PM: hibernate: fix swapoff race and optimize swap
From: Youngjun Park @ 2026-03-20 17:03 UTC (permalink / raw)
To: rafael, akpm
Cc: chrisl, kasong, pavel, shikemeng, nphamcs, bhe, baohua,
youngjun.park, usama.arif, linux-pm, linux-mm
Currently, in the uswsusp path, only the swap type value is retrieved at
lookup time without holding a reference. If swapoff races after the type
is acquired, subsequent slot allocations operate on a stale swap device.
Additionally, grabbing and releasing the swap device reference on every
slot allocation is inefficient across the entire hibernation swap path.
This patch series addresses these issues:
- Patch 1: Fixes the swapoff race in uswsusp by pinning the swap device
from the point it is looked up until the session completes.
- Patch 2: Removes the overhead of per-slot reference counting in alloc/free
paths and cleans up the redundant SWP_WRITEOK check.
- Patch 3: Fixes a spurious WARNING in the uswsusp GFP mask restore path.
(Found during uswsusp test)
Links:
RFC v1: https://lore.kernel.org/linux-mm/20260305202413.1888499-1-usama.arif@linux.dev/T/#m3693d45180f14f441b6951984f4b4bfd90ec0c9d
RFC v2: https://lore.kernel.org/linux-mm/20260306024608.1720991-1-youngjun.park@lge.com/
RFC v3: https://lore.kernel.org/linux-mm/20260312112511.3596781-1-youngjun.park@lge.com/
v4: https://lore.kernel.org/linux-mm/abv+rjgyArqZ2uym@yjaykim-PowerEdge-T330/T/#m924fa3e58d0f0da488300653163ee8db7e870e4a
v5: https://lore.kernel.org/linux-mm/ab0YEn+Fd41q6LM7@yjaykim-PowerEdge-T330/T/#m8409d470c68cb152b0849940759bff7d7806f397
Testing:
- Hibernate/resume via sysfs
(echo reboot > /sys/power/disk && echo disk > /sys/power/state)
- Hibernate with suspend via sysfs
(echo suspend > /sys/power/disk && echo disk > /sys/power/state)
- Hibernate/resume via uswsusp (suspend-utils s2disk/resume on QEMU)
- Verified swap I/O works correctly after resume.
- Verified swapoff succeeds after snapshot resume completes.
- Verified pm_restore_gfp_mask() WARNING no longer triggers (Patch 3).
- Verified SNAPSHOT_FREEZE followed by snapshot_release() does not
trigger pm_restore_gfp_mask() WARNING (Patch 3).
- swapoff during active uswsusp session:
- Verified swapoff returns -EBUSY while swap device is pinned (Patch 1).
- Verified swapoff succeeds after uswsusp process terminates.
Changelog:
v5 -> v6:
- Replaced get/put reference approach with SWP_HIBERNATION
pinning to prevent swapoff, per Kairui's feedback. Renamed helpers
from get/find/put_hibernation_swap_type() to
pin/find/unpin_hibernation_swap_type().
- Removed swapoff waiting on hibernation reference.
swapoff now returns -EBUSY immediately when the swap device is
pinned.
- Updated function comments per Kairui's review.
- Updated commit message.
- Fixed pm_restore_gfp_mask_safe() to use saved_gfp_count
check instead of saved_gfp_mask, and hold system_transition_mutex
for the check. Addressed AI review finding that SNAPSHOT_FREEZE
followed by snapshot_release() could still trigger the WARNING.
v4 -> v5:
- Rebased onto v7.0-rc4 (Rafael J. Wysocki comment)
- No functional changes. rebase conflict fix.
rfc v3 -> v4:
- Introduced get/find/put_hibernation_swap_type() helpers per Kairui's
feedback. find_ for lookup-only, get/put for reference management.
- Switched to swap_type_to_info() and added type < 0 check per
Kairui's suggestion.
- Fixed get_hibernation_swap_type() return when ref == false (Reviewed by Kairui)
- Made swapoff wait interruptible to prevent hang when uswsusp
holds a swap reference.
- Fixed spurious WARN_ON in pm_restore_gfp_mask() by introducing
pm_restore_gfp_mask_safe() (Patch 3).
- Updated commit messages and added comments for clarity.
- Rebased onto latest mm-new tree.
rfc v2 -> rfc v3:
- Split into 2 patches per Chris Li's feedback.
- Simplified by not holding reference in normal hibernation path
per Chris Li's suggestion.
- Removed redundant SWP_WRITEOK check.
- Rebased onto f543926f9d0c3f6dfb354adfe7fbaeedd1277c6b.
rfc v1 -> rfc v2:
- Squashed into single patch per Usama Arif's feedback.
Youngjun Park (3):
mm/swap, PM: hibernate: fix swapoff race in uswsusp by pinning swap
device
mm/swap: remove redundant swap device reference in alloc/free
PM: hibernate: fix spurious GFP mask WARNING in uswsusp path
include/linux/suspend.h | 1 +
include/linux/swap.h | 5 +-
kernel/power/main.c | 18 ++++
kernel/power/swap.c | 2 +-
kernel/power/user.c | 21 +++--
mm/swapfile.c | 178 +++++++++++++++++++++++++++++++---------
6 files changed, 178 insertions(+), 47 deletions(-)
base-commit: f338e77383789c0cae23ca3d48adcc5e9e137e3c
--
2.34.1
^ permalink raw reply
* [PATCH v6 1/3] mm/swap, PM: hibernate: fix swapoff race in uswsusp by pinning swap device
From: Youngjun Park @ 2026-03-20 17:03 UTC (permalink / raw)
To: rafael, akpm
Cc: chrisl, kasong, pavel, shikemeng, nphamcs, bhe, baohua,
youngjun.park, usama.arif, linux-pm, linux-mm
In-Reply-To: <20260320170313.163386-1-youngjun.park@lge.com>
Hibernation via uswsusp (/dev/snapshot ioctls) has a race window:
after selecting the resume swap area but before user space is frozen,
swapoff may run and invalidate the selected swap device.
Fix this by pinning the swap device with SWP_HIBERNATION while it is
in use. The pin is exclusive, which is sufficient since
hibernate_acquire() already prevents concurrent hibernation sessions.
The kernel swsusp path (sysfs-based hibernate/resume) uses
find_hibernation_swap_type() which is not affected by the pin. It
freezes user space before touching swap, so swapoff cannot race.
Introduce dedicated helpers:
- pin_hibernation_swap_type(): Look up and pin the swap device.
Used by the uswsusp path.
- find_hibernation_swap_type(): Lookup without pinning.
Used by the kernel swsusp path.
- unpin_hibernation_swap_type(): Clear the hibernation pin.
While a swap device is pinned, swapoff is prevented from proceeding.
Signed-off-by: Youngjun Park <youngjun.park@lge.com>
---
include/linux/swap.h | 5 +-
kernel/power/swap.c | 2 +-
kernel/power/user.c | 15 ++++-
mm/swapfile.c | 135 ++++++++++++++++++++++++++++++++++++++-----
4 files changed, 136 insertions(+), 21 deletions(-)
diff --git a/include/linux/swap.h b/include/linux/swap.h
index 62fc7499b408..82bfc965c3f8 100644
--- a/include/linux/swap.h
+++ b/include/linux/swap.h
@@ -216,6 +216,7 @@ enum {
SWP_PAGE_DISCARD = (1 << 10), /* freed swap page-cluster discards */
SWP_STABLE_WRITES = (1 << 11), /* no overwrite PG_writeback pages */
SWP_SYNCHRONOUS_IO = (1 << 12), /* synchronous IO is efficient */
+ SWP_HIBERNATION = (1 << 13), /* pinned for hibernation */
/* add others here before... */
};
@@ -452,7 +453,9 @@ static inline long get_nr_swap_pages(void)
extern void si_swapinfo(struct sysinfo *);
extern int add_swap_count_continuation(swp_entry_t, gfp_t);
-int swap_type_of(dev_t device, sector_t offset);
+int pin_hibernation_swap_type(dev_t device, sector_t offset);
+void unpin_hibernation_swap_type(int type);
+int find_hibernation_swap_type(dev_t device, sector_t offset);
int find_first_swap(dev_t *device);
extern unsigned int count_swap_pages(int, int);
extern sector_t swapdev_block(int, pgoff_t);
diff --git a/kernel/power/swap.c b/kernel/power/swap.c
index 2e64869bb5a0..cc4764149e8f 100644
--- a/kernel/power/swap.c
+++ b/kernel/power/swap.c
@@ -341,7 +341,7 @@ static int swsusp_swap_check(void)
* This is called before saving the image.
*/
if (swsusp_resume_device)
- res = swap_type_of(swsusp_resume_device, swsusp_resume_block);
+ res = find_hibernation_swap_type(swsusp_resume_device, swsusp_resume_block);
else
res = find_first_swap(&swsusp_resume_device);
if (res < 0)
diff --git a/kernel/power/user.c b/kernel/power/user.c
index 4401cfe26e5c..aab9aece1009 100644
--- a/kernel/power/user.c
+++ b/kernel/power/user.c
@@ -71,7 +71,7 @@ static int snapshot_open(struct inode *inode, struct file *filp)
memset(&data->handle, 0, sizeof(struct snapshot_handle));
if ((filp->f_flags & O_ACCMODE) == O_RDONLY) {
/* Hibernating. The image device should be accessible. */
- data->swap = swap_type_of(swsusp_resume_device, 0);
+ data->swap = pin_hibernation_swap_type(swsusp_resume_device, 0);
data->mode = O_RDONLY;
data->free_bitmaps = false;
error = pm_notifier_call_chain_robust(PM_HIBERNATION_PREPARE, PM_POST_HIBERNATION);
@@ -90,8 +90,10 @@ static int snapshot_open(struct inode *inode, struct file *filp)
data->free_bitmaps = !error;
}
}
- if (error)
+ if (error) {
+ unpin_hibernation_swap_type(data->swap);
hibernate_release();
+ }
data->frozen = false;
data->ready = false;
@@ -115,6 +117,7 @@ static int snapshot_release(struct inode *inode, struct file *filp)
data = filp->private_data;
data->dev = 0;
free_all_swap_pages(data->swap);
+ unpin_hibernation_swap_type(data->swap);
if (data->frozen) {
pm_restore_gfp_mask();
free_basic_memory_bitmaps();
@@ -235,11 +238,17 @@ static int snapshot_set_swap_area(struct snapshot_data *data,
offset = swap_area.offset;
}
+ /*
+ * Pin the swap device if a swap area was already
+ * set by SNAPSHOT_SET_SWAP_AREA.
+ */
+ unpin_hibernation_swap_type(data->swap);
+
/*
* User space encodes device types as two-byte values,
* so we need to recode them
*/
- data->swap = swap_type_of(swdev, offset);
+ data->swap = pin_hibernation_swap_type(swdev, offset);
if (data->swap < 0)
return swdev ? -ENODEV : -EINVAL;
data->dev = swdev;
diff --git a/mm/swapfile.c b/mm/swapfile.c
index 94af29d1de88..ac1574acade7 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -133,7 +133,7 @@ static DEFINE_PER_CPU(struct percpu_swap_cluster, percpu_swap_cluster) = {
/* May return NULL on invalid type, caller must check for NULL return */
static struct swap_info_struct *swap_type_to_info(int type)
{
- if (type >= MAX_SWAPFILES)
+ if (type < 0 || type >= MAX_SWAPFILES)
return NULL;
return READ_ONCE(swap_info[type]); /* rcu_dereference() */
}
@@ -1972,22 +1972,15 @@ void swap_free_hibernation_slot(swp_entry_t entry)
put_swap_device(si);
}
-/*
- * Find the swap type that corresponds to given device (if any).
- *
- * @offset - number of the PAGE_SIZE-sized block of the device, starting
- * from 0, in which the swap header is expected to be located.
- *
- * This is needed for the suspend to disk (aka swsusp).
- */
-int swap_type_of(dev_t device, sector_t offset)
+static int __find_hibernation_swap_type(dev_t device, sector_t offset)
{
int type;
+ lockdep_assert_held(&swap_lock);
+
if (!device)
- return -1;
+ return -EINVAL;
- spin_lock(&swap_lock);
for (type = 0; type < nr_swapfiles; type++) {
struct swap_info_struct *sis = swap_info[type];
@@ -1997,16 +1990,118 @@ int swap_type_of(dev_t device, sector_t offset)
if (device == sis->bdev->bd_dev) {
struct swap_extent *se = first_se(sis);
- if (se->start_block == offset) {
- spin_unlock(&swap_lock);
+ if (se->start_block == offset)
return type;
- }
}
}
- spin_unlock(&swap_lock);
return -ENODEV;
}
+/**
+ * pin_hibernation_swap_type - Pin the swap device for hibernation
+ * @device: Block device containing the resume image
+ * @offset: Offset identifying the swap area
+ *
+ * Locate the swap device for @device/@offset and mark it as pinned
+ * for hibernation. While pinned, swapoff() is prevented.
+ *
+ * Only one uswsusp context may pin a swap device at a time.
+ * If already pinned, this function returns -EBUSY.
+ *
+ * Return:
+ * >= 0 on success (swap type).
+ * -EINVAL if @device is invalid.
+ * -ENODEV if the swap device is not found.
+ * -EBUSY if the device is already pinned for hibernation.
+ */
+int pin_hibernation_swap_type(dev_t device, sector_t offset)
+{
+ int type;
+ struct swap_info_struct *si;
+
+ spin_lock(&swap_lock);
+
+ type = __find_hibernation_swap_type(device, offset);
+ if (type < 0) {
+ spin_unlock(&swap_lock);
+ return type;
+ }
+
+ si = swap_type_to_info(type);
+ if (WARN_ON_ONCE(!si)) {
+ spin_unlock(&swap_lock);
+ return -ENODEV;
+ }
+
+ /*
+ * hibernate_acquire() prevents concurrent hibernation sessions.
+ * This check additionally guards against double-pinning within
+ * the same session.
+ */
+ if (WARN_ON_ONCE(si->flags & SWP_HIBERNATION)) {
+ spin_unlock(&swap_lock);
+ return -EBUSY;
+ }
+
+ si->flags |= SWP_HIBERNATION;
+
+ spin_unlock(&swap_lock);
+ return type;
+}
+
+/**
+ * unpin_hibernation_swap_type - Unpin the swap device for hibernation
+ * @type: Swap type previously returned by pin_hibernation_swap_type()
+ *
+ * Clear the hibernation pin on the given swap device, allowing
+ * swapoff() to proceed normally.
+ *
+ * If @type does not refer to a valid swap device, this function
+ * does nothing.
+ */
+void unpin_hibernation_swap_type(int type)
+{
+ struct swap_info_struct *si;
+
+ spin_lock(&swap_lock);
+ si = swap_type_to_info(type);
+ if (!si) {
+ spin_unlock(&swap_lock);
+ return;
+ }
+ si->flags &= ~SWP_HIBERNATION;
+ spin_unlock(&swap_lock);
+}
+
+/**
+ * find_hibernation_swap_type - Find swap type for hibernation
+ * @device: Block device containing the resume image
+ * @offset: Offset within the device identifying the swap area
+ *
+ * Locate the swap device corresponding to @device and @offset.
+ *
+ * Unlike pin_hibernation_swap_type(), this function only performs a
+ * lookup and does not mark the swap device as pinned for hibernation.
+ *
+ * This is safe in the sysfs-based hibernation path where user space
+ * is already frozen and swapoff() cannot run concurrently.
+ *
+ * Return:
+ * A non-negative swap type on success.
+ * -EINVAL if @device is invalid.
+ * -ENODEV if no matching swap device is found.
+ */
+int find_hibernation_swap_type(dev_t device, sector_t offset)
+{
+ int type;
+
+ spin_lock(&swap_lock);
+ type = __find_hibernation_swap_type(device, offset);
+ spin_unlock(&swap_lock);
+
+ return type;
+}
+
int find_first_swap(dev_t *device)
{
int type;
@@ -2803,6 +2898,14 @@ SYSCALL_DEFINE1(swapoff, const char __user *, specialfile)
spin_unlock(&swap_lock);
goto out_dput;
}
+
+ /* Refuse swapoff while the device is pinned for hibernation */
+ if (p->flags & SWP_HIBERNATION) {
+ err = -EBUSY;
+ spin_unlock(&swap_lock);
+ goto out_dput;
+ }
+
if (!security_vm_enough_memory_mm(current->mm, p->pages))
vm_unacct_memory(p->pages);
else {
--
2.34.1
^ permalink raw reply related
* [PATCH v6 2/3] mm/swap: remove redundant swap device reference in alloc/free
From: Youngjun Park @ 2026-03-20 17:03 UTC (permalink / raw)
To: rafael, akpm
Cc: chrisl, kasong, pavel, shikemeng, nphamcs, bhe, baohua,
youngjun.park, usama.arif, linux-pm, linux-mm
In-Reply-To: <20260320170313.163386-1-youngjun.park@lge.com>
In the previous commit, uswsusp was modified to pin the swap device
when the swap type is determined, ensuring the device remains valid
throughout the hibernation I/O path.
Therefore, it is no longer necessary to repeatedly get and put the swap
device reference for each swap slot allocation and free operation.
For hibernation via the sysfs interface, user-space tasks are frozen
before swap allocation begins, so swapoff cannot race with allocation.
After resume, tasks remain frozen while swap slots are freed, so
additional reference management is not required there either.
Remove the redundant swap device get/put operations from the
hibernation swap allocation and free paths.
Also remove the SWP_WRITEOK check before allocation, as the cluster
allocation logic already validates the swap device state.
Signed-off-by: Youngjun Park <youngjun.park@lge.com>
---
mm/swapfile.c | 43 ++++++++++++++++++++-----------------------
1 file changed, 20 insertions(+), 23 deletions(-)
diff --git a/mm/swapfile.c b/mm/swapfile.c
index ac1574acade7..dd9631658808 100644
--- a/mm/swapfile.c
+++ b/mm/swapfile.c
@@ -1923,7 +1923,12 @@ void swap_put_entries_direct(swp_entry_t entry, int nr)
}
#ifdef CONFIG_HIBERNATION
-/* Allocate a slot for hibernation */
+/*
+ * Allocate a slot for hibernation.
+ *
+ * Note: The caller must ensure the swap device is stable, either by
+ * holding a reference or by freezing user-space before calling this.
+ */
swp_entry_t swap_alloc_hibernation_slot(int type)
{
struct swap_info_struct *si = swap_type_to_info(type);
@@ -1933,43 +1938,35 @@ swp_entry_t swap_alloc_hibernation_slot(int type)
if (!si)
goto fail;
- /* This is called for allocating swap entry, not cache */
- if (get_swap_device_info(si)) {
- if (si->flags & SWP_WRITEOK) {
- /*
- * Grab the local lock to be compliant
- * with swap table allocation.
- */
- local_lock(&percpu_swap_cluster.lock);
- offset = cluster_alloc_swap_entry(si, NULL);
- local_unlock(&percpu_swap_cluster.lock);
- if (offset)
- entry = swp_entry(si->type, offset);
- }
- put_swap_device(si);
- }
+ /*
+ * Grab the local lock to be compliant
+ * with swap table allocation.
+ */
+ local_lock(&percpu_swap_cluster.lock);
+ offset = cluster_alloc_swap_entry(si, NULL);
+ local_unlock(&percpu_swap_cluster.lock);
+ if (offset)
+ entry = swp_entry(si->type, offset);
fail:
return entry;
}
-/* Free a slot allocated by swap_alloc_hibernation_slot */
+/*
+ * Free a slot allocated by swap_alloc_hibernation_slot.
+ * As with allocation, the caller must ensure the swap device is stable.
+ */
void swap_free_hibernation_slot(swp_entry_t entry)
{
- struct swap_info_struct *si;
+ struct swap_info_struct *si = __swap_entry_to_info(entry);
struct swap_cluster_info *ci;
pgoff_t offset = swp_offset(entry);
- si = get_swap_device(entry);
- if (WARN_ON(!si))
- return;
-
ci = swap_cluster_lock(si, offset);
swap_put_entry_locked(si, ci, offset);
swap_cluster_unlock(ci);
/* In theory readahead might add it to the swap cache by accident */
__try_to_reclaim_swap(si, offset, TTRS_ANYWAY);
- put_swap_device(si);
}
static int __find_hibernation_swap_type(dev_t device, sector_t offset)
--
2.34.1
^ permalink raw reply related
* Re: [PATCH v3 2/4] serdev: add rust private data to serdev_device
From: Markus Probst @ 2026-03-20 17:13 UTC (permalink / raw)
To: Greg Kroah-Hartman
Cc: Rob Herring, Jiri Slaby, Miguel Ojeda, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Kari Argillander,
Rafael J. Wysocki, Viresh Kumar, Boqun Feng, David Airlie,
Simona Vetter, linux-serial, linux-kernel, rust-for-linux,
linux-pm, driver-core, dri-devel
In-Reply-To: <2026032048-curtly-refold-e4df@gregkh>
[-- Attachment #1: Type: text/plain, Size: 4104 bytes --]
On Fri, 2026-03-20 at 17:59 +0100, Greg Kroah-Hartman wrote:
> On Fri, Mar 20, 2026 at 04:53:09PM +0000, Markus Probst wrote:
> > On Sat, 2026-03-14 at 14:31 +0100, Greg Kroah-Hartman wrote:
> > > On Sat, Mar 14, 2026 at 12:08:09PM +0000, Markus Probst wrote:
> > > > On Sat, 2026-03-14 at 12:52 +0100, Greg Kroah-Hartman wrote:
> > > > > On Sat, Mar 14, 2026 at 11:42:02AM +0000, Markus Probst wrote:
> > > > > > On Sat, 2026-03-14 at 09:07 +0100, Greg Kroah-Hartman wrote:
> > > > > > > On Fri, Mar 13, 2026 at 06:12:31PM +0000, Markus Probst wrote:
> > > > > > > > Add rust private data to `struct serdev_device`, as it is required by the
> > > > > > > > rust abstraction added in the following commit
> > > > > > > > (rust: add basic serial device bus abstractions).
> > > > > > >
> > > > > > > why is rust "special" here? What's wrong with the existing private
> > > > > > > pointer in this structure? Why must we add another one?
> > > > > > Because in rust, the device drvdata will be set after probe has run. In
> > > > > > serdev, once the device has been opened, it can receive data. It must
> > > > > > be opened either inside probe or before probe, because it can only be
> > > > > > configured (baudrate, flow control etc.) and data written to after it
> > > > > > has been opened. Because it can receive data before drvdata has been
> > > > > > set yet, we need to ensure it waits on data receival for the probe to
> > > > > > be finished. Otherwise this would be a null pointer dereference. To do
> > > > > > this, we need to store a `Completion` for it to wait and a `bool` in
> > > > > > case the probe exits with an error. We cannot store this data in the
> > > > > > device drvdata, because this is where the drivers drvdata goes. We also
> > > > > > cannot create a wrapper of the drivers drvdata, because
> > > > > > `Device::drvdata::<T>()` would always fail in that case. That is why we
> > > > > > need a "rust_private_data" for this abstraction to store the
> > > > > > `Completion` and `bool`.
> > > > >
> > > > > So why is this any different from any other bus type? I don't see the
> > > > > "uniqueness" here that has not required this to happen for PCI or USB or
> > > > > anything else.
> > > > >
> > > > > What am I missing?
> > > > In Short:
> > > > In serdev, we have to handle incoming device data (serdev calls on a
> > > > function pointer we provide in advance), even in the case that the
> > > > driver hasn't completed probe yet.
> > >
> > > But how is that any different from a USB or PCI driver doing the same
> > > thing? Why is serdev so unique here?
> > >
> > In PCI or USB we don't need to provide function pointers for callbacks
> > in advance, which will be can be called any time (even while probe).
>
> All class drivers are like this, once you register with them, the
> function pointers you provide can be called before your probe call ends.
> So this isn't unique as far as I can tell.
>
> > > What specific serdev function
> > > causes this
> > >
> > drivers/tty/serdev/serdev-ttyport.c basically only wraps the serdev
> > calls to tty calls. This isn't directly caused by a serdev function,
> > but by the tty part.
> >
> > > why isn't it an issue with the C api?
> > >
> > In C you can set the drvdata inside the probe and even with it not
> > being fully initialized.
> >
> > With Rust the drvdata is only available after the probe.
> > But there is the posibility of serdev calling the provided callback
> > inside probe.
>
> Other classes have this same issue, so why isn't this a problem for HID
> and input and the like?
This is unique for a bus device.
A class device has its own Data (e. g. PwmOps), i. e. it will only be
registered after this Data has been initialized by the driver.
The receive_buf callback on the serdev device on the other hand must
happen on the drvdata, as there is no place to store its own Data.
The drvdata is only available at the end of the probe in Rust.
Thanks
- Markus Probst
>
> thanks,
>
> greg k-h
[-- Attachment #2: This is a digitally signed message part --]
[-- Type: application/pgp-signature, Size: 870 bytes --]
^ permalink raw reply
* Re: [GIT PULL] pmdomain fixes for v7.0-rc5
From: pr-tracker-bot @ 2026-03-20 17:13 UTC (permalink / raw)
To: Ulf Hansson; +Cc: Linus, linux-pm, linux-kernel, Ulf Hansson, linux-arm-kernel
In-Reply-To: <20260320110521.2044803-1-ulf.hansson@linaro.org>
The pull request you sent on Fri, 20 Mar 2026 12:05:08 +0100:
> git://git.kernel.org/pub/scm/linux/kernel/git/ulfh/linux-pm.git tags/pmdomain-v7.0-rc1-2
has been merged into torvalds/linux.git:
https://git.kernel.org/torvalds/c/c715f13bb30f9f4d1bd8888667ef32e43b6fedc1
Thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/prtracker.html
^ permalink raw reply
* Re: [PATCH v7 6/7] dax/hmem, cxl: Defer and resolve Soft Reserved ownership
From: Koralahalli Channabasappa, Smita @ 2026-03-20 17:17 UTC (permalink / raw)
To: Jonathan Cameron, Smita Koralahalli
Cc: linux-cxl, linux-kernel, nvdimm, linux-fsdevel, linux-pm,
Ard Biesheuvel, Alison Schofield, Vishal Verma, Ira Weiny,
Dan Williams, Yazen Ghannam, Dave Jiang, Davidlohr Bueso,
Matthew Wilcox, Jan Kara, Rafael J . Wysocki, Len Brown,
Pavel Machek, Li Ming, Jeff Johnson, Ying Huang, Yao Xingtao,
Peter Zijlstra, Greg Kroah-Hartman, Nathan Fontenot, Terry Bowman,
Robert Richter, Benjamin Cheatham, Zhijian Li, Borislav Petkov,
Tomasz Wolski
In-Reply-To: <20260319142910.0000113d@huawei.com>
Hi Jonathan,
Thanks for all the comments. I will fix all of them in v8.
Thanks
Smita
On 3/19/2026 7:29 AM, Jonathan Cameron wrote:
> On Thu, 19 Mar 2026 01:14:59 +0000
> Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com> wrote:
>
>> The current probe time ownership check for Soft Reserved memory based
>> solely on CXL window intersection is insufficient. dax_hmem probing is not
>> always guaranteed to run after CXL enumeration and region assembly, which
>> can lead to incorrect ownership decisions before the CXL stack has
>> finished publishing windows and assembling committed regions.
>>
>> Introduce deferred ownership handling for Soft Reserved ranges that
>> intersect CXL windows. When such a range is encountered during the
>> initial dax_hmem probe, schedule deferred work to wait for the CXL stack
>> to complete enumeration and region assembly before deciding ownership.
>>
>> Once the deferred work runs, evaluate each Soft Reserved range
>> individually: if a CXL region fully contains the range, skip it and let
>> dax_cxl bind. Otherwise, register it with dax_hmem. This per-range
>> ownership model avoids the need for CXL region teardown and
>> alloc_dax_region() resource exclusion prevents double claiming.
>>
>> Introduce a boolean flag dax_hmem_initial_probe to live inside device.c
>> so it survives module reload. Ensure dax_cxl defers driver registration
>> until dax_hmem has completed ownership resolution. dax_cxl calls
>> dax_hmem_flush_work() before cxl_driver_register(), which both waits for
>> the deferred work to complete and creates a module symbol dependency that
>> forces dax_hmem.ko to load before dax_cxl.
>>
>> Co-developed-by: Dan Williams <dan.j.williams@intel.com>
>> Signed-off-by: Dan Williams <dan.j.williams@intel.com>
>> Signed-off-by: Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com>
> Hi Smita,
>
> I think this is very likely to be what is causing the bug Alison
> saw in cxl_test.
>
> It looks to be possible to flush work before the work structure has
> been configured. Even though it's not on a work queue and there is
> nothing to do, there are early sanity checks that fail giving the warning
> Alison reported.
>
> A couple of ways to fix that inline. I'd be tempted to both initialize
> the function statically and gate against flushing if the whole thing isn't
> set up yet.
>
> Jonathan
>
>> ---
>> drivers/dax/bus.h | 7 +++++
>> drivers/dax/cxl.c | 1 +
>> drivers/dax/hmem/device.c | 3 ++
>> drivers/dax/hmem/hmem.c | 66 +++++++++++++++++++++++++++++++++++++--
>> 4 files changed, 75 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/dax/bus.h b/drivers/dax/bus.h
>> index cbbf64443098..ebbfe2d6da14 100644
>> --- a/drivers/dax/bus.h
>> +++ b/drivers/dax/bus.h
>> @@ -49,6 +49,13 @@ void dax_driver_unregister(struct dax_device_driver *dax_drv);
>> void kill_dev_dax(struct dev_dax *dev_dax);
>> bool static_dev_dax(struct dev_dax *dev_dax);
>>
>> +#if IS_ENABLED(CONFIG_DEV_DAX_HMEM)
>> +extern bool dax_hmem_initial_probe;
>> +void dax_hmem_flush_work(void);
>> +#else
>> +static inline void dax_hmem_flush_work(void) { }
>> +#endif
>> +
>> #define MODULE_ALIAS_DAX_DEVICE(type) \
>> MODULE_ALIAS("dax:t" __stringify(type) "*")
>> #define DAX_DEVICE_MODALIAS_FMT "dax:t%d"
>> diff --git a/drivers/dax/cxl.c b/drivers/dax/cxl.c
>> index a2136adfa186..3ab39b77843d 100644
>> --- a/drivers/dax/cxl.c
>> +++ b/drivers/dax/cxl.c
>> @@ -44,6 +44,7 @@ static struct cxl_driver cxl_dax_region_driver = {
>>
>> static void cxl_dax_region_driver_register(struct work_struct *work)
>> {
>> + dax_hmem_flush_work();
>> cxl_driver_register(&cxl_dax_region_driver);
>> }
>>
>> diff --git a/drivers/dax/hmem/device.c b/drivers/dax/hmem/device.c
>> index 56e3cbd181b5..991a4bf7d969 100644
>> --- a/drivers/dax/hmem/device.c
>> +++ b/drivers/dax/hmem/device.c
>> @@ -8,6 +8,9 @@
>> static bool nohmem;
>> module_param_named(disable, nohmem, bool, 0444);
>>
>> +bool dax_hmem_initial_probe;
>> +EXPORT_SYMBOL_GPL(dax_hmem_initial_probe);
>> +
>> static bool platform_initialized;
>> static DEFINE_MUTEX(hmem_resource_lock);
>> static struct resource hmem_active = {
>> diff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c
>> index 1e3424358490..8c574123bd3b 100644
>> --- a/drivers/dax/hmem/hmem.c
>> +++ b/drivers/dax/hmem/hmem.c
>> @@ -3,6 +3,7 @@
>> #include <linux/memregion.h>
>> #include <linux/module.h>
>> #include <linux/dax.h>
>> +#include <cxl/cxl.h>
>> #include "../bus.h"
>>
>> static bool region_idle;
>> @@ -58,6 +59,19 @@ static void release_hmem(void *pdev)
>> platform_device_unregister(pdev);
>> }
>>
>> +struct dax_defer_work {
>> + struct platform_device *pdev;
>> + struct work_struct work;
>> +};
>> +
>> +static struct dax_defer_work dax_hmem_work;
>
> static struct dax_defer_work dax_hmem_work = {
> .work = __WORK_INITIALIZER(&dax_hmem_work.work,
> process_defer_work),
> };
> or something similar.
>
>
>> +
>> +void dax_hmem_flush_work(void)
>> +{
>> + flush_work(&dax_hmem_work.work);
>> +}
>> +EXPORT_SYMBOL_GPL(dax_hmem_flush_work);
>> +
>> static int hmem_register_device(struct device *host, int target_nid,
>> const struct resource *res)
>> {
>> @@ -69,8 +83,11 @@ static int hmem_register_device(struct device *host, int target_nid,
>> if (IS_ENABLED(CONFIG_DEV_DAX_CXL) &&
>> region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
>> IORES_DESC_CXL) != REGION_DISJOINT) {
>> - dev_dbg(host, "deferring range to CXL: %pr\n", res);
>> - return 0;
>> + if (!dax_hmem_initial_probe) {
>> + dev_dbg(host, "deferring range to CXL: %pr\n", res);
>> + queue_work(system_long_wq, &dax_hmem_work.work);
>> + return 0;
>> + }
>> }
>>
>> rc = region_intersects_soft_reserve(res->start, resource_size(res));
>> @@ -123,8 +140,48 @@ static int hmem_register_device(struct device *host, int target_nid,
>> return rc;
>> }
>>
>> +static int hmem_register_cxl_device(struct device *host, int target_nid,
>> + const struct resource *res)
>> +{
>> + if (region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
>> + IORES_DESC_CXL) == REGION_DISJOINT)
>> + return 0;
>> +
>> + if (cxl_region_contains_resource((struct resource *)res)) {
>> + dev_dbg(host, "CXL claims resource, dropping: %pr\n", res);
>> + return 0;
>> + }
>> +
>> + dev_dbg(host, "CXL did not claim resource, registering: %pr\n", res);
>> + return hmem_register_device(host, target_nid, res);
>> +}
>> +
>> +static void process_defer_work(struct work_struct *w)
>> +{
>> + struct dax_defer_work *work = container_of(w, typeof(*work), work);
>> + struct platform_device *pdev = work->pdev;
> If you do the suggested __INITIALIZE_WORK() then I'd add
> a paranoid
>
> if (!work->pdev)
> return;
> We don't actually queue the work before pdev is set, but that might
> be obvious once we spilt up assigning the function and the data
> it uses.
>
>> +
>> + wait_for_device_probe();
>> +
>> + guard(device)(&pdev->dev);
>> + if (!pdev->dev.driver)
>> + return;
>> +
>> + dax_hmem_initial_probe = true;
>> + walk_hmem_resources(&pdev->dev, hmem_register_cxl_device);
>> +}
>> +
>> static int dax_hmem_platform_probe(struct platform_device *pdev)
>> {
>> + if (work_pending(&dax_hmem_work.work))
>> + return -EBUSY;
>> +
>> + if (!dax_hmem_work.pdev) {
>> + get_device(&pdev->dev);
>> + dax_hmem_work.pdev = pdev;
>
> Using the pdev rather than dev breaks the pattern of doing a get_device()
> and assigning in one line. This is a bit ugly.
>
> dax_hmem_work.pdev = to_pci_dev(get_device(&pdev->dev));
>
> but perhaps makes the association tighter than current code.
>
>> + INIT_WORK(&dax_hmem_work.work, process_defer_work);
>
> See above. I think assigning the work function should be static
> which should resolve the issue Alison was seeing as then it should
> be fine to call flush_work() on the item that isn't on a work queue
> yet but is initialized.
>
>> + }
>> +
>> return walk_hmem_resources(&pdev->dev, hmem_register_device);
>> }
>>
>> @@ -162,6 +219,11 @@ static __init int dax_hmem_init(void)
>>
>> static __exit void dax_hmem_exit(void)
>> {
>> + flush_work(&dax_hmem_work.work);
>
> I think this needs to be under the if (dax_hmem_work.pdev)
> Not sure there is any guarantee dax_hmem_platform_probe() has run
> before we get here otherwise. Alternative is to assign
> the work function statically.
>
>
>
>> +
>> + if (dax_hmem_work.pdev)
>> + put_device(&dax_hmem_work.pdev->dev);
>> +
>> platform_driver_unregister(&dax_hmem_driver);
>> platform_driver_unregister(&dax_hmem_platform_driver);
>> }
>
^ permalink raw reply
* Re: [PATCH v7 3/7] dax/cxl, hmem: Initialize hmem early and defer dax_cxl binding
From: Koralahalli Channabasappa, Smita @ 2026-03-20 17:29 UTC (permalink / raw)
To: Dan Williams, Alison Schofield, Smita Koralahalli,
Jonathan Cameron
Cc: linux-cxl, linux-kernel, nvdimm, linux-fsdevel, linux-pm,
Ard Biesheuvel, Vishal Verma, Ira Weiny, Yazen Ghannam,
Dave Jiang, Davidlohr Bueso, Matthew Wilcox, Jan Kara,
Rafael J . Wysocki, Len Brown, Pavel Machek, Li Ming,
Jeff Johnson, Ying Huang, Yao Xingtao, Peter Zijlstra,
Greg Kroah-Hartman, Nathan Fontenot, Terry Bowman, Robert Richter,
Benjamin Cheatham, Zhijian Li, Borislav Petkov, Tomasz Wolski
In-Reply-To: <69bc81bfa9baa_7ee310093@dwillia2-mobl4.notmuch>
On 3/19/2026 4:07 PM, Dan Williams wrote:
> Koralahalli Channabasappa, Smita wrote:
> [..]
>>> I agree with Jonathan's comments in Patch 6, using __WORK_INITIALIZER or
>>> initializing work in dax_hmem_init() and gating flush on pdev will fix
>>> the WARN — I will add both for v8. But I think the WARN is likely
>>> indicating an ordering issue here..
>
> Yes, Jonathan is right, static initialization is also my expecation.
>
>>> On initial boot, the Makefile ordering ensures dax_hmem_init() runs
>>> before cxl_dax_region_init(), so both work items land on system_long_wq
>>> in the right order and dax_hmem's deferred work is queued before
>>> dax_cxl's driver registration work.
>
> There is nothing that guarantees that 2 work items in system_long_wq run
> in submission order. Unlikely that matters given the explicit flushing.
>
>>> On module reload which Alison is trying here I dont think, modules are
>>> loaded by Makefile order. I think dax_cxl's workqueue is calling
>>> dax_hmem_flush_work() before dax_hmem probe has had a chance to queue
>>> its work, so flush_work() flushes nothing and dax_cxl registers its
>>> driver without waiting.
>
> Module load order does not matter after initial probe completion.
Thanks for the clarification on system_long_wq ordering.
>
> ...and dax_hmem is guaranteed to always load before dax_cxl due to the
> symbol dependency of dax_hmem_flush_work().
>
>>> __WORK_INITIALIZER fixes the WARN, but doesn't fix the race I guess if
>>> we are hitting that here..
>>>
>>> [ 34.673051] initcall dax_hmem_init+0x0/0xff0 [dax_hmem] returned 0
>>> after 2225 usecs
>>> [ 34.676011] calling cxl_dax_region_init+0x0/0xff0 [dax_cxl] @ 1059
>>>
>>> These two lines indicate cxl_dax started after dax_hmem_init() returns
>>> but I dont think that guarantees dax_hmem_platform_probe() has actually
>>> run..
>>>
>>> I dont know if wait_for_device_probe() in cxl_dax_region_driver_register
>>> might help..
>>>
>>> Thanks
>>> Smita
>>
>> Actually, thinking about this more..
>>
>> dax_hmem_initial_probe lives in device.c (built-in) so it survives
>> module reload. On reload it's still true from the first boot. This means
>> hmem_register_device() skips the deferral path entirely..
>
> Yes, that is the expectation.
>
>> The problem is this bypasses the cxl_region_contains_resource() check
>> that the deferred work normally does. On first boot,
>> process_defer_work() walks each range and decides per-range: if CXL
>> covers it, skip. If not, register with HMEM. On reload, that check never
>> happens — whoever registers first via alloc_dax_region() wins,
>> regardless of whether CXL actually covers the range.
>
> Yes, I think you have hit on a real issue. There is no point in having
> dax_hmem auto-attach on driver reload. If userspace unloads the driver
> it gets to keep the pieces. So that means something like this:
>
> diff --git a/drivers/dax/hmem/hmem.c b/drivers/dax/hmem/hmem.c
> index 15e462589b92..7478bc78a698 100644
> --- a/drivers/dax/hmem/hmem.c
> +++ b/drivers/dax/hmem/hmem.c
> @@ -112,10 +112,12 @@ static int hmem_register_device(struct device *host, int target_nid,
> region_intersects(res->start, resource_size(res), IORESOURCE_MEM,
> IORES_DESC_CXL) != REGION_DISJOINT) {
> if (!dax_hmem_initial_probe) {
> - dev_dbg(host, "deferring range to CXL: %pr\n", res);
> + dev_dbg(host, "await CXL initial probe: %pr\n", res);
> queue_work(system_long_wq, &dax_hmem_work.work);
> return 0;
> }
> + dev_dbg(host, "deferring range to CXL: %pr\n", res);
> + return 0;
> }
>
> rc = region_intersects_soft_reserve(res->start, resource_size(res));
>
> ---
>
> ...because if userspace wants to reload the dax_hmem driver, then it
> needs to pick what happens with the CXL intersection. Userspace can
> always unload cxl_acpi to force everything back to dax_hmem.
>
> Now, you might say, "but this means that if the initial probe results in
> a partial result of some regions in dax_hmem and others in dax_cxl, that
> state can not be recovered outside of a reboot". I think that is ok.
> This mechanism is automatic best-effort workaround for bugs / missing
> capabilities in the CXL driver. Module reload fidelity is out of scope.
The fixup for the reload case makes sense.
I will incorporate this into v8 along with Jonathan's __WORK_INITIALIZER
and the pdev gating.
Thanks
Smita
>
>> So if dax_cxl registers first on reload, it could claim a range that CXL
>> doesn't actually cover, and dax_hmem would lose a range it should own..
>
> With the above change, dax_cxl always wins in the "reload" scenario iff
> cxl_acpi is loaded. Otherwise dax_hmem owns all the Soft Reserved.
>
>> I dont know if Im thinking through this right..
>
> You definitely identified the need for that fixup above.
^ permalink raw reply
* Re: [PATCH v1 0/6] powercap: intel_rapl: Move primitives to interface drivers
From: Rafael J. Wysocki @ 2026-03-20 18:10 UTC (permalink / raw)
To: srinivas pandruvada, Kuppuswamy Sathyanarayanan
Cc: Daniel Lezcano, Zhang Rui, Lukasz Luba, linux-pm, linux-kernel
In-Reply-To: <43d464a89afdf955282ffe3ce0c5acd50e047500.camel@linux.intel.com>
On Fri, Mar 20, 2026 at 6:02 PM srinivas pandruvada
<srinivas.pandruvada@linux.intel.com> wrote:
>
> On Fri, 2026-03-13 at 11:53 -0700, Kuppuswamy Sathyanarayanan wrote:
> > RAPL supports multiple interfaces (MSR, MMIO, and TPMI), each with
> > their
> > own register layouts and bitmask definitions. Currently, the
> > primitive
> > tables and bitmasks for all three interfaces live in the common
> > driver,
> > which requires interface-specific handling logic in shared code and
> > makes
> > the common layer unnecessarily complex.
> >
> > This series cleans this up by moving each interface's primitive
> > definitions into its own driver, so the common driver no longer needs
> > to
> > know anything about per-interface register layouts.
> >
> > All patches are no-functional-change refactoring.
> >
> > This series is based on top of the earlier RAPL cleanup series:
> >
> >
> > https://lore.kernel.org/linux-pm/20260212233044.329790-1-sathyanarayanan.kuppuswamy@linux.intel.com/T/#t
> >
>
> For whole series:
>
> Acked-by: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
Whole series applied as 7.1 material, thanks!
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox