[PATCH 8/8] cpufreq: cppc: Expose Resource Priority attributes via sysfs

Lifeng Zheng <[email protected]> Fri, 17 Jul 2026 10:45:02 +0800
Newsgroups dev.linux.lists.acpica-devel,org.kernel.vger.linux-acpi,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm
Message-ID <[email protected]>
Add a "resource_priority" kobject directory under each cpufreq policy
that has CPPC v4 Resource Priority entries.  Inside it, create a
sub-directory per resource priority group (indexed 0..N-1) with the
following sysfs attributes:

  controlled_resources  (RO) -- resource type IDs as human-readable names
  enable                (RW) -- enable/disable the priority group
  priority_count        (RO) -- number of priority levels
  priority              (RW) -- current priority value

Create the sysfs hierarchy in cppc_cpufreq_cpu_init() and tear it down
in cppc_cpufreq_cpu_exit().  A void *res_prio_data pointer in
cppc_cpudata tracks the allocation.

These attributes allow administrators and power-management daemons to
inspect and tune Resource Priority settings at runtime without
requiring platform-specific tools.

Signed-off-by: Lifeng Zheng <[email protected]>
---
 drivers/cpufreq/cppc_cpufreq.c | 270 +++++++++++++++++++++++++++++++++
 include/acpi/cppc_acpi.h       |   1 +
 2 files changed, 271 insertions(+)

diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index f6cea0c54dd9..ed88f4e55ced 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -640,6 +640,9 @@ static void cppc_cpufreq_put_cpu_data(struct cpufreq_policy *policy)
 	policy->driver_data = NULL;
 }
 
+static int cppc_create_res_prio_sysfs(struct cpufreq_policy *policy);
+static void cppc_remove_res_prio_sysfs(struct cpufreq_policy *policy);
+
 static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
 {
 	unsigned int cpu = policy->cpu;
@@ -715,6 +718,11 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
 	}
 
 	cppc_cpufreq_cpu_fie_init(policy);
+
+	ret = cppc_create_res_prio_sysfs(policy);
+	if (ret)
+		goto out;
+
 	return 0;
 
 out:
@@ -729,6 +737,7 @@ static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
 	unsigned int cpu = policy->cpu;
 	int ret;
 
+	cppc_remove_res_prio_sysfs(policy);
 	cppc_cpufreq_cpu_fie_exit(policy);
 
 	cpu_data->perf_ctrls.desired_perf = caps->lowest_perf;
@@ -1026,6 +1035,267 @@ static struct freq_attr *cppc_cpufreq_attr[] = {
 	NULL,
 };
 
+/* ====== Resource Priority sysfs interface (CPPC v4) ====== */
+
+struct cppc_res_prio_group {
+	struct kobject kobj;
+	int index;
+	unsigned int cpu;
+};
+
+struct cppc_res_prio_data {
+	struct kobject kobj;
+	int num_groups;
+	struct cppc_res_prio_group groups[];
+};
+
+static const char * const resource_type_names[] = {
+	NULL,
+	"processor_boost",
+	"processor_throttle",
+	"l2_cache",
+	"l3_cache",
+	"memory_bandwidth",
+};
+
+#define RESOURCE_TYPE_MAX 5
+
+static const char *resource_type_to_name(u32 id)
+{
+	if (id >= 1 && id <= RESOURCE_TYPE_MAX)
+		return resource_type_names[id];
+	return NULL;
+}
+
+static ssize_t controlled_resources_show(struct kobject *kobj,
+					 struct kobj_attribute *attr, char *buf)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	u32 resources[32];
+	int num = ARRAY_SIZE(resources);
+	int ret, i, pos = 0;
+
+	ret = cppc_get_resource_priority_resources(grp->cpu, grp->index,
+						   resources, &num);
+	if (ret)
+		return ret;
+
+	if (num == 0)
+		return sysfs_emit(buf, "\n");
+
+	for (i = 0; i < num; i++) {
+		const char *name = resource_type_to_name(resources[i]);
+
+		if (name)
+			pos += sysfs_emit_at(buf, pos, "%s", name);
+		else
+			pos += sysfs_emit_at(buf, pos, "unknown(0x%02x)", resources[i]);
+
+		if (i < num - 1)
+			pos += sysfs_emit_at(buf, pos, " ");
+	}
+
+	pos += sysfs_emit_at(buf, pos, "\n");
+	return pos;
+}
+
+static ssize_t enable_show(struct kobject *kobj,
+			   struct kobj_attribute *attr, char *buf)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	bool val;
+	int ret;
+
+	ret = cppc_get_res_priority_enable(grp->cpu, grp->index, &val);
+	if (ret == -EOPNOTSUPP)
+		return sysfs_emit(buf, "<unsupported>\n");
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%d\n", val);
+}
+
+static ssize_t enable_store(struct kobject *kobj,
+			    struct kobj_attribute *attr,
+			    const char *buf, size_t count)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	bool val;
+	int ret;
+
+	ret = kstrtobool(buf, &val);
+	if (ret)
+		return ret;
+
+	ret = cppc_set_res_priority_enable(grp->cpu, grp->index, val);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static ssize_t priority_count_show(struct kobject *kobj,
+				   struct kobj_attribute *attr,
+				   char *buf)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	u64 val;
+	int ret;
+
+	ret = cppc_get_res_priority_count(grp->cpu, grp->index, &val);
+	if (ret == -EOPNOTSUPP)
+		return sysfs_emit(buf, "<unsupported>\n");
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%llu\n", val);
+}
+
+static ssize_t priority_show(struct kobject *kobj,
+			     struct kobj_attribute *attr, char *buf)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	u64 val;
+	int ret;
+
+	ret = cppc_get_res_priority(grp->cpu, grp->index, &val);
+	if (ret == -EOPNOTSUPP)
+		return sysfs_emit(buf, "<unsupported>\n");
+	if (ret)
+		return ret;
+
+	return sysfs_emit(buf, "%llu\n", val);
+}
+
+static ssize_t priority_store(struct kobject *kobj,
+			      struct kobj_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct cppc_res_prio_group *grp =
+		container_of(kobj, struct cppc_res_prio_group, kobj);
+	u64 val;
+	int ret;
+
+	ret = kstrtou64(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	ret = cppc_set_res_priority(grp->cpu, grp->index, val);
+	if (ret)
+		return ret;
+
+	return count;
+}
+
+static struct kobj_attribute attr_controlled_resources =
+	__ATTR(controlled_resources, 0444, controlled_resources_show, NULL);
+static struct kobj_attribute attr_enable =
+	__ATTR(enable, 0644, enable_show, enable_store);
+static struct kobj_attribute attr_priority_count =
+	__ATTR(priority_count, 0444, priority_count_show, NULL);
+static struct kobj_attribute attr_priority =
+	__ATTR(priority, 0644, priority_show, priority_store);
+
+static struct attribute *res_prio_group_attrs[] = {
+	&attr_controlled_resources.attr,
+	&attr_enable.attr,
+	&attr_priority_count.attr,
+	&attr_priority.attr,
+	NULL,
+};
+
+ATTRIBUTE_GROUPS(res_prio_group);
+
+static void cppc_res_prio_group_release(struct kobject *kobj)
+{
+	/*
+	 * cppc_res_prio_group is embedded in the flexible array of
+	 * cppc_res_prio_data and freed when the parent is released.
+	 */
+}
+
+static const struct kobj_type cppc_res_prio_group_ktype = {
+	.release = cppc_res_prio_group_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_groups = res_prio_group_groups,
+};
+
+static void cppc_res_prio_release(struct kobject *kobj)
+{
+	struct cppc_res_prio_data *data =
+		container_of(kobj, struct cppc_res_prio_data, kobj);
+
+	kfree(data);
+}
+
+static const struct kobj_type cppc_res_prio_ktype = {
+	.release = cppc_res_prio_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+};
+
+static void cppc_remove_res_prio_sysfs(struct cpufreq_policy *policy)
+{
+	struct cppc_cpudata *cpu_data = policy->driver_data;
+	struct cppc_res_prio_data *data = cpu_data->res_prio_data;
+	int i;
+
+	if (!data)
+		return;
+
+	for (i = 0; i < data->num_groups; i++)
+		kobject_put(&data->groups[i].kobj);
+
+	kobject_put(&data->kobj);
+	cpu_data->res_prio_data = NULL;
+}
+
+static int cppc_create_res_prio_sysfs(struct cpufreq_policy *policy)
+{
+	struct cppc_cpudata *cpu_data = policy->driver_data;
+	struct cppc_res_prio_data *data;
+	int num, i, ret;
+
+	ret = cppc_get_resource_priority_count(policy->cpu, &num);
+	if (ret || num <= 0)
+		return 0;
+
+	data = kzalloc(struct_size(data, groups, num), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->num_groups = num;
+
+	ret = kobject_init_and_add(&data->kobj, &cppc_res_prio_ktype,
+				   &policy->kobj, "resource_priority");
+	if (ret) {
+		kfree(data);
+		return ret;
+	}
+
+	for (i = 0; i < num; i++) {
+		data->groups[i].index = i;
+		data->groups[i].cpu = policy->cpu;
+
+		ret = kobject_init_and_add(&data->groups[i].kobj,
+					   &cppc_res_prio_group_ktype,
+					   &data->kobj, "%d", i);
+		if (ret) {
+			while (--i >= 0)
+				kobject_put(&data->groups[i].kobj);
+			kobject_put(&data->kobj);
+			return ret;
+		}
+	}
+
+	cpu_data->res_prio_data = data;
+	return 0;
+}
+
 static struct cpufreq_driver cppc_cpufreq_driver = {
 	.flags = CPUFREQ_CONST_LOOPS | CPUFREQ_NEED_UPDATE_LIMITS,
 	.verify = cppc_verify_policy,
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 86a4764c504d..481fca627389 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -175,6 +175,7 @@ struct cppc_cpudata {
 	struct cppc_perf_fb_ctrs perf_fb_ctrs;
 	unsigned int shared_type;
 	cpumask_var_t shared_cpu_map;
+	void *res_prio_data;
 };
 
 #ifdef CONFIG_ACPI_CPPC_LIB
-- 
2.33.0