Linux Documentation
 help / color / mirror / Atom feed
From: KobaK <kobak@nvidia.com>
To: catalin.marinas@arm.com, will@kernel.org
Cc: corbet@lwn.net, skhan@linuxfoundation.org,
	linux-arm-kernel@lists.infradead.org, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
	fenghuay@nvidia.com, tdave@nvidia.com, kaihengf@nvidia.com,
	rupakr@nvidia.com, mohammedk@nvidia.com
Subject: [RFC PATCH v6 1/3] arm64: add CPU prefetch and cache modulation sysfs interface
Date: Mon, 17 Aug 2026 09:49:41 +0800	[thread overview]
Message-ID: <20260817014943.10-2-kobak@nvidia.com> (raw)
In-Reply-To: <20260817014943.10-1-kobak@nvidia.com>

From: Koba Ko <kobak@nvidia.com>

Performance characterization on arm64 systems sometimes needs a way to
inspect and bracket selected implementation-defined prefetch and cache
controls without exposing raw register contents. Add an RFC-only arm64
cpumod interface that presents a small set of named, range-checked
per-CPU sysfs attributes under each CPU device.

Establish the Kconfig and Makefile plumbing, CPU profile detection for
Grace and Vera from the target CPU's MIDR, register-field descriptors,
per-CPU kobject and attribute helpers, locking, and target-CPU read/write
callbacks. Common attributes are exposed for all supported profiles,
while Grace- and Vera-specific fields are exposed only for the matching
profile. Place the option at the end of the top-level Kernel Features
menu because these implementation controls are not tied to an Arm
architecture revision.

Per-CPU cpumod sysfs state follows the CPU lifecycle. Module
initialization populates subtrees for every online CPU while holding
cpus_read_lock(), so initial enumeration and CPU hotplug state
registration are serialized against concurrent topology changes. Offline
CPUs are intentionally skipped during initial setup because target-CPU
MIDR detection requires an IPI-capable online CPU; a later online callback
creates their subtree.

Profile detection reads MIDR through a synchronous callback on the CPU
owning the sysfs subtree and returns nonzero only when that dispatch
fails, so no IPI error can be hidden. After a successful read, retain
CPUMOD_PROFILE_UNKNOWN as the unsupported-profile sentinel. Subtree
creation skips that profile during both initial online enumeration and
later CPUHP online callbacks without blocking module load or CPU hotplug.
Object and sysfs setup failures remain fatal and are propagated.

Keep the dynamically allocated kobject state in a private per-CPU pointer
instead of the CPU device's generic driver-data slot. The online callback
is idempotent when that pointer already records a subtree. The offline
callback clears the private pointer before dropping the kobject reference,
allowing the release callback to retain ownership of the final free.

Register the dynamic CPUHP state with
cpuhp_setup_state_nocalls_cpuslocked(). The nocalls form avoids replaying
startup callbacks for CPUs already initialized by the explicit online
enumeration. On init failure, release the CPU read lock, destroy all
subtrees created so far, and return the error. Module exit removes the
CPUHP state with the matching nocalls helper and tears down all remaining
per-CPU state across possible CPUs.

Reviewed-by: Fenghua Yu <fenghuay@nvidia.com>
Reviewed-by: Tushar Dave <tdave@nvidia.com>
Reviewed-by: Kai-Heng Feng <kaihengf@nvidia.com>
Signed-off-by: Koba Ko <kobak@nvidia.com>
---
 arch/arm64/Kconfig                      |  17 +
 arch/arm64/kernel/Makefile              |   1 +
 arch/arm64/kernel/arm_cpumod.c          | 471 ++++++++++++++++++++++++
 arch/arm64/kernel/arm_cpumod_internal.h | 113 ++++++
 4 files changed, 602 insertions(+)
 create mode 100644 arch/arm64/kernel/arm_cpumod.c
 create mode 100644 arch/arm64/kernel/arm_cpumod_internal.h

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b3afe0688919b..5e4ada4884b89 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -2417,6 +2417,23 @@ config ARM64_CONTPTE
 	  bit, for any mappings that meet the size and alignment requirements.
 	  This reduces TLB pressure and improves performance.
 
+config ARM64_CPUMOD
+	tristate "Arm CPU prefetch modulation sysfs controls"
+	help
+	  Expose selected CPU implementation control register fields through
+	  per-CPU sysfs attributes for performance characterization.
+
+	  The exposed controls allow hardware prefetch and cache-management
+	  policy fields to be inspected and adjusted on CPUs where firmware
+	  permits EL1 access to the relevant implementation control registers.
+	  Systems where firmware traps or blocks these writes cannot use this
+	  interface.
+
+	  This interface is intended for controlled performance evaluation and
+	  should not be enabled by default on production systems.
+
+	  If unsure, say N.
+
 endmenu # "Kernel Features"
 
 menu "Boot options"
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index d2690c3ec5288..f180a52284e7d 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -69,6 +69,7 @@ obj-$(CONFIG_VMCORE_INFO)		+= vmcore_info.o
 obj-$(CONFIG_ARM_SDE_INTERFACE)		+= sdei.o
 obj-$(CONFIG_ARM64_PTR_AUTH)		+= pointer_auth.o
 obj-$(CONFIG_ARM64_MPAM)		+= mpam.o
+obj-$(CONFIG_ARM64_CPUMOD)		+= arm_cpumod.o
 obj-$(CONFIG_ARM64_MTE)			+= mte.o
 obj-y					+= vdso-wrap.o
 obj-$(CONFIG_COMPAT_VDSO)		+= vdso32-wrap.o
diff --git a/arch/arm64/kernel/arm_cpumod.c b/arch/arm64/kernel/arm_cpumod.c
new file mode 100644
index 0000000000000..9e64f2c8e7810
--- /dev/null
+++ b/arch/arm64/kernel/arm_cpumod.c
@@ -0,0 +1,471 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#define pr_fmt(fmt) "cpumod: " fmt
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/cpu.h>
+#include <linux/mutex.h>
+#include <linux/percpu.h>
+#include <linux/slab.h>
+#include <asm/cputype.h>
+#include <asm/sysreg.h>
+#include <linux/bits.h>
+#include "arm_cpumod_internal.h"
+
+#define DRIVER_DESC "CPU Modulation Kernel Module"
+
+/* Disables hardware prefetching.
+ * 0b0 Enables hardware prefetching. This is the default value.
+ * 0b1 Disables hardware prefetching.
+ */
+#define CPUECTLR_PF_DIS				(15)
+#define CPUECTLR_PF_DIS_MSK			BIT_ULL(15)
+
+#define CPUECTLR_CMC_WAYS			(61)
+#define CPUECTLR_CMC_WAYS_MSK			GENMASK_ULL(63, 61)
+
+#define CPUECTLR2_PF_MODE			(11)
+#define CPUECTLR2_PF_MODE_MSK			GENMASK_ULL(14, 11)
+
+#define CPUECTLR2_CBUSY_FILTER_WINDOW		(9)
+#define CPUECTLR2_CBUSY_FILTER_WINDOW_MSK	GENMASK_ULL(10, 9)
+
+#define CPUECTLR2_CBUSY_FILTER_THRESHOLD	(7)
+#define CPUECTLR2_CBUSY_FILTER_THRESHOLD_MSK	GENMASK_ULL(8, 7)
+
+static enum cpuhp_state cpumod_hp_state;
+static DEFINE_PER_CPU(struct cpumod_subsys *, cpumod_subsys);
+
+struct cpumod_attr_call {
+	struct cpumod_subsys *subsys;
+	struct cpumod_attr *attr;
+	u64 value;
+};
+
+struct cpumod_midr_call {
+	u32 midr;
+};
+
+static const char *cpumod_profile_name(enum cpumod_profile profile)
+{
+	switch (profile) {
+	case CPUMOD_PROFILE_GRACE:
+		return "Grace";
+	case CPUMOD_PROFILE_VERA:
+		return "Vera";
+	default:
+		return "unknown";
+	}
+}
+
+/* IMP defined CPU Extended Control Register */
+#define IMP_CPUECTLR_EL1	sys_reg(3, 0, 15, 1, 4)
+#define IMP_CPUECTLR2_EL1	sys_reg(3, 0, 15, 1, 5)
+
+static enum cpumod_profile cpumod_profile_from_midr(u32 midr)
+{
+	switch (midr & MIDR_CPU_MODEL_MASK) {
+	case MIDR_NEOVERSE_V2:
+		return CPUMOD_PROFILE_GRACE;
+	case MIDR_NVIDIA_OLYMPUS:
+		return CPUMOD_PROFILE_VERA;
+	default:
+		return CPUMOD_PROFILE_UNKNOWN;
+	}
+}
+
+static void cpumod_read_midr_remote(void *info)
+{
+	struct cpumod_midr_call *call = info;
+
+	call->midr = read_cpuid_id();
+}
+
+static int cpumod_detect_profile(struct cpumod_subsys *subsys)
+{
+	struct cpumod_midr_call call;
+	int ret;
+
+	ret = smp_call_function_single(subsys->cpu, cpumod_read_midr_remote,
+				       &call, 1);
+	if (ret) {
+		pr_err("Failed to read CPU%u MIDR: %d\n", subsys->cpu, ret);
+		return ret;
+	}
+
+	subsys->profile = cpumod_profile_from_midr(call.midr);
+	if (subsys->profile == CPUMOD_PROFILE_UNKNOWN) {
+		pr_debug("Unsupported CPU%u MIDR 0x%08x\n", subsys->cpu,
+			 call.midr);
+	}
+
+	return 0;
+}
+
+static u64 cpumod_read_reg(enum cpumod_reg_id reg)
+{
+	switch (reg) {
+	case CPUMOD_REG_CPUECTLR:
+		return read_sysreg_s(IMP_CPUECTLR_EL1);
+	case CPUMOD_REG_CPUECTLR2:
+		return read_sysreg_s(IMP_CPUECTLR2_EL1);
+	default:
+		return 0;
+	}
+}
+
+static void cpumod_write_reg(enum cpumod_reg_id reg, u64 value)
+{
+	switch (reg) {
+	case CPUMOD_REG_CPUECTLR:
+		write_sysreg_s(value, IMP_CPUECTLR_EL1);
+		/* The remote callback executes this barrier on the target CPU. */
+		isb();
+		break;
+	case CPUMOD_REG_CPUECTLR2:
+		write_sysreg_s(value, IMP_CPUECTLR2_EL1);
+		/* The remote callback executes this barrier on the target CPU. */
+		isb();
+		break;
+	default:
+		return;
+	}
+}
+
+static u64 cpumod_attr_read_reg(const struct cpumod_attr *attr)
+{
+	return cpumod_attr_unpack_value(attr, cpumod_read_reg(attr->reg));
+}
+
+static void cpumod_attr_write_reg(const struct cpumod_attr *attr, u64 value)
+{
+	u64 reg = cpumod_read_reg(attr->reg);
+	u64 field_value = cpumod_attr_pack_value(attr, value);
+
+	cpumod_write_reg(attr->reg, (reg & ~attr->field_mask) | field_value);
+}
+
+static void cpumod_attr_read_remote(void *info)
+{
+	struct cpumod_attr_call *call = info;
+	u64 *value = cpumod_attr_value_ptr(call->subsys, call->attr);
+
+	*value = cpumod_attr_read_reg(call->attr);
+}
+
+static void cpumod_attr_write_remote(void *info)
+{
+	struct cpumod_attr_call *call = info;
+	struct cpumod_subsys *subsys = call->subsys;
+	struct cpumod_attr *attr = call->attr;
+	u64 readback;
+
+	cpumod_attr_write_reg(attr, call->value);
+	readback = cpumod_attr_read_reg(attr);
+	pr_debug("cpu%u %s: %llx\n",
+		 subsys->cpu,
+		 attr->debug_name,
+		 (unsigned long long)readback);
+}
+
+static ssize_t cpumod_attr_show(struct kobject *kobj,
+				struct kobj_attribute *kattr, char *buf)
+{
+	struct cpumod_subsys *subsys = cpumod_subsys_from_kobj(kobj);
+	struct cpumod_attr *attr = cpumod_attr_from_kobj_attr(kattr);
+	struct cpumod_attr_call call = {
+		.subsys = subsys,
+		.attr = attr,
+	};
+	int ret;
+
+	guard(mutex)(&subsys->lock);
+	ret = smp_call_function_single(subsys->cpu,
+				       cpumod_attr_read_remote, &call, 1);
+	if (ret)
+		return ret;
+
+	ret = sysfs_emit(buf, "%llu\n",
+			 (unsigned long long)*cpumod_attr_value_ptr(subsys, attr));
+
+	return ret;
+}
+
+static ssize_t cpumod_attr_store(struct kobject *kobj,
+				 struct kobj_attribute *kattr,
+				 const char *buf, size_t count)
+{
+	struct cpumod_subsys *subsys = cpumod_subsys_from_kobj(kobj);
+	struct cpumod_attr *attr = cpumod_attr_from_kobj_attr(kattr);
+	struct cpumod_attr_call call = {
+		.subsys = subsys,
+		.attr = attr,
+	};
+	unsigned int val;
+	int ret;
+
+	ret = kstrtouint(buf, 10, &val);
+	if (ret)
+		return -EINVAL;
+	if (!cpumod_attr_value_valid(attr, val))
+		return -EINVAL;
+
+	call.value = cpumod_attr_mask_value(attr, val);
+
+	guard(mutex)(&subsys->lock);
+	ret = smp_call_function_single(subsys->cpu,
+				       cpumod_attr_write_remote, &call, 1);
+	if (ret)
+		return ret;
+	cpumod_attr_set_value(subsys, attr, call.value);
+
+	return count;
+}
+
+static ssize_t affected_cpus_show(struct kobject *kobj,
+				  struct kobj_attribute *attr, char *buf)
+{
+	struct cpumod_subsys *subsys = cpumod_subsys_from_kobj(kobj);
+
+	return sysfs_emit(buf, "%u\n", subsys->cpu);
+}
+
+static struct kobj_attribute affected_cpus_attr = __ATTR_RO(affected_cpus);
+
+static umode_t cpumod_attr_is_visible(struct kobject *kobj,
+				      struct attribute *attr, int unused)
+{
+	struct cpumod_subsys *subsys = cpumod_subsys_from_kobj(kobj);
+	struct cpumod_attr *cpumod_attr;
+
+	if (attr == &affected_cpus_attr.attr)
+		return attr->mode;
+
+	cpumod_attr = container_of(attr, struct cpumod_attr, kattr.attr);
+	if (!cpumod_attr_visible_for_profile(cpumod_attr, subsys->profile))
+		return 0;
+
+	return attr->mode;
+}
+
+#define CPUMOD_ATTR_RW(_name, _field, _reg, _shift, _field_mask,	\
+		       _max_value, _debug_name,			\
+		       _visible_profiles)				\
+static struct cpumod_attr _name##_attr = {				\
+	.kattr = __ATTR(_name, 0644, cpumod_attr_show, cpumod_attr_store), \
+	.value_offset = offsetof(struct cpumod_subsys, _field),	\
+	.reg = _reg,						\
+	.shift = _shift,					\
+	.field_mask = _field_mask,				\
+	.max_value = _max_value,				\
+	.debug_name = _debug_name,				\
+	.visible_profiles = _visible_profiles,			\
+}
+
+CPUMOD_ATTR_RW(pf_dis, pf_dis, CPUMOD_REG_CPUECTLR, CPUECTLR_PF_DIS,
+	       CPUECTLR_PF_DIS_MSK, 1, "PF_DIS",
+	       CPUMOD_PROFILE_VISIBLE_ALL);
+CPUMOD_ATTR_RW(pf_mode, pf_mode, CPUMOD_REG_CPUECTLR2, CPUECTLR2_PF_MODE,
+	       CPUECTLR2_PF_MODE_MSK, 9, "PF_MODE",
+	       CPUMOD_PROFILE_VISIBLE_ALL);
+CPUMOD_ATTR_RW(cbusy_filter_threshold, cbusy_filter_threshold,
+	       CPUMOD_REG_CPUECTLR2, CPUECTLR2_CBUSY_FILTER_THRESHOLD,
+	       CPUECTLR2_CBUSY_FILTER_THRESHOLD_MSK, 3,
+	       "CBUSY_FILTER_THRESHOLD",
+	       CPUMOD_PROFILE_VISIBLE_GRACE);
+CPUMOD_ATTR_RW(cbusy_filter_window, cbusy_filter_window,
+	       CPUMOD_REG_CPUECTLR2, CPUECTLR2_CBUSY_FILTER_WINDOW,
+	       CPUECTLR2_CBUSY_FILTER_WINDOW_MSK, 3,
+	       "CBUSY_FILTER_WINDOW",
+	       CPUMOD_PROFILE_VISIBLE_GRACE);
+CPUMOD_ATTR_RW(cmc_min_ways, cmc_min_ways, CPUMOD_REG_CPUECTLR,
+	       CPUECTLR_CMC_WAYS, CPUECTLR_CMC_WAYS_MSK, 7,
+	       "CMC_MIN_WAYS", CPUMOD_PROFILE_VISIBLE_GRACE);
+CPUMOD_ATTR_RW(l2spr_cmc_max_ways, l2spr_cmc_max_ways, CPUMOD_REG_CPUECTLR,
+	       CPUECTLR_CMC_WAYS, CPUECTLR_CMC_WAYS_MSK, 7,
+	       "L2SPR_CMC_MAX_WAYS",
+	       CPUMOD_PROFILE_VISIBLE_VERA);
+
+#undef CPUMOD_ATTR_RW
+
+static struct attribute *cpumod_attrs[] = {
+	&affected_cpus_attr.attr,
+	&pf_dis_attr.kattr.attr,
+	&pf_mode_attr.kattr.attr,
+	&cbusy_filter_threshold_attr.kattr.attr,
+	&cbusy_filter_window_attr.kattr.attr,
+	&cmc_min_ways_attr.kattr.attr,
+	&l2spr_cmc_max_ways_attr.kattr.attr,
+	NULL,
+};
+
+static const struct attribute_group cpumod_group = {
+	.attrs = cpumod_attrs,
+	.is_visible = cpumod_attr_is_visible,
+};
+
+static const struct attribute_group *cpumod_groups[] = {
+	&cpumod_group,
+	NULL,
+};
+
+static void cpumod_release(struct kobject *kobj)
+{
+	kfree(cpumod_subsys_from_kobj(kobj));
+}
+
+static const struct kobj_type cpumod_ktype = {
+	.release = cpumod_release,
+	.sysfs_ops = &kobj_sysfs_ops,
+	.default_groups = cpumod_groups,
+};
+
+static int cpumod_create_subsys(struct device *cpu_dev, unsigned int cpu)
+{
+	struct cpumod_subsys *subsys;
+	int ret;
+
+	if (per_cpu(cpumod_subsys, cpu))
+		return 0;
+
+	subsys = kzalloc_obj(*subsys, GFP_KERNEL);
+	if (!subsys)
+		return -ENOMEM;
+
+	subsys->cpu = cpu;
+	mutex_init(&subsys->lock);
+	ret = cpumod_detect_profile(subsys);
+	if (ret) {
+		kfree(subsys);
+		return ret;
+	}
+	if (subsys->profile == CPUMOD_PROFILE_UNKNOWN) {
+		kfree(subsys);
+		return 0;
+	}
+
+	pr_debug("CPU%u detected %s profile\n", cpu,
+		 cpumod_profile_name(subsys->profile));
+
+	ret = kobject_init_and_add(&subsys->kobj, &cpumod_ktype,
+				   &cpu_dev->kobj, "cpumod");
+	if (ret) {
+		pr_err("Failed to create cpumod sysfs subtree for CPU%u: %d\n",
+		       cpu, ret);
+		kobject_put(&subsys->kobj);
+		return ret;
+	}
+
+	per_cpu(cpumod_subsys, cpu) = subsys;
+
+	return 0;
+}
+
+static void cpumod_destroy_subsys(unsigned int cpu)
+{
+	struct cpumod_subsys *subsys = per_cpu(cpumod_subsys, cpu);
+
+	if (!subsys)
+		return;
+
+	per_cpu(cpumod_subsys, cpu) = NULL;
+	kobject_put(&subsys->kobj);
+}
+
+static void cpumod_destroy_all_subsys(void)
+{
+	unsigned int cpu;
+
+	for_each_possible_cpu(cpu)
+		cpumod_destroy_subsys(cpu);
+}
+
+static int cpumod_online_cpu(unsigned int cpu)
+{
+	struct device *cpu_dev = get_cpu_device(cpu);
+
+	if (!cpu_dev) {
+		pr_err("Failed to get CPU%u device\n", cpu);
+		return -ENODEV;
+	}
+
+	return cpumod_create_subsys(cpu_dev, cpu);
+}
+
+static int cpumod_offline_cpu(unsigned int cpu)
+{
+	cpumod_destroy_subsys(cpu);
+
+	return 0;
+}
+
+static int __init cpumod_init(void)
+{
+	unsigned int cpu;
+	int ret;
+
+	cpus_read_lock();
+	for_each_online_cpu(cpu) {
+		struct device *cpu_dev = get_cpu_device(cpu);
+
+		if (!cpu_dev) {
+			pr_err("Failed to get CPU%u device\n", cpu);
+			ret = -ENODEV;
+			goto err_unlock;
+		}
+
+		ret = cpumod_create_subsys(cpu_dev, cpu);
+		if (ret)
+			goto err_unlock;
+	}
+
+	ret = cpuhp_setup_state_nocalls_cpuslocked(CPUHP_AP_ONLINE_DYN,
+						   "arm64/cpumod:online",
+						   cpumod_online_cpu,
+						   cpumod_offline_cpu);
+	cpus_read_unlock();
+	if (ret < 0)
+		goto err_destroy;
+
+	cpumod_hp_state = ret;
+	pr_info("module loaded\n");
+
+	return 0;
+
+err_unlock:
+	cpus_read_unlock();
+err_destroy:
+	cpumod_destroy_all_subsys();
+	return ret;
+}
+
+static void __exit cpumod_exit(void)
+{
+	cpuhp_remove_state_nocalls(cpumod_hp_state);
+	cpumod_destroy_all_subsys();
+	pr_info("module unloaded\n");
+}
+
+module_init(cpumod_init);
+module_exit(cpumod_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_AUTHOR("kobak@nvidia.com");
+MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/arch/arm64/kernel/arm_cpumod_internal.h b/arch/arm64/kernel/arm_cpumod_internal.h
new file mode 100644
index 0000000000000..04bf345dd383a
--- /dev/null
+++ b/arch/arm64/kernel/arm_cpumod_internal.h
@@ -0,0 +1,113 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Internal arm_cpumod data structures and helpers.
+ */
+
+#ifndef CPUMOD_INTERNAL_H
+#define CPUMOD_INTERNAL_H
+
+#include <linux/bits.h>
+#include <linux/kernel.h>
+#include <linux/kobject.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/types.h>
+
+enum cpumod_profile {
+	CPUMOD_PROFILE_GRACE,
+	CPUMOD_PROFILE_VERA,
+	CPUMOD_PROFILE_UNKNOWN,
+};
+
+enum cpumod_reg_id {
+	CPUMOD_REG_CPUECTLR,
+	CPUMOD_REG_CPUECTLR2,
+};
+
+#define CPUMOD_PROFILE_VISIBLE_GRACE	BIT(0)
+#define CPUMOD_PROFILE_VISIBLE_VERA	BIT(1)
+#define CPUMOD_PROFILE_VISIBLE_ALL	(CPUMOD_PROFILE_VISIBLE_GRACE | \
+					 CPUMOD_PROFILE_VISIBLE_VERA)
+
+struct cpumod_subsys {
+	/* Per-CPU instance lock: serializes sysfs read/write for this CPU. */
+	struct mutex lock;
+	struct kobject kobj;
+	unsigned int cpu;
+	enum cpumod_profile profile;
+	u64 pf_dis;
+	u64 pf_mode;
+	u64 cbusy_filter_threshold;
+	u64 cbusy_filter_window;
+	u64 cmc_min_ways;
+	u64 l2spr_cmc_max_ways;
+};
+
+struct cpumod_attr {
+	struct kobj_attribute kattr;
+	size_t value_offset;
+	enum cpumod_reg_id reg;
+	u8 shift;
+	u64 field_mask;
+	u64 max_value;
+	const char *debug_name;
+	u8 visible_profiles;
+};
+
+static inline struct cpumod_subsys *cpumod_subsys_from_kobj(struct kobject *kobj)
+{
+	return container_of(kobj, struct cpumod_subsys, kobj);
+}
+
+static inline struct cpumod_attr *cpumod_attr_from_kobj_attr(struct kobj_attribute *kattr)
+{
+	return container_of(kattr, struct cpumod_attr, kattr);
+}
+
+static inline u64 *cpumod_attr_value_ptr(struct cpumod_subsys *subsys,
+					 const struct cpumod_attr *attr)
+{
+	return (u64 *)((char *)subsys + attr->value_offset);
+}
+
+static inline u64 cpumod_attr_mask_value(const struct cpumod_attr *attr, u64 value)
+{
+	return value & (attr->field_mask >> attr->shift);
+}
+
+static inline bool cpumod_attr_value_valid(const struct cpumod_attr *attr, u64 value)
+{
+	return value <= attr->max_value;
+}
+
+static inline u64 cpumod_attr_unpack_value(const struct cpumod_attr *attr, u64 reg)
+{
+	return (reg & attr->field_mask) >> attr->shift;
+}
+
+static inline u64 cpumod_attr_pack_value(const struct cpumod_attr *attr, u64 value)
+{
+	return (value << attr->shift) & attr->field_mask;
+}
+
+static inline void cpumod_attr_set_value(struct cpumod_subsys *subsys,
+					 const struct cpumod_attr *attr,
+					 u64 value)
+{
+	*cpumod_attr_value_ptr(subsys, attr) = cpumod_attr_mask_value(attr, value);
+}
+
+static inline bool cpumod_attr_visible_for_profile(const struct cpumod_attr *attr,
+						   enum cpumod_profile profile)
+{
+	switch (profile) {
+	case CPUMOD_PROFILE_GRACE:
+		return attr->visible_profiles & CPUMOD_PROFILE_VISIBLE_GRACE;
+	case CPUMOD_PROFILE_VERA:
+		return attr->visible_profiles & CPUMOD_PROFILE_VISIBLE_VERA;
+	default:
+		return false;
+	}
+}
+
+#endif /* CPUMOD_INTERNAL_H */
-- 
2.43.0


  reply	other threads:[~2026-08-17  1:49 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17  1:49 [RFC PATCH v6 0/3] arm64: expose CPU prefetch and cache modulation controls KobaK
2026-08-17  1:49 ` KobaK [this message]
2026-08-17  1:49 ` [RFC PATCH v6 2/3] Documentation/arch/arm64: document arm_cpumod KobaK
2026-08-17  1:49 ` [RFC PATCH v6 3/3] selftests: arm64: add arm_cpumod kselftest KobaK
2026-08-17  2:21 ` [RFC PATCH v6 0/3] arm64: expose CPU prefetch and cache modulation controls KobaK

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260817014943.10-2-kobak@nvidia.com \
    --to=kobak@nvidia.com \
    --cc=catalin.marinas@arm.com \
    --cc=corbet@lwn.net \
    --cc=fenghuay@nvidia.com \
    --cc=kaihengf@nvidia.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-doc@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=mohammedk@nvidia.com \
    --cc=rupakr@nvidia.com \
    --cc=skhan@linuxfoundation.org \
    --cc=tdave@nvidia.com \
    --cc=will@kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox