[RFC PATCH 6/6] x86/resctrl: Add support for L3 occupancy monitoring via RMID MMIO read

From: Chen Yu

Date: Wed May 27 2026 - 05:43:31 EST


The CMRC (Cache Monitoring Registers for CPU Agents Description)
ACPI sub-table provides the MMIO address used to read the LLC
occupancy counter for each RMID. When ERDT is enabled on the
platform, use this MMIO interface instead of the legacy MSR read
to obtain the L3 occupancy value.

Introduce erdt_mon_read(), a helper that retrieves monitoring
data for a given RMID and event ID from an ERDT domain. Initial
support is added for the L3 occupancy monitoring event
(QOS_L3_OCCUP_EVENT_ID).

If the platform supports ERDT, CMRC-based MMIO access is used by
default. If ERDT is unavailable, the implementation is to use
MSR-based operations.

Suggested-by: Tony Luck <tony.luck@xxxxxxxxx>
Co-developed-by: Anil S Keshavamurthy <anil.s.keshavamurthy@xxxxxxxxx>
Signed-off-by: Anil S Keshavamurthy <anil.s.keshavamurthy@xxxxxxxxx>
Signed-off-by: Chen Yu <yu.c.chen@xxxxxxxxx>
---
arch/x86/include/asm/resctrl.h | 2 +
arch/x86/kernel/cpu/resctrl/core.c | 2 +-
arch/x86/kernel/cpu/resctrl/erdt.c | 90 +++++++++++++++++++++++++++
arch/x86/kernel/cpu/resctrl/monitor.c | 7 +++
4 files changed, 100 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h
index 97c2f6bc7a5f..9b3b03279dd8 100644
--- a/arch/x86/include/asm/resctrl.h
+++ b/arch/x86/include/asm/resctrl.h
@@ -41,6 +41,8 @@ struct resctrl_pqr_state {
};

bool erdt_enabled(void);
+struct rdt_domain_hdr;
+int erdt_mon_read(struct rdt_domain_hdr *hdr, int ev_id, int rmid, u64 *val);

DECLARE_PER_CPU(struct resctrl_pqr_state, pqr_state);

diff --git a/arch/x86/kernel/cpu/resctrl/core.c b/arch/x86/kernel/cpu/resctrl/core.c
index 893d2efe76f0..6e0feae2595c 100644
--- a/arch/x86/kernel/cpu/resctrl/core.c
+++ b/arch/x86/kernel/cpu/resctrl/core.c
@@ -965,7 +965,7 @@ static __init bool get_rdt_mon_resources(void)
bool ret = false;

if (rdt_cpu_has(X86_FEATURE_CQM_OCCUP_LLC)) {
- resctrl_enable_mon_event(QOS_L3_OCCUP_EVENT_ID, false, 0, NULL);
+ resctrl_enable_mon_event(QOS_L3_OCCUP_EVENT_ID, erdt_enabled(), 0, NULL);
ret = true;
}
if (rdt_cpu_has(X86_FEATURE_CQM_MBM_TOTAL)) {
diff --git a/arch/x86/kernel/cpu/resctrl/erdt.c b/arch/x86/kernel/cpu/resctrl/erdt.c
index 30b7dfd99086..6180f14ab377 100644
--- a/arch/x86/kernel/cpu/resctrl/erdt.c
+++ b/arch/x86/kernel/cpu/resctrl/erdt.c
@@ -39,6 +39,7 @@ static DEFINE_XARRAY(erdt_domain_xa); /* Indexed by L3 cache ID */

#define ERDT_VALID_VERSION 1
#define CMRC_VALID_INDEX_FUNC_VERSION 1
+#define UNAVAILABLE_COUNTER BIT_ULL(63)

static u32 valid_subtbl_mask;

@@ -65,6 +66,95 @@ static __init int lookup_logical_cpu_by_x2apicid(u32 x2apicid)
return -1;
}

+static void __iomem *cmrc_index_function_1(struct erdt_domain_info *d,
+ struct acpi_erdt_cmrc *cmrc, int rmid)
+{
+ u16 clump_size, stride_size;
+ void __iomem *vaddr;
+
+ clump_size = cmrc->clump_size;
+ stride_size = cmrc->clump_stride;
+
+ /*
+ * MMIO_ADDRESS_for_RMID# = CMRC Base +
+ * (RMID / ClumpSize) * Stride +
+ * (RMID % ClumpSize) * 8
+ */
+ vaddr = d->base[ERDT_MMIO_CMRC_BASE] +
+ (rmid / clump_size) * stride_size +
+ (rmid % clump_size) * 8;
+
+ return vaddr;
+}
+
+/*
+ * erdt_read_l3_occupancy - Read L3 occupancy count for a given RMID
+ * @d: Pointer to the ERDT domain info
+ * @rmid: Resource Monitoring ID to read occupancy for
+ *
+ * Calculates the MMIO address using clump and stride information
+ * from the CMRC ACPI structure and reads the L3 cache occupancy
+ * count for the given RMID. The raw value is scaled using the
+ * up_scale factor provided by firmware.
+ *
+ * Return: 0 for success, error code for other cases.
+ */
+static int erdt_read_l3_occupancy(struct erdt_domain_info *d, int rmid,
+ u64 *val)
+{
+ struct acpi_erdt_cmrc *cmrc;
+ void __iomem *vaddr;
+ u64 l3_cmt_count;
+ u32 offset;
+
+ cmrc = d->cmrc;
+ if (!cmrc)
+ return -EIO;
+
+ offset = (rmid / cmrc->clump_size) * cmrc->clump_stride +
+ (rmid % cmrc->clump_size) * 8;
+ if (offset + sizeof(u64) > (u32)cmrc->cmt_reg_size << 12)
+ return -EINVAL;
+
+ vaddr = cmrc_index_function_1(d, cmrc, rmid);
+
+ l3_cmt_count = readq(vaddr);
+ if (l3_cmt_count & UNAVAILABLE_COUNTER)
+ return -EINVAL;
+
+ *val = l3_cmt_count * cmrc->up_scale;
+
+ return 0;
+}
+
+/*
+ * erdt_mon_read - Read monitoring data for a given domain and RMID
+ * @hdr: Domain header
+ * @ev_id: Monitoring event ID (e.g. QOS_L3_OCCUP_EVENT_ID)
+ * @rmid: Resource Monitoring ID for which to read the data
+ * @val: Store the read data
+ *
+ * Looks up the domain by domid and dispatches the read request
+ * to the appropriate helper based on the event type.
+ * Currently supports only L3 occupancy monitoring.
+ *
+ * Return 0 on succeed, error code otherwise.
+ */
+int erdt_mon_read(struct rdt_domain_hdr *hdr, int ev_id, int rmid,
+ u64 *val)
+{
+ struct erdt_domain_info *d;
+
+ d = xa_load(&erdt_domain_xa, hdr->id);
+ if (!d)
+ return -EIO;
+
+ if (ev_id == QOS_L3_OCCUP_EVENT_ID)
+ return erdt_read_l3_occupancy(d, rmid, val);
+
+ return -EINVAL;
+}
+
/*
* get_l3_cache_id_from_cacd - Resolve L3 cache ID from CACD subtable
* @cacd: Pointer to the ACPI ERDT CACD structure
diff --git a/arch/x86/kernel/cpu/resctrl/monitor.c b/arch/x86/kernel/cpu/resctrl/monitor.c
index 1e81b3c33843..12b4014e47f3 100644
--- a/arch/x86/kernel/cpu/resctrl/monitor.c
+++ b/arch/x86/kernel/cpu/resctrl/monitor.c
@@ -278,6 +278,13 @@ int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_domain_hdr *hdr,

switch (r->rid) {
case RDT_RESOURCE_L3:
+ /*
+ * No SNC for mmio based L3 occupancy, so there is no need
+ * to convert logical RMID to a physical RMID via
+ * logical_rmid_to_physical_rmid().
+ */
+ if (erdt_enabled() && eventid == QOS_L3_OCCUP_EVENT_ID)
+ 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);
--
2.25.1