mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT
@ 2026-07-25  9:20 Chen Yu
  2026-07-25  9:22 ` [PATCH v6 1/9] x86/topology: Export topo_lookup_cpuid() for resctrl use Chen Yu
                   ` (8 more replies)
  0 siblings, 9 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:20 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

v5: https://lore.kernel.org/lkml/cover.1782866200.git.yu.c.chen@intel.com/
v4: https://lore.kernel.org/lkml/cover.1781332698.git.yu.c.chen@intel.com/
v3: https://lore.kernel.org/lkml/cover.1780710620.git.yu.c.chen@intel.com/
v2: https://lore.kernel.org/lkml/cover.1780587063.git.yu.c.chen@intel.com/
v1: https://lore.kernel.org/lkml/cover.1779872016.git.yu.c.chen@intel.com/

Intel Enhanced Resource Director Technology (ERDT) extends the existing
RDT framework with two major capabilities:

  1. MMIO-based access to monitoring and allocation registers, replacing
     the legacy MSR-based interface.
  2. Region-aware RDT for fine-grained control over different tiers of
     memory (e.g., CXL.mem, DDR).

This is described in the Intel RDT Architecture Specification:
https://cdrdv2-public.intel.com/789566/356688-intel-rdt-arch-spec.pdf

This patch set focuses on the first part: enabling MMIO-based access for
Cache Monitoring Technology (CMT), while CAT/MBM/MBA are still using MSR.
The platform advertises the MMIO register layout through the ACPI ERDT
(Enhanced Resource Director Technology) table, which contains sub-tables
describing per-domain register regions for monitoring and allocation.

With ERDT, L3 cache occupancy counters are read via MMIO rather than
MSR, allowing the reads to be performed from any CPU without requiring
cross-CPU IPIs. This series parses the relevant ACPI sub-tables (RMDD,
CMRC), prepares the resctrl monitor infrastructure for MMIO-based reads,
and adds initial support for reading L3 occupancy via the CMRC interface.

kselftest of CMT and L3_CAT has passed with minor adjustment at
https://lore.kernel.org/lkml/20260523101715.3964456-1-yu.c.chen@intel.com/.

V6 has gone through local sashiko review, with false positives left
unaddressed.

Major changes from V5 to V6:
- Reorder the series so that the x86/topology change, which touches a
  different subsystem, comes first. (Reinette Chatre)
- Drop the v5 "x86/resctrl: Replace 'msr' in monitoring data identifiers"
  patch. None of the renamed identifiers are used by the MMIO code.
  (Reinette Chatre)
- Split the domain setup into erdt_cpu_valid() and
  erdt_l3_mon_domain_setup(). Validation now happens in
  domain_add_cpu_mon() before the CPU is added to the domain cpumask,
  and attaching ERDT data to a freshly created domain (Reinette Chatre)
- Drop the SNC special case and the new resctrl_disable_mon_event().
  (Reinette Chatre)
- resctrl_arch_round_mon_val() now rounds to the ERDT scale instead of
  returning the value unchanged (Reinette Chatre)
- Make erdt_get_max_rmid() a global value instead of a per-CPU lookup.
  (Reinette Chatre)
- Use struct cpumask instead of cpumask_var_t, list_for_each_entry()
  instead of open coded container_of(), and document every member of
  struct erdt_domain_info. (Reinette Chatre)

Thanks Tony, Reinette, Thomas, Hongyu for your time to look at this patch set.

Changes from V4 to V5:
There are some major changes since v4:
- (biggest change) Eliminate the xarray for runtime lookups; embed
  struct erdt_domain_info directly in rdt_hw_l3_mon_domain and assign
  during l3_mon_domain_setup().
- Separate CPUID and ACPI enumeration cleanly. Do not use CPUID feature
  flags to gate MMIO-based monitoring. Use ACPI table presence (e.g., CMRC table)
  to determine event enablement.
- Use ACPI RMDD's own "Max RMID" field for MMIO access instead of relying
  on CPUID's max RMID (which applies to MSR).
- Enforce the SNC constraint in code rather than burying it behind a comment
  WARN. Disable mon_capable in rdt_get_l3_mon_config() when ERDT is
  enabled and snc_nodes_per_l3_cache > 1.
- Split non-resctrl changes (topology.c, apic.h) into a separate preparatory
  patch prefixed with x86/topology.
- Move the "depends on X86" to "X86_64" adjustment to a separate patch with explicit
  justification in its changelog.

Changes from V3 to V4:
- Remove the redundant table length check in subtbl_valid() (Thomas Gleixner)
- Reuse subtbl_valid() for all the table iteration (Thomas Gleixner)
- Refine the commit log of [PATCH 5/6] to state that this change is a
  preparation for [PATCH 6/6] rather than fixing an existing issue
  (Thomas Gleixner, Reinette Chatre, Tony Luck)
- Fix if CACD lists all CPUs in the LLC domain (sashiko)
- Deal with a corner case that if there is no valid RMDD tables,
  the erdt_enabled_flag should remain false.(sashiko)
- Add Thomas's Reviewed-by and Hongyu's Tested-by.

Changes from V2 to V3:
- Wrap __resctrl_arch_late_init() to avoid the goto logic. (Thomas Gleixner)
- Make the variables in struct erdt_domain_info tabular format (Thomas Gleixner)
- Remove tail comments (Thomas Gleixner)
- Make the name of erdt_enabled() and variable in it consistent and
  comprehensible. (Thomas Gleixner)
- Use topo_lookup_cpuid() to search the CPU id according to the x2apic id
  (Thomas Gleixner)
- Fix kernel doc comment format (Thomas Gleixner)
- Use brackets for multiple lines "if" case. (Thomas Gleixner)
- Let the parameter for cacd_init() to fully utilize 100 characters.
  (Thomas Gleixner)
- Variables are reordered in reverse fir-tree.(Thomas Gleixner)
- Added a named constant and use it in the rmdd->flags check.
  (Thomas Gleixner)
- Introduce helper functions to make the code readable when iterating
  the RMDD tables. (Thomas Gleixner)
- Make the macros tabular format. (Thomas Gleixner)

Changes from V1 to V2:
- Add #include <linux/cleanup.h> to follow the "include-what-you-use" best
  practice (Tony Luck)
- Fix 3 issues reported by:
  https://sashiko.dev/#/patchset/cover.1779872016.git.yu.c.chen%40intel.com
  Remove the variable of cacd in struct erdt_domain_info as it will
  never be used after initialization.
  Invoke erdt_exit() to avoid resource leak if rdt_alloc_capable and
  rdt_mon_capable are both false.
  Adjust the comments suggested by sashiko.

Anil S Keshavamurthy (1):
  x86/resctrl: Parse ACPI ERDT table and save CACD cpumask for RMDD
    domains

Chen Yu (7):
  x86/topology: Export topo_lookup_cpuid() for resctrl use
  x86/resctrl: Require 64-bit x86 for resctrl support
  x86/resctrl: Attach ACPI ERDT information to L3 mon domain on CPU
    online
  x86/resctrl: Parse ACPI CMRC table
  x86/resctrl: Refactor the monitor read function
  x86/resctrl: Introduce erdt_cpu_has() and erdt_support()
  x86/resctrl: Add MMIO-based LLC occupancy monitoring support

Tony Luck (1):
  fs/resctrl: Do not invoke smp_processor_id() in preemptible context

 arch/x86/Kconfig                       |   4 +-
 arch/x86/include/asm/apic.h            |   1 +
 arch/x86/include/asm/resctrl.h         |  11 +-
 arch/x86/kernel/cpu/resctrl/Makefile   |   1 +
 arch/x86/kernel/cpu/resctrl/core.c     |  53 ++-
 arch/x86/kernel/cpu/resctrl/erdt.c     | 453 +++++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/internal.h |  49 ++-
 arch/x86/kernel/cpu/resctrl/monitor.c  |  36 +-
 arch/x86/kernel/cpu/topology.c         |   2 +-
 fs/resctrl/monitor.c                   |  44 ++-
 10 files changed, 627 insertions(+), 27 deletions(-)
 create mode 100644 arch/x86/kernel/cpu/resctrl/erdt.c

-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 1/9] x86/topology: Export topo_lookup_cpuid() for resctrl use
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
@ 2026-07-25  9:22 ` Chen Yu
  2026-07-25  9:22 ` [PATCH v6 2/9] x86/resctrl: Require 64-bit x86 for resctrl support Chen Yu
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:22 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

Enhanced RDT (ERDT) enumerates its monitoring and control resources
through ACPI tables that identify each CPU by its x2APIC ID. To
associate this firmware-provided data with the kernel's per-CPU
structures, ERDT must translate those x2APIC IDs into logical CPU
numbers.

topo_lookup_cpuid() already performs this translation, but it is
static to topology.c and therefore not reachable from resctrl.

Export topo_lookup_cpuid() and add its declaration to asm/apic.h so
that ERDT can resolve x2APIC IDs from ACPI tables to logical CPU
numbers.

No functional change intended.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Move this patch to the beginning of the series because it touches a
    different subsystem than the rest of the series. (Reinette Chatre)
    Order the commit tags per Documentation/process/maintainer-tip.rst.
    (Reinette Chatre)
---
 arch/x86/include/asm/apic.h    | 1 +
 arch/x86/kernel/cpu/topology.c | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/apic.h b/arch/x86/include/asm/apic.h
index 9cd493d467d4..bb84651b14bd 100644
--- a/arch/x86/include/asm/apic.h
+++ b/arch/x86/include/asm/apic.h
@@ -54,6 +54,7 @@ static inline void x86_32_probe_apic(void) { }
 #endif
 
 extern u32 cpuid_to_apicid[];
+int topo_lookup_cpuid(u32 apic_id);
 
 #define CPU_ACPIID_INVALID	U32_MAX
 
diff --git a/arch/x86/kernel/cpu/topology.c b/arch/x86/kernel/cpu/topology.c
index 4913b64ec592..bcee70fb9277 100644
--- a/arch/x86/kernel/cpu/topology.c
+++ b/arch/x86/kernel/cpu/topology.c
@@ -92,7 +92,7 @@ static inline u32 topo_apicid(u32 apicid, enum x86_topology_domains dom)
 	return apicid & (UINT_MAX << x86_topo_system.dom_shifts[dom - 1]);
 }
 
-static int topo_lookup_cpuid(u32 apic_id)
+int topo_lookup_cpuid(u32 apic_id)
 {
 	int i;
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 2/9] x86/resctrl: Require 64-bit x86 for resctrl support
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
  2026-07-25  9:22 ` [PATCH v6 1/9] x86/topology: Export topo_lookup_cpuid() for resctrl use Chen Yu
@ 2026-07-25  9:22 ` Chen Yu
  2026-07-25  9:22 ` [PATCH v6 3/9] x86/resctrl: Parse ACPI ERDT table and save CACD cpumask for RMDD domains Chen Yu
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:22 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

All known hardware that supports Intel RDT or AMD QoS is 64-bit. Future
enhancements like MMIO-based Enhanced RDT (ERDT) monitoring require
readq()/writeq() which are only available on 64-bit. There is also no
realistic use case for users to build a 32-bit kernel on a server and
enable resctrl.

Drop 32-bit support by changing the X86_CPU_RESCTRL dependency from X86
to X86_64.

Since X86_CPU_RESCTRL now implies X86_64, remove the redundant X86_64
dependency from X86_CPU_RESCTRL_INTEL_AET.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Order the tags per Documentation/process/maintainer-tip.rst.
---
 arch/x86/Kconfig | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index bdad90f210e4..6685e120cff3 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -514,7 +514,7 @@ config X86_MPPARSE
 
 config X86_CPU_RESCTRL
 	bool "x86 CPU resource control support"
-	depends on X86 && (CPU_SUP_INTEL || CPU_SUP_AMD)
+	depends on X86_64 && (CPU_SUP_INTEL || CPU_SUP_AMD)
 	depends on MISC_FILESYSTEMS
 	select ARCH_HAS_CPU_RESCTRL
 	select RESCTRL_FS
@@ -537,7 +537,7 @@ config X86_CPU_RESCTRL
 
 config X86_CPU_RESCTRL_INTEL_AET
 	bool "Intel Application Energy Telemetry"
-	depends on X86_64 && X86_CPU_RESCTRL && CPU_SUP_INTEL && INTEL_PMT_TELEMETRY=y && INTEL_TPMI=y
+	depends on X86_CPU_RESCTRL && CPU_SUP_INTEL && INTEL_PMT_TELEMETRY=y && INTEL_TPMI=y
 	help
 	  Enable per-RMID telemetry events in resctrl.
 
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 3/9] x86/resctrl: Parse ACPI ERDT table and save CACD cpumask for RMDD domains
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
  2026-07-25  9:22 ` [PATCH v6 1/9] x86/topology: Export topo_lookup_cpuid() for resctrl use Chen Yu
  2026-07-25  9:22 ` [PATCH v6 2/9] x86/resctrl: Require 64-bit x86 for resctrl support Chen Yu
@ 2026-07-25  9:22 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 4/9] x86/resctrl: Attach ACPI ERDT information to L3 mon domain on CPU online Chen Yu
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:22 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

From: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>

Parse the RMDD subtables within the ERDT ACPI table and their nested
CACD entries to construct per-domain CPU masks.

There is one ERDT table per platform. Each RMDD describes one resource
management domain (RMD), also known as an L3 domain, and carries MMIO
base information for later monitoring support.

For each RMDD, parse the associated CACD, map its x2APIC IDs to logical
CPUs, and save the resulting CPU mask. This mask associates each ERDT
domain with the CPUs that belong to it and is used later when attaching
ERDT data to resctrl monitoring domains.

Suggested-by: Tony Luck <tony.luck@intel.com>
Suggested-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Anil S Keshavamurthy <anil.s.keshavamurthy@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Make the changelog specific to this patch instead of describing all
    ERDT sub-tables, and say "one ERDT table" instead of "one ERDT".
    (Reinette Chatre)
    Move ERDT_MMIO_CMRC_BASE to the patch that uses it. (Reinette Chatre)
    Drop the unused linux/cpu.h and linux/xarray.h includes.
    (Reinette Chatre)
    Rename __erdt_enabled to erdt_enabled and add a comment describing
    what it means. (Reinette Chatre)
    Drop the CPU parameter from erdt_get_max_rmid(). There is one global
    ERDT max RMID, computed while the RMDD tables are parsed.
    (Reinette Chatre)
    Use list_for_each_entry() and list_for_each_entry_safe() instead of
    open coding container_of(). (Reinette Chatre)
    Drop the size_t casts from check_mul_overflow(). (Reinette Chatre)
    Drop the unnecessary braces after "if (!addr)". (Reinette Chatre)
    Use struct cpumask instead of cpumask_var_t and drop the
    zalloc_cpumask_var()/free_cpumask_var() calls. (Reinette Chatre)
    Rename parse_rmdd_entry() to parse_rmdd_table(), an RMDD is a
    sub-table and not an entry. (Reinette Chatre)
    Add the unit to the invalid RMDD length message. (Reinette Chatre)
    Note in a comment that an RMDD table can have more than one CACD
    sub-table. (Reinette Chatre)
    Print the domain IDs of both RMDDs in the sub-table mismatch warning.
    (Reinette Chatre)
    Drop the unclear "max_rmid > INT_MAX" test. (Reinette Chatre)
    Print the ERDT revision found together with the expected one, and use
    %u for unsigned values. (Reinette Chatre)
    Document every member of struct erdt_domain_info and rename its
    struct list_head member from "list" to "entry". (Reinette Chatre)
---
 arch/x86/kernel/cpu/resctrl/Makefile   |   1 +
 arch/x86/kernel/cpu/resctrl/core.c     |  14 +-
 arch/x86/kernel/cpu/resctrl/erdt.c     | 266 +++++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/internal.h |  31 +++
 4 files changed, 311 insertions(+), 1 deletion(-)
 create mode 100644 arch/x86/kernel/cpu/resctrl/erdt.c

diff --git a/arch/x86/kernel/cpu/resctrl/Makefile b/arch/x86/kernel/cpu/resctrl/Makefile
index 273ddfa30836..2216ee084832 100644
--- a/arch/x86/kernel/cpu/resctrl/Makefile
+++ b/arch/x86/kernel/cpu/resctrl/Makefile
@@ -2,6 +2,7 @@
 obj-$(CONFIG_X86_CPU_RESCTRL)		+= core.o rdtgroup.o monitor.o
 obj-$(CONFIG_X86_CPU_RESCTRL)		+= ctrlmondata.o
 obj-$(CONFIG_X86_CPU_RESCTRL_INTEL_AET)	+= intel_aet.o
+obj-$(CONFIG_X86_CPU_RESCTRL)		+= erdt.o
 obj-$(CONFIG_RESCTRL_FS_PSEUDO_LOCK)	+= pseudo_lock.o
 
 # To allow define_trace.h's recursive include:
diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 9c01d2562b7a..23925bcd71d7 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -1013,6 +1013,7 @@ static __init void check_quirks(void)
 
 static __init bool get_rdt_resources(void)
 {
+	erdt_init();
 	rdt_alloc_capable = get_rdt_alloc_resources();
 	rdt_mon_capable = get_rdt_mon_resources();
 
@@ -1114,7 +1115,7 @@ void resctrl_cpu_detect(struct cpuinfo_x86 *c)
 	}
 }
 
-static int __init resctrl_arch_late_init(void)
+static int __init __resctrl_arch_late_init(void)
 {
 	struct rdt_resource *r;
 	int state, ret, i;
@@ -1157,6 +1158,15 @@ static int __init resctrl_arch_late_init(void)
 	return 0;
 }
 
+static int __init resctrl_arch_late_init(void)
+{
+	int ret = __resctrl_arch_late_init();
+
+	if (ret)
+		erdt_exit();
+	return ret;
+}
+
 late_initcall(resctrl_arch_late_init);
 
 static void __exit resctrl_arch_exit(void)
@@ -1166,6 +1176,8 @@ static void __exit resctrl_arch_exit(void)
 	cpuhp_remove_state(rdt_online);
 
 	resctrl_exit();
+
+	erdt_exit();
 }
 
 __exitcall(resctrl_arch_exit);
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
new file mode 100644
index 000000000000..8998cae47090
--- /dev/null
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Enhanced Resource Director Technology (ERDT)
+ *
+ * Copyright (C) 2026 Intel Corporation
+ *
+ */
+
+#define pr_fmt(fmt)     "resctrl: " fmt
+
+#include <linux/acpi.h>
+#include <linux/overflow.h>
+#include <linux/resctrl.h>
+#include <linux/sizes.h>
+
+#include <asm/apic.h>
+
+#include "internal.h"
+
+static LIST_HEAD(domain_info_list);
+
+/* True when the ERDT ACPI table describes at least one domain with at least one CPU. */
+static bool erdt_enabled;
+
+#define ERDT_VALID_VERSION		1
+#define RMDD_FLAG_CPU_L3_DOMAIN		BIT(0)
+
+/* Bitmask of valid sub-tables found in the first RMDD, used to ensure all RMDDs match. */
+static u32 valid_subtbl_mask;
+
+/* Domain ID of the first RMDD that established @valid_subtbl_mask, for diagnostics. */
+static u16 first_rmdd_domain_id;
+
+static int erdt_max_rmid;
+
+int erdt_get_max_rmid(void)
+{
+	return erdt_max_rmid;
+}
+
+static void __iomem *erdt_ioremap(phys_addr_t base, u32 num_pages, const char *desc)
+{
+	void __iomem *addr;
+	size_t size;
+
+	if (check_mul_overflow(num_pages, SZ_4K, &size))
+		return NULL;
+
+	addr = ioremap(base, size);
+	if (!addr)
+		pr_warn(FW_BUG "ERDT: Failed to map %s at phys addr %pa (size: %u pages)\n",
+		       desc, &base, num_pages);
+
+	return addr;
+}
+
+static void erdt_iounmap_domain(struct erdt_domain_info *domain)
+{
+	for (int i = 0; i < ERDT_MMIO_NUM_TYPES; i++) {
+		if (domain->base[i]) {
+			iounmap(domain->base[i]);
+			domain->base[i] = NULL;
+		}
+	}
+}
+
+static void cleanup_one_domain(struct erdt_domain_info *d)
+{
+	erdt_iounmap_domain(d);
+	kfree(d);
+}
+
+/*
+ * Save CACD information for this RMDD:
+ * convert the X2APIC to CPU and save them in a mask.
+ */
+static __init int cacd_init(struct acpi_subtbl_hdr_16 *subtbl,
+			    struct erdt_domain_info *domain_info)
+{
+	struct acpi_erdt_cacd *cacd = (struct acpi_erdt_cacd *)subtbl;
+	int num_ids, cpu;
+
+	if (cacd->header.length < struct_size(cacd, X2APICIDS, 1)) {
+		pr_warn(FW_BUG "Invalid x2apicid CACD table\n");
+		return -EIO;
+	}
+
+	num_ids = (cacd->header.length - sizeof(*cacd)) / sizeof(cacd->X2APICIDS[0]);
+
+	for (int i = 0; i < num_ids; i++) {
+		cpu = topo_lookup_cpuid(cacd->X2APICIDS[i]);
+		if (cpu < 0) {
+			pr_warn(FW_BUG "Unknown x2apicid 0x%x\n", cacd->X2APICIDS[i]);
+			return -EIO;
+		}
+
+		cpumask_set_cpu(cpu, &domain_info->cpu_mask);
+	}
+
+	return 0;
+}
+
+static inline struct acpi_subtbl_hdr_16 *rmdd_subtbl(struct acpi_erdt_rmdd *rmdd)
+{
+	return (void *)rmdd + sizeof(*rmdd);
+}
+
+static inline struct acpi_subtbl_hdr_16 *next_subtbl(struct acpi_subtbl_hdr_16 *subtbl)
+{
+	return (void *)subtbl + subtbl->length;
+}
+
+static inline bool subtbl_valid(void *end, struct acpi_subtbl_hdr_16 *subtbl)
+{
+	/* Ensure the header is within bounds before dereferencing it. */
+	if ((void *)subtbl + sizeof(*subtbl) > end)
+		return false;
+
+	/* A sub-table must be at least as large as its header. */
+	if (subtbl->length < sizeof(*subtbl))
+		return false;
+
+	/* The entire sub-table (including body) must fit within the parent. */
+	if ((void *)subtbl + subtbl->length > end)
+		return false;
+
+	return true;
+}
+
+static __init bool parse_rmdd_table(struct acpi_subtbl_hdr_16 *rmdd_hdr)
+{
+	struct erdt_domain_info *domain_info;
+	struct acpi_subtbl_hdr_16 *subtbl;
+	struct acpi_erdt_rmdd *rmdd;
+	u32 subtbl_mask = 0;
+
+	if (rmdd_hdr->length < sizeof(*rmdd)) {
+		pr_warn(FW_BUG "Invalid RMDD length %u bytes\n", rmdd_hdr->length);
+		return false;
+	}
+
+	rmdd = (struct acpi_erdt_rmdd *)rmdd_hdr;
+
+	/* Quietly ignore non-CPU-based L3 domains */
+	if (!(rmdd->flags & RMDD_FLAG_CPU_L3_DOMAIN))
+		return true;
+
+	domain_info = kzalloc_obj(*domain_info, GFP_KERNEL);
+	if (!domain_info)
+		return false;
+
+	domain_info->dom_id = -1;
+
+	domain_info->base[ERDT_MMIO_RMDD_CREG] =
+		erdt_ioremap(rmdd->creg_base, rmdd->creg_size, "RMDD ctrl base");
+	if (!domain_info->base[ERDT_MMIO_RMDD_CREG])
+		goto cleanup;
+
+	for (subtbl = rmdd_subtbl(rmdd);
+	     subtbl_valid((void *)rmdd + rmdd->header.length, subtbl);
+	     subtbl = next_subtbl(subtbl)) {
+		switch (subtbl->type) {
+		/* An RMDD table has one or more CACD sub-table(s) */
+		case ACPI_ERDT_TYPE_CACD:
+			if (cacd_init(subtbl, domain_info))
+				goto cleanup;
+
+			subtbl_mask |= BIT(ACPI_ERDT_TYPE_CACD);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (!subtbl_mask)
+		goto cleanup;
+
+	/*
+	 * Require all RMDDs to support same set of sub-tables
+	 */
+	if (!valid_subtbl_mask) {
+		valid_subtbl_mask = subtbl_mask;
+		first_rmdd_domain_id = rmdd->domain_id;
+	} else if (subtbl_mask != valid_subtbl_mask) {
+		pr_warn(FW_BUG "RMDD %u sub-table set does not match the first RMDD %u\n",
+			rmdd->domain_id, first_rmdd_domain_id);
+		goto cleanup;
+	}
+
+	if (!rmdd->max_rmid) {
+		pr_warn(FW_BUG "Unreasonable RMDD max_rmid %u\n", rmdd->max_rmid);
+		goto cleanup;
+	}
+	domain_info->max_rmid = rmdd->max_rmid;
+
+	if (!erdt_max_rmid)
+		erdt_max_rmid = rmdd->max_rmid;
+	else
+		erdt_max_rmid = min_t(int, erdt_max_rmid, rmdd->max_rmid);
+
+	list_add(&domain_info->entry, &domain_info_list);
+
+	return true;
+
+cleanup:
+	cleanup_one_domain(domain_info);
+	return false;
+}
+
+void erdt_exit(void)
+{
+	struct erdt_domain_info *d, *tmp;
+
+	list_for_each_entry_safe(d, tmp, &domain_info_list, entry) {
+		list_del(&d->entry);
+		cleanup_one_domain(d);
+	}
+	erdt_enabled = false;
+	valid_subtbl_mask = 0;
+	first_rmdd_domain_id = 0;
+}
+
+static __init int enumerate_erdt_table(struct acpi_table_header *table_hdr)
+{
+	struct acpi_table_erdt *erdt = (struct acpi_table_erdt *)table_hdr;
+	struct acpi_subtbl_hdr_16 *subtbl;
+	void *table_end;
+
+	if (erdt->header.revision != ERDT_VALID_VERSION) {
+		pr_info("Unsupported ERDT table revision %u (expected %u)\n",
+			erdt->header.revision, ERDT_VALID_VERSION);
+		return -EINVAL;
+	}
+
+	if (erdt->header.length < sizeof(*erdt)) {
+		pr_warn(FW_BUG "ERDT: Invalid table length %u bytes\n", erdt->header.length);
+		return -EINVAL;
+	}
+
+	subtbl = (void *)erdt + sizeof(struct acpi_table_erdt);
+	table_end = (void *)erdt + erdt->header.length;
+
+	while (subtbl_valid(table_end, subtbl)) {
+		if (subtbl->type == ACPI_ERDT_TYPE_RMDD &&
+		    !parse_rmdd_table(subtbl))
+			goto cleanup;
+
+		subtbl = next_subtbl(subtbl);
+	}
+
+	if (list_empty(&domain_info_list))
+		goto cleanup;
+
+	erdt_enabled = true;
+
+	return 0;
+
+cleanup:
+	erdt_exit();
+	return -EINVAL;
+}
+
+int __init erdt_init(void)
+{
+	return acpi_table_parse(ACPI_SIG_ERDT, enumerate_erdt_table);
+}
diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
index e3cfa0c10e92..bdff3ea36e62 100644
--- a/arch/x86/kernel/cpu/resctrl/internal.h
+++ b/arch/x86/kernel/cpu/resctrl/internal.h
@@ -21,6 +21,33 @@
 
 #define RMID_VAL_UNAVAIL		BIT_ULL(62)
 
+/*
+ * Index into erdt_domain_info::base[] for each MMIO region.
+ * @ERDT_MMIO_RMDD_CREG: RMDD control register base address
+ */
+enum erdt_mmio_type {
+	ERDT_MMIO_RMDD_CREG,
+	ERDT_MMIO_LAST = ERDT_MMIO_RMDD_CREG
+};
+
+#define ERDT_MMIO_NUM_TYPES	(ERDT_MMIO_LAST + 1)
+
+/**
+ * struct erdt_domain_info - Per-domain ERDT information
+ * @base:	Array of ioremapped MMIO region base addresses, indexed by ERDT_MMIO_* type
+ * @cpu_mask:	CPUs belonging to this resource management domain
+ * @max_rmid:	Maximum RMID supported by this domain
+ * @dom_id:	L3 cache ID shared by all CPUs in this domain (-1 if unset)
+ * @entry:	Links into the global domain_info_list
+ */
+struct erdt_domain_info {
+	void __iomem		*base[ERDT_MMIO_NUM_TYPES];
+	struct cpumask		cpu_mask;
+	u32			max_rmid;
+	int			dom_id;
+	struct list_head	entry;
+};
+
 /*
  * With the above fields in use 62 bits remain in MSR_IA32_QM_CTR for
  * data to be returned. The counter width is discovered from the hardware
@@ -253,4 +280,8 @@ static inline void intel_aet_mon_domain_setup(int cpu, int id, struct rdt_resour
 static inline bool intel_handle_aet_option(bool force_off, char *tok) { return false; }
 #endif
 
+int erdt_get_max_rmid(void);
+int erdt_init(void);
+void erdt_exit(void);
+
 #endif /* _ASM_X86_RESCTRL_INTERNAL_H */
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 4/9] x86/resctrl: Attach ACPI ERDT information to L3 mon domain on CPU online
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (2 preceding siblings ...)
  2026-07-25  9:22 ` [PATCH v6 3/9] x86/resctrl: Parse ACPI ERDT table and save CACD cpumask for RMDD domains Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 5/9] x86/resctrl: Parse ACPI CMRC table Chen Yu
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

Reading LLC occupancy counters via MMIO requires the per-domain ERDT
information, parsed earlier from the ACPI ERDT table, to be reachable
from the resctrl L3 monitoring domain. Nothing links the two yet, so
the monitoring code cannot locate the MMIO registers of a domain.

ERDT and CPUID enumerate CPU-to-L3-domain membership independently:
CPUID leaf 4 describes the L3 cache topology, while the firmware CACD
sub-table lists the CPUs of each ERDT domain. Both views must agree on
a CPU's L3 domain for that CPU to be monitored safely.

When a CPU comes online, validate that firmware and CPUID agree on its
L3 domain before adding it to a resctrl monitoring domain. Exclude the
CPU from all monitoring domains on a mismatch because a topology
inconsistency between ERDT and CPUID indicates a firmware defect that
makes the CPU's domain placement unreliable for any resource. Otherwise
attach the matching ERDT domain information to the L3 monitoring domain
so that later code can read monitoring data via ERDT and its sub-tables.

Suggested-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Call erdt_cpu_valid() in domain_add_cpu_mon() before the CPU is added
    to the domain cpu_mask, and bail out early on a mismatch.
    (Reinette Chatre)
    Drop the redundant pr_warn() in the caller, the callee already warns.
    (Reinette Chatre)
    Use list_for_each_entry(). (Reinette Chatre)
    Make the domain information pointer const instead of documenting it
    as read only in a comment. (Reinette Chatre)
    Adjust the ERDT/CPUID mismatch check that the CPUs disagrees with
    ERDT/CPUID domain is excluded from resctrl.
    (Reinette Chatre)
---
 arch/x86/kernel/cpu/resctrl/core.c     | 11 ++++-
 arch/x86/kernel/cpu/resctrl/erdt.c     | 66 ++++++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/internal.h |  5 ++
 3 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 23925bcd71d7..c2568b29474e 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -580,6 +580,9 @@ static void domain_add_cpu_mon(int cpu, struct rdt_resource *r)
 		return;
 	}
 
+	if (!erdt_cpu_valid(cpu))
+		return;
+
 	hdr = resctrl_find_domain(&r->mon_domains, id, &add_pos);
 	if (hdr)
 		cpumask_set_cpu(cpu, &hdr->cpu_mask);
@@ -589,8 +592,14 @@ static void domain_add_cpu_mon(int cpu, struct rdt_resource *r)
 		/* Update the mbm_assign_mode state for the CPU if supported */
 		if (r->mon.mbm_cntr_assignable)
 			resctrl_arch_mbm_cntr_assign_set_one(r);
-		if (!hdr)
+		if (!hdr) {
 			l3_mon_domain_setup(cpu, id, r, add_pos);
+			hdr = resctrl_find_domain(&r->mon_domains, id, NULL);
+		}
+
+		if (hdr)
+			erdt_l3_mon_domain_setup(cpu, hdr);
+
 		break;
 	case RDT_RESOURCE_PERF_PKG:
 		if (!hdr)
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 8998cae47090..6257869d0db2 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -207,6 +207,72 @@ static __init bool parse_rmdd_table(struct acpi_subtbl_hdr_16 *rmdd_hdr)
 	return false;
 }
 
+bool erdt_cpu_valid(int cpu)
+{
+	struct erdt_domain_info *d;
+	int dom_id;
+
+	if (!erdt_enabled)
+		return true;
+
+	dom_id = get_cpu_cacheinfo_id(cpu, RESCTRL_L3_CACHE);
+	if (dom_id < 0)
+		return true;
+
+	/*
+	 * Find the erdt_domain_info that contains this CPU,
+	 * check if all CPUs in erdt_domain_info's cpumask
+	 * have the same id(L3 id).
+	 *
+	 * For example, erdt_domain_info reports:
+	 * domain0: CPU0, CPU2, domain1: CPU1, CPU3
+	 * rdt_domain_hdr reports:
+	 * domain0: CPU0, CPU1, domain1: CPU2, CPU3
+	 * As a result, CPU1, CPU2 should not be covered by resctrl.
+	 */
+	list_for_each_entry(d, &domain_info_list, entry) {
+
+		if (cpumask_test_cpu(cpu, &d->cpu_mask)) {
+			if (d->dom_id == -1) {
+				d->dom_id = dom_id;
+			} else if (d->dom_id != dom_id) {
+				pr_warn(FW_BUG "CPU%d's id=%d not equal to CACD domain(%*pbl) id=%d, skip this CPU\n",
+					cpu, dom_id, cpumask_pr_args(&d->cpu_mask), d->dom_id);
+
+				return false;
+			}
+
+			return true;
+		}
+	}
+
+	pr_warn(FW_BUG "Cannot find CACD domain for CPU%d\n", cpu);
+	return false;
+}
+
+/*
+ * Associate ERDT table information with this domain.
+ */
+void erdt_l3_mon_domain_setup(int cpu, struct rdt_domain_hdr *hdr)
+{
+	struct rdt_hw_l3_mon_domain *hw_dom;
+	struct erdt_domain_info *d;
+
+	if (!erdt_enabled)
+		return;
+
+	hw_dom = resctrl_to_arch_mon_dom(container_of(hdr, struct rdt_l3_mon_domain, hdr));
+
+	list_for_each_entry(d, &domain_info_list, entry) {
+		if (cpumask_test_cpu(cpu, &d->cpu_mask)) {
+			/* Assign the ERDT information to hw_dom */
+			if (!hw_dom->d_info)
+				hw_dom->d_info = d;
+			return;
+		}
+	}
+}
+
 void erdt_exit(void)
 {
 	struct erdt_domain_info *d, *tmp;
diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
index bdff3ea36e62..bd437c3e5bf0 100644
--- a/arch/x86/kernel/cpu/resctrl/internal.h
+++ b/arch/x86/kernel/cpu/resctrl/internal.h
@@ -99,14 +99,19 @@ struct rdt_hw_ctrl_domain {
  * @arch_mbm_states:	Per-event pointer to the MBM event's saved state.
  *			An MBM event's state is an array of struct arch_mbm_state
  *			indexed by RMID on x86.
+ * @d_info:		ERDT table information of this domain
  *
  * Members of this structure are accessed via helpers that provide abstraction.
  */
 struct rdt_hw_l3_mon_domain {
 	struct rdt_l3_mon_domain	d_resctrl;
 	struct arch_mbm_state		*arch_mbm_states[QOS_NUM_L3_MBM_EVENTS];
+	const struct erdt_domain_info	*d_info;
 };
 
+bool erdt_cpu_valid(int cpu);
+void erdt_l3_mon_domain_setup(int cpu, struct rdt_domain_hdr *hdr);
+
 static inline struct rdt_hw_ctrl_domain *resctrl_to_arch_ctrl_dom(struct rdt_ctrl_domain *r)
 {
 	return container_of(r, struct rdt_hw_ctrl_domain, d_resctrl);
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 5/9] x86/resctrl: Parse ACPI CMRC table
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (3 preceding siblings ...)
  2026-07-25  9:23 ` [PATCH v6 4/9] x86/resctrl: Attach ACPI ERDT information to L3 mon domain on CPU online Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 6/9] x86/resctrl: Refactor the monitor read function Chen Yu
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

The CMRC (Cache Monitoring Registers for CPU Agents Description) sub-table of
ERDT describes the MMIO registers used to read cache monitoring counters (e.g.
LLC occupancy) for an RMD.

Parse each CMRC sub-table, ioremap its register window, and save a copy of the
CMRC table in the corresponding ERDT domain entry so that later monitoring code
can read the counters via MMIO.

Suggested-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
Reviewed-by: Thomas Gleixner <tglx@kernel.org>
---
v5->v6:
    Say that a copy of the CMRC table is saved, the code does not save a
    pointer to the ACPI table. (Reinette Chatre)
    Use cmrc->header.length instead of subtbl->length, for consistency
    with cacd_init(). (Reinette Chatre)
    Replace the "TBD: Only 1 CMRR per domain is allowed?" comment with a
    description of what the code actually does. (Reinette Chatre)
    Introduce ERDT_MMIO_CMRC_BASE here instead of in the RMDD parsing
    patch, since this is the patch that uses it. (Reinette Chatre)
    Order the commit tags per Documentation/process/maintainer-tip.rst.
    (Reinette Chatre)
---
 arch/x86/include/asm/resctrl.h         |  2 +
 arch/x86/kernel/cpu/resctrl/erdt.c     | 57 ++++++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/internal.h |  6 ++-
 3 files changed, 64 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h
index 575f8408a9e7..e60c2aea7ebd 100644
--- a/arch/x86/include/asm/resctrl.h
+++ b/arch/x86/include/asm/resctrl.h
@@ -49,6 +49,8 @@ DECLARE_STATIC_KEY_FALSE(rdt_enable_key);
 DECLARE_STATIC_KEY_FALSE(rdt_alloc_enable_key);
 DECLARE_STATIC_KEY_FALSE(rdt_mon_enable_key);
 
+int erdt_get_scale(void);
+
 static inline bool resctrl_arch_alloc_capable(void)
 {
 	return rdt_alloc_capable;
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 6257869d0db2..422618991927 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -23,6 +23,7 @@ static LIST_HEAD(domain_info_list);
 static bool erdt_enabled;
 
 #define ERDT_VALID_VERSION		1
+#define CMRC_SUPPORTED_INDEX_FN		1
 #define RMDD_FLAG_CPU_L3_DOMAIN		BIT(0)
 
 /* Bitmask of valid sub-tables found in the first RMDD, used to ensure all RMDDs match. */
@@ -33,11 +34,19 @@ static u16 first_rmdd_domain_id;
 
 static int erdt_max_rmid;
 
+/* Scale to bytes for the monitoring counters when ERDT is enabled. */
+static int erdt_scale;
+
 int erdt_get_max_rmid(void)
 {
 	return erdt_max_rmid;
 }
 
+int erdt_get_scale(void)
+{
+	return erdt_scale;
+}
+
 static void __iomem *erdt_ioremap(phys_addr_t base, u32 num_pages, const char *desc)
 {
 	void __iomem *addr;
@@ -67,6 +76,7 @@ static void erdt_iounmap_domain(struct erdt_domain_info *domain)
 static void cleanup_one_domain(struct erdt_domain_info *d)
 {
 	erdt_iounmap_domain(d);
+	kfree(d->cmrc);
 	kfree(d);
 }
 
@@ -100,6 +110,43 @@ static __init int cacd_init(struct acpi_subtbl_hdr_16 *subtbl,
 	return 0;
 }
 
+static __init int cmrc_init(struct acpi_subtbl_hdr_16 *subtbl,
+			    struct erdt_domain_info *domain_info)
+{
+	struct acpi_erdt_cmrc *cmrc = (struct acpi_erdt_cmrc *)subtbl;
+
+	if (cmrc->header.length < sizeof(*cmrc)) {
+		pr_warn(FW_BUG "Truncated CMRC subtable\n");
+		return -EIO;
+	}
+
+	if (cmrc->index_fn != CMRC_SUPPORTED_INDEX_FN) {
+		pr_info("Unsupported CMRC index function %u\n", cmrc->index_fn);
+		return -EIO;
+	}
+
+	if (!cmrc->clump_size) {
+		pr_warn(FW_BUG "CMRC clump_size is zero\n");
+		return -EIO;
+	}
+
+	domain_info->base[ERDT_MMIO_CMRC_BASE] =
+		erdt_ioremap(cmrc->cmt_reg_base, cmrc->cmt_reg_size, "CMRC base");
+	if (!domain_info->base[ERDT_MMIO_CMRC_BASE])
+		return -EIO;
+
+	domain_info->cmrc = kmemdup(cmrc, cmrc->header.length, GFP_KERNEL);
+	if (!domain_info->cmrc) {
+		iounmap(domain_info->base[ERDT_MMIO_CMRC_BASE]);
+		domain_info->base[ERDT_MMIO_CMRC_BASE] = NULL;
+		return -ENOMEM;
+	}
+
+	erdt_scale = max_t(int, erdt_scale, cmrc->up_scale);
+
+	return 0;
+}
+
 static inline struct acpi_subtbl_hdr_16 *rmdd_subtbl(struct acpi_erdt_rmdd *rmdd)
 {
 	return (void *)rmdd + sizeof(*rmdd);
@@ -166,6 +213,16 @@ static __init bool parse_rmdd_table(struct acpi_subtbl_hdr_16 *rmdd_hdr)
 				goto cleanup;
 
 			subtbl_mask |= BIT(ACPI_ERDT_TYPE_CACD);
+			break;
+		case ACPI_ERDT_TYPE_CMRC:
+			/*
+			 * Only one CMRC is supported per domain as there is no
+			 * method to distinguish different CMRCs within a domain.
+			 */
+			if (!(subtbl_mask & BIT(ACPI_ERDT_TYPE_CMRC)) &&
+			    !cmrc_init(subtbl, domain_info))
+				subtbl_mask |= BIT(ACPI_ERDT_TYPE_CMRC);
+
 			break;
 		default:
 			break;
diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
index bd437c3e5bf0..27f28b0fead8 100644
--- a/arch/x86/kernel/cpu/resctrl/internal.h
+++ b/arch/x86/kernel/cpu/resctrl/internal.h
@@ -24,10 +24,12 @@
 /*
  * Index into erdt_domain_info::base[] for each MMIO region.
  * @ERDT_MMIO_RMDD_CREG: RMDD control register base address
+ * @ERDT_MMIO_CMRC_BASE: CMRC monitoring register base address
  */
 enum erdt_mmio_type {
 	ERDT_MMIO_RMDD_CREG,
-	ERDT_MMIO_LAST = ERDT_MMIO_RMDD_CREG
+	ERDT_MMIO_CMRC_BASE,
+	ERDT_MMIO_LAST = ERDT_MMIO_CMRC_BASE
 };
 
 #define ERDT_MMIO_NUM_TYPES	(ERDT_MMIO_LAST + 1)
@@ -35,6 +37,7 @@ enum erdt_mmio_type {
 /**
  * struct erdt_domain_info - Per-domain ERDT information
  * @base:	Array of ioremapped MMIO region base addresses, indexed by ERDT_MMIO_* type
+ * @cmrc:	Copy of the ACPI CMRC sub-table for this domain
  * @cpu_mask:	CPUs belonging to this resource management domain
  * @max_rmid:	Maximum RMID supported by this domain
  * @dom_id:	L3 cache ID shared by all CPUs in this domain (-1 if unset)
@@ -42,6 +45,7 @@ enum erdt_mmio_type {
  */
 struct erdt_domain_info {
 	void __iomem		*base[ERDT_MMIO_NUM_TYPES];
+	struct acpi_erdt_cmrc	*cmrc;
 	struct cpumask		cpu_mask;
 	u32			max_rmid;
 	int			dom_id;
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 6/9] x86/resctrl: Refactor the monitor read function
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (4 preceding siblings ...)
  2026-07-25  9:23 ` [PATCH v6 5/9] x86/resctrl: Parse ACPI CMRC table Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 7/9] fs/resctrl: Do not invoke smp_processor_id() in preemptible context Chen Yu
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

Split the monitor read helper into an L3 read path and an AET
(Intel Application Energy Telemetry) read path. This makes the
two distinct monitoring sources easier to extend independently
and prepares the L3 path for ERDT-based MMIO reads.

No functional change.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Drop "added in a later patch". (Reinette Chatre)
---
 arch/x86/kernel/cpu/resctrl/monitor.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/arch/x86/kernel/cpu/resctrl/monitor.c b/arch/x86/kernel/cpu/resctrl/monitor.c
index 569894d6e5c8..786828a0a3c2 100644
--- a/arch/x86/kernel/cpu/resctrl/monitor.c
+++ b/arch/x86/kernel/cpu/resctrl/monitor.c
@@ -239,9 +239,9 @@ static u64 get_corrected_val(struct rdt_resource *r, struct rdt_l3_mon_domain *d
 	return chunks * hw_res->mon_scale;
 }
 
-int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
-			   u32 unused, u32 rmid, enum resctrl_event_id eventid,
-			   void *arch_priv, u64 *val, void *ignored)
+static int arch_l3_read_event(struct rdt_domain_hdr *hdr, u32 rmid,
+			      enum resctrl_event_id eventid, u64 *val,
+			      struct rdt_resource *r)
 {
 	struct rdt_hw_l3_mon_domain *hw_dom;
 	struct rdt_l3_mon_domain *d;
@@ -251,11 +251,6 @@ int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
 	int cpu;
 	int ret;
 
-	resctrl_arch_rmid_read_context_check();
-
-	if (r->rid == RDT_RESOURCE_PERF_PKG)
-		return intel_aet_read_event(hdr->id, rmid, arch_priv, val);
-
 	if (!domain_header_is_valid(hdr, RESCTRL_MON_DOMAIN, RDT_RESOURCE_L3))
 		return -EINVAL;
 
@@ -281,6 +276,22 @@ int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
 	return ret;
 }
 
+int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
+			   u32 unused, u32 rmid, enum resctrl_event_id eventid,
+			   void *arch_priv, u64 *val, void *ignored)
+{
+	resctrl_arch_rmid_read_context_check();
+
+	switch (r->rid) {
+	case RDT_RESOURCE_L3:
+		return arch_l3_read_event(hdr, rmid, eventid, val, r);
+	case RDT_RESOURCE_PERF_PKG:
+		return intel_aet_read_event(hdr->id, rmid, arch_priv, val);
+	default:
+		return -EINVAL;
+	}
+}
+
 static int __cntr_id_read(u32 cntr_id, u64 *val)
 {
 	u64 msr_val;
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 7/9] fs/resctrl: Do not invoke smp_processor_id() in preemptible context
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (5 preceding siblings ...)
  2026-07-25  9:23 ` [PATCH v6 6/9] x86/resctrl: Refactor the monitor read function Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 8/9] x86/resctrl: Introduce erdt_cpu_has() and erdt_support() Chen Yu
  2026-07-25  9:23 ` [PATCH v6 9/9] x86/resctrl: Add MMIO-based LLC occupancy monitoring support Chen Yu
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

From: Tony Luck <tony.luck@intel.com>

LLC occupancy can be read on any CPU when the counter is accessed via
MMIO, so such an event is read from task context on whatever CPU the
caller happens to be running on rather than being bounced to a CPU in
the monitoring domain. mon_evt::any_cpu marks these CPU-agnostic events.

__l3_mon_event_count() calls smp_processor_id() to find the CPU to read
from. For an any_cpu event that lookup is unsafe:
the code runs in preemptible task context, so smp_processor_id() emits a
debug warning.

Skip the current-CPU lookup when an event's any_cpu flag is set, events with
this flag do not require execution on a specific CPU. For legacy MSR-based
access, update rmid_read::err if the reading of the event was dispatched to
a wrong CPU, according to the change at:
https://lore.kernel.org/lkml/6b3c66a49788828bd8c04a6911bd74c91ccd56f3.1782857711.git.reinette.chatre@intel.com/

Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Drop "in a follow-up patch" from the changelog. (Reinette Chatre)
    Set rmid_read::err when an MSR based read is dispatched to a CPU
    outside the monitoring domain, to align with the conflicting change
    at
    https://lore.kernel.org/lkml/6b3c66a49788828bd8c04a6911bd74c91ccd56f3.1782857711.git.reinette.chatre@intel.com/
    (Reinette Chatre)
---
 fs/resctrl/monitor.c | 44 ++++++++++++++++++++++++++++++++++----------
 1 file changed, 34 insertions(+), 10 deletions(-)

diff --git a/fs/resctrl/monitor.c b/fs/resctrl/monitor.c
index a932a1fea818..c08d2f90d721 100644
--- a/fs/resctrl/monitor.c
+++ b/fs/resctrl/monitor.c
@@ -422,9 +422,37 @@ static void mbm_cntr_free(struct rdt_l3_mon_domain *d, int cntr_id)
 	memset(&d->cntr_cfg[cntr_id], 0, sizeof(*d->cntr_cfg));
 }
 
+/**
+ * cpu_on_correct_domain() - Check if current CPU is in the correct
+ *			      domain for the event.
+ * @rr: The rmid_read structure containing event and domain information.
+ *
+ * Context: Preemptible process context when @rr->evt->any_cpu is set.
+ *          Non-migratable process context (via smp_call_on_cpu()) or
+ *          non-preemptible context (via smp_call_function_any()) when
+ *          the event must be read on a specific CPU.
+ * Return: true if the current CPU can read this event, false otherwise.
+ */
+static bool cpu_on_correct_domain(struct rmid_read *rr)
+{
+	int cpu;
+
+	/* Any CPU is OK for this event */
+	if (rr->evt->any_cpu)
+		return true;
+
+	cpu = smp_processor_id();
+
+	/* Single domain. Must be on a CPU in that domain. */
+	if (rr->hdr)
+		return cpumask_test_cpu(cpu, &rr->hdr->cpu_mask);
+
+	/* Summing domains that share a cache, must be on a CPU for that cache. */
+	return cpumask_test_cpu(cpu, &rr->ci->shared_cpu_map);
+}
+
 static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
 {
-	int cpu = smp_processor_id();
 	u32 closid = rdtgrp->closid;
 	u32 rmid = rdtgrp->mon.rmid;
 	struct rdt_l3_mon_domain *d;
@@ -457,9 +485,6 @@ static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
 		return 0;
 	}
 
-	/* Reading a single domain, must be on a CPU in that domain. */
-	if (!cpumask_test_cpu(cpu, &d->hdr.cpu_mask))
-		return -EINVAL;
 	if (rr->is_mbm_cntr)
 		rr->err = resctrl_arch_cntr_read(rr->r, d, closid, rmid, cntr_id,
 						 rr->evt->evtid, &tval);
@@ -477,7 +502,6 @@ static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
 
 static int __l3_mon_event_count_sum(struct rdtgroup *rdtgrp, struct rmid_read *rr)
 {
-	int cpu = smp_processor_id();
 	u32 closid = rdtgrp->closid;
 	u32 rmid = rdtgrp->mon.rmid;
 	struct rdt_l3_mon_domain *d;
@@ -495,10 +519,6 @@ static int __l3_mon_event_count_sum(struct rdtgroup *rdtgrp, struct rmid_read *r
 		return -EINVAL;
 	}
 
-	/* Summing domains that share a cache, must be on a CPU for that cache. */
-	if (!cpumask_test_cpu(cpu, &rr->ci->shared_cpu_map))
-		return -EINVAL;
-
 	/*
 	 * Legacy files must report the sum of an event across all
 	 * domains that share the same L3 cache instance.
@@ -529,7 +549,11 @@ static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
 {
 	switch (rr->r->rid) {
 	case RDT_RESOURCE_L3:
-		WARN_ON_ONCE(rr->evt->any_cpu);
+		if (!cpu_on_correct_domain(rr)) {
+			rr->err = -EIO;
+			return -EINVAL;
+		}
+
 		if (rr->hdr)
 			return __l3_mon_event_count(rdtgrp, rr);
 		else
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 8/9] x86/resctrl: Introduce erdt_cpu_has() and erdt_support()
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (6 preceding siblings ...)
  2026-07-25  9:23 ` [PATCH v6 7/9] fs/resctrl: Do not invoke smp_processor_id() in preemptible context Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  2026-07-25  9:23 ` [PATCH v6 9/9] x86/resctrl: Add MMIO-based LLC occupancy monitoring support Chen Yu
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

Introduce erdt_cpu_has() to verify if a specific RDT feature is backed
by an ERDT table. erdt_cpu_has() is derived from rdt_cpu_has(), which
not only considers firmware (ERDT table and its sub-tables) support for
an event, but also considers userspace input like "rdt=!cmt".
Similar to rdt_cpu_has(), erdt_cpu_has() expects an x86 feature flag
as parameter.

Introduce erdt_support() as a helper called by erdt_cpu_has() to check
whether the ERDT firmware supports a given feature.

erdt_support() deliberately claims no feature yet and returns false. A
feature may only be reported as ERDT backed once the low level accessors
that read its monitoring data via MMIO exist, otherwise callers would
take the ERDT path and find no implementation behind it. Features are
added to erdt_support() as their MMIO implementations are introduced.

Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    New patch. Split out of the v5 "x86/resctrl: Introduce helpers to
    read L3 occupancy via MMIO" patch, which carried two logical
    changes. (Reinette Chatre)
    Rename erdt_support_features() to erdt_support(). (Reinette Chatre)
    Reword "expects x86 feature flag as parameters" to "expects an x86
    feature flag as parameter". (Reinette Chatre)
    Explain in the changelog why erdt_support() returns false here: a
    feature is only reported as ERDT backed once the MMIO accessors that
    read its monitoring data exist. (Reinette Chatre)
    Drop the unused asm/cpufeatures.h include. (Reinette Chatre)
---
 arch/x86/include/asm/resctrl.h         |  1 +
 arch/x86/kernel/cpu/resctrl/core.c     | 23 +++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/erdt.c     |  5 +++++
 arch/x86/kernel/cpu/resctrl/internal.h |  2 ++
 4 files changed, 31 insertions(+)

diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h
index e60c2aea7ebd..5491853113dd 100644
--- a/arch/x86/include/asm/resctrl.h
+++ b/arch/x86/include/asm/resctrl.h
@@ -49,6 +49,7 @@ DECLARE_STATIC_KEY_FALSE(rdt_enable_key);
 DECLARE_STATIC_KEY_FALSE(rdt_alloc_enable_key);
 DECLARE_STATIC_KEY_FALSE(rdt_mon_enable_key);
 
+bool erdt_cpu_has(int flag);
 int erdt_get_scale(void);
 
 static inline bool resctrl_arch_alloc_capable(void)
diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index c2568b29474e..74087d04f1da 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -887,6 +887,29 @@ bool rdt_cpu_has(int flag)
 	return ret;
 }
 
+bool erdt_cpu_has(int flag)
+{
+	struct rdt_options *o;
+	bool ret;
+
+	ret = erdt_support(flag);
+
+	if (!ret)
+		return ret;
+
+	for (o = rdt_options; o < &rdt_options[NUM_RDT_OPTIONS]; o++) {
+		if (flag == o->flag) {
+			if (o->force_off)
+				ret = false;
+			if (o->force_on)
+				ret = true;
+			break;
+		}
+	}
+
+	return ret;
+}
+
 bool resctrl_arch_is_evt_configurable(enum resctrl_event_id evt)
 {
 	if (!rdt_cpu_has(X86_FEATURE_BMEC))
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 422618991927..167d2dfc368d 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -37,6 +37,11 @@ static int erdt_max_rmid;
 /* Scale to bytes for the monitoring counters when ERDT is enabled. */
 static int erdt_scale;
 
+bool erdt_support(int flag)
+{
+	return false;
+}
+
 int erdt_get_max_rmid(void)
 {
 	return erdt_max_rmid;
diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
index 27f28b0fead8..d14a36d65b05 100644
--- a/arch/x86/kernel/cpu/resctrl/internal.h
+++ b/arch/x86/kernel/cpu/resctrl/internal.h
@@ -289,6 +289,8 @@ static inline void intel_aet_mon_domain_setup(int cpu, int id, struct rdt_resour
 static inline bool intel_handle_aet_option(bool force_off, char *tok) { return false; }
 #endif
 
+bool erdt_support(int flag);
+bool erdt_cpu_has(int flag);
 int erdt_get_max_rmid(void);
 int erdt_init(void);
 void erdt_exit(void);
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH v6 9/9] x86/resctrl: Add MMIO-based LLC occupancy monitoring support
  2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
                   ` (7 preceding siblings ...)
  2026-07-25  9:23 ` [PATCH v6 8/9] x86/resctrl: Introduce erdt_cpu_has() and erdt_support() Chen Yu
@ 2026-07-25  9:23 ` Chen Yu
  8 siblings, 0 replies; 10+ messages in thread
From: Chen Yu @ 2026-07-25  9:23 UTC (permalink / raw)
  To: tony.luck, reinette.chatre
  Cc: tglx, bp, mingo, dave.hansen, hpa, fenghuay, babu.moger, chen.yu,
	x86, linux-kernel

Add erdt_mon_read() to read LLC occupancy via MMIO and use it when
the platform supports ERDT. Register the L3 occupancy event with
ERDT enabled when available, falling back to the MSR-based path
otherwise.

Use the CMRC (Cache Monitoring Registers for CPU Agents Description)
ACPI sub-table to read LLC occupancy counters for each RMID via MMIO
when ERDT is enabled. This CMRC information is stored in the
rdt_hw_l3_mon_domain, which could be accessed directly.

Currently, the per-domain limbo handler is still in use. There is no need
to switch to a global limbo handler, because even after such a switch, the
worker thread would still have to iterate through all domains one by one.
The per-domain handler already accomplishes this using a worker thread rather
than costly IPIs, so there is no clear benefit to switching to a global handler.

Suggested-by: Reinette Chatre <reinette.chatre@intel.com>
Signed-off-by: Chen Yu <yu.c.chen@intel.com>
Tested-by: Hongyu Ning <hongyu.ning@linux.intel.com>
---
v5->v6:
    Merge the MMIO read helper from the v5 "x86/resctrl: Introduce
    helpers to read L3 occupancy via MMIO" patch into the v5
    "x86/resctrl: Enable read L3 occupancy via MMIO" patch, so that
    erdt_mon_read() is introduced in the same patch that calls it and
    the series stays bisectable.
    Round to the ERDT scale in resctrl_arch_round_mon_val() instead of
    returning the value unchanged (Reinette Chatre)
    Explain in the changelog why the per-domain limbo handler is kept
    even though the occupancy counters can be read from any CPU.
    Drop the SNC WARN_ONCE() and the new resctrl_disable_mon_event()
    API. (Reinette Chatre)
    Use accurate types for erdt_mon_read(): u32 for the RMID and
    enum resctrl_event_id for the event. (Reinette Chatre)
    Compute the CMRC MMIO offset in cmrc_index_function_1() only,
    instead of open coding the same formula twice. (Reinette Chatre)
    Fix the Unavailable bit of the MMIO L3 occupancy counter. Per the
    RDT architecture specification it is bit 63 and not bit 62, and it
    is only defined when bit 0 of the CMRC flags is set.
    Document that struct rdt_hw_resource::mon_scale only applies to the
    MSR path. The v5 patch "x86/resctrl: Replace 'msr' in monitoring
    data identifiers" that carried this note is dropped.
    (Reinette Chatre)
---
 arch/x86/include/asm/resctrl.h         |  8 +++-
 arch/x86/kernel/cpu/resctrl/core.c     |  5 ++-
 arch/x86/kernel/cpu/resctrl/erdt.c     | 59 ++++++++++++++++++++++++++
 arch/x86/kernel/cpu/resctrl/internal.h |  7 ++-
 arch/x86/kernel/cpu/resctrl/monitor.c  |  9 +++-
 5 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h
index 5491853113dd..0948f64856ef 100644
--- a/arch/x86/include/asm/resctrl.h
+++ b/arch/x86/include/asm/resctrl.h
@@ -132,7 +132,13 @@ static inline void __resctrl_sched_in(struct task_struct *tsk)
 
 static inline unsigned int resctrl_arch_round_mon_val(unsigned int val)
 {
-	unsigned int scale = boot_cpu_data.x86_cache_occ_scale;
+	unsigned int scale = boot_cpu_data.x86_cache_occ_scale, escale;
+
+	if (erdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC)) {
+		escale = erdt_get_scale();
+		if (escale)
+			scale = escale;
+	}
 
 	/* h/w works in units of "boot_cpu_data.x86_cache_occ_scale" */
 	val /= scale;
diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 74087d04f1da..b528023a7bcc 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -997,7 +997,10 @@ static __init bool get_rdt_mon_resources(void)
 	struct rdt_resource *r = &rdt_resources_all[RDT_RESOURCE_L3].r_resctrl;
 	bool ret = false;
 
-	if (rdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC)) {
+	if (erdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC)) {
+		resctrl_enable_mon_event(QOS_L3_OCCUP_EVENT_ID, true, 0, NULL);
+		ret = true;
+	} else if (rdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC)) {
 		resctrl_enable_mon_event(QOS_L3_OCCUP_EVENT_ID, false, 0, NULL);
 		ret = true;
 	}
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 167d2dfc368d..5f9377c80be6 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -26,6 +26,10 @@ static bool erdt_enabled;
 #define CMRC_SUPPORTED_INDEX_FN		1
 #define RMDD_FLAG_CPU_L3_DOMAIN		BIT(0)
 
+/* Set in a monitoring counter when it holds no valid data to report. */
+#define UNAVAILABLE_COUNTER		BIT_ULL(63)
+#define CMRC_FLAG_UNAVAILABLE_BIT	BIT(0)
+
 /* Bitmask of valid sub-tables found in the first RMDD, used to ensure all RMDDs match. */
 static u32 valid_subtbl_mask;
 
@@ -39,6 +43,9 @@ static int erdt_scale;
 
 bool erdt_support(int flag)
 {
+	if (flag == X86_FEATURE_CQM_OCCUP_LLC)
+		return valid_subtbl_mask & BIT(ACPI_ERDT_TYPE_CMRC);
+
 	return false;
 }
 
@@ -52,6 +59,58 @@ int erdt_get_scale(void)
 	return erdt_scale;
 }
 
+static u32 cmrc_index_function_1(struct acpi_erdt_cmrc *cmrc, u32 rmid)
+{
+	/*
+	 * MMIO_offset_for_RMID# =
+	 *   (RMID / ClumpSize) * Stride +
+	 *   (RMID % ClumpSize) * 8
+	 */
+	return (rmid / cmrc->clump_size) * cmrc->clump_stride +
+	       (rmid % cmrc->clump_size) * 8;
+}
+
+static int erdt_read_l3_occupancy(const struct erdt_domain_info *d, u32 rmid, u64 *val)
+{
+	struct acpi_erdt_cmrc *cmrc;
+	u64 l3_cmt_count;
+	u32 offset;
+
+	cmrc = d->cmrc;
+	if (!cmrc)
+		return -EIO;
+
+	offset = cmrc_index_function_1(cmrc, rmid);
+	/* Overflow of cmt_reg_size * SZ_4K already validated in erdt_ioremap(). */
+	if (offset + sizeof(u64) > (u32)cmrc->cmt_reg_size * SZ_4K)
+		return -EINVAL;
+
+	l3_cmt_count = readq(d->base[ERDT_MMIO_CMRC_BASE] + offset);
+	if ((cmrc->flags & CMRC_FLAG_UNAVAILABLE_BIT) &&
+	    (l3_cmt_count & UNAVAILABLE_COUNTER))
+		return -EINVAL;
+
+	*val = l3_cmt_count * cmrc->up_scale;
+
+	return 0;
+}
+
+int erdt_mon_read(struct rdt_domain_hdr *hdr, enum resctrl_event_id evtid, u32 rmid, u64 *val)
+{
+	struct rdt_hw_l3_mon_domain *hw_dom;
+	const struct erdt_domain_info *d;
+
+	hw_dom = resctrl_to_arch_mon_dom(container_of(hdr, struct rdt_l3_mon_domain, hdr));
+	d = hw_dom->d_info;
+	if (!d)
+		return -EIO;
+
+	if (evtid == QOS_L3_OCCUP_EVENT_ID)
+		return erdt_read_l3_occupancy(d, rmid, val);
+
+	return -EIO;
+}
+
 static void __iomem *erdt_ioremap(phys_addr_t base, u32 num_pages, const char *desc)
 {
 	void __iomem *addr;
diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h
index d14a36d65b05..a3cb32e8fabc 100644
--- a/arch/x86/kernel/cpu/resctrl/internal.h
+++ b/arch/x86/kernel/cpu/resctrl/internal.h
@@ -158,7 +158,11 @@ struct msr_param {
  *			which has been corrected for features like CDP.
  * @msr_base:		Base MSR address for CBMs
  * @msr_update:		Function pointer to update QOS MSRs
- * @mon_scale:		cqm counter * mon_scale = occupancy in bytes
+ * @mon_scale:		Scale factor applied to a raw counter value on the
+ *			MSR-based read path: CMT occupancy counter * mon_scale =
+ *			occupancy in bytes, and MBM chunk count * mon_scale = bytes
+ *			transferred. ERDT reads occupancy via MMIO and applies its
+ *			own firmware-provided scale instead.
  * @mbm_width:		Monitor width, to detect and correct for overflow.
  * @cdp_enabled:	CDP state of this resource
  * @mbm_cntr_assign_enabled:	ABMC feature is enabled
@@ -292,6 +296,7 @@ static inline bool intel_handle_aet_option(bool force_off, char *tok) { return f
 bool erdt_support(int flag);
 bool erdt_cpu_has(int flag);
 int erdt_get_max_rmid(void);
+int erdt_mon_read(struct rdt_domain_hdr *hdr, enum resctrl_event_id evtid, u32 rmid, u64 *val);
 int erdt_init(void);
 void erdt_exit(void);
 
diff --git a/arch/x86/kernel/cpu/resctrl/monitor.c b/arch/x86/kernel/cpu/resctrl/monitor.c
index 786828a0a3c2..d33a865e59d4 100644
--- a/arch/x86/kernel/cpu/resctrl/monitor.c
+++ b/arch/x86/kernel/cpu/resctrl/monitor.c
@@ -284,6 +284,10 @@ int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,
 
 	switch (r->rid) {
 	case RDT_RESOURCE_L3:
+		if (eventid == QOS_L3_OCCUP_EVENT_ID &&
+		    erdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC))
+			return erdt_mon_read(hdr, eventid, rmid, val);
+
 		return arch_l3_read_event(hdr, rmid, eventid, val, r);
 	case RDT_RESOURCE_PERF_PKG:
 		return intel_aet_read_event(hdr->id, rmid, arch_priv, val);
@@ -430,12 +434,15 @@ int __init rdt_get_l3_mon_config(struct rdt_resource *r)
 	struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
 	unsigned int threshold;
 	u32 eax, ebx, ecx, edx;
+	int max_rmid;
 
 	snc_nodes_per_l3_cache = snc_get_config();
 
+	max_rmid = erdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC) ?
+						erdt_get_max_rmid() : boot_cpu_data.x86_cache_max_rmid;
 	resctrl_rmid_realloc_limit = boot_cpu_data.x86_cache_size * 1024;
 	hw_res->mon_scale = boot_cpu_data.x86_cache_occ_scale / snc_nodes_per_l3_cache;
-	r->mon.num_rmid = (boot_cpu_data.x86_cache_max_rmid + 1) / snc_nodes_per_l3_cache;
+	r->mon.num_rmid = (max_rmid + 1) / snc_nodes_per_l3_cache;
 	hw_res->mbm_width = MBM_CNTR_WIDTH_BASE;
 
 	if (mbm_offset > 0 && mbm_offset <= MBM_CNTR_WIDTH_OFFSET_MAX)
-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-07-25  9:33 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-25  9:20 [PATCH v6 0/9] Introduce MMIO-based CMT access for Enhanced RDT Chen Yu
2026-07-25  9:22 ` [PATCH v6 1/9] x86/topology: Export topo_lookup_cpuid() for resctrl use Chen Yu
2026-07-25  9:22 ` [PATCH v6 2/9] x86/resctrl: Require 64-bit x86 for resctrl support Chen Yu
2026-07-25  9:22 ` [PATCH v6 3/9] x86/resctrl: Parse ACPI ERDT table and save CACD cpumask for RMDD domains Chen Yu
2026-07-25  9:23 ` [PATCH v6 4/9] x86/resctrl: Attach ACPI ERDT information to L3 mon domain on CPU online Chen Yu
2026-07-25  9:23 ` [PATCH v6 5/9] x86/resctrl: Parse ACPI CMRC table Chen Yu
2026-07-25  9:23 ` [PATCH v6 6/9] x86/resctrl: Refactor the monitor read function Chen Yu
2026-07-25  9:23 ` [PATCH v6 7/9] fs/resctrl: Do not invoke smp_processor_id() in preemptible context Chen Yu
2026-07-25  9:23 ` [PATCH v6 8/9] x86/resctrl: Introduce erdt_cpu_has() and erdt_support() Chen Yu
2026-07-25  9:23 ` [PATCH v6 9/9] x86/resctrl: Add MMIO-based LLC occupancy monitoring support Chen Yu

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®