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

Lifeng Zheng <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.