From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mgamail.intel.com (mgamail.intel.com [198.175.65.14]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 91F9E175A60 for ; Wed, 27 May 2026 09:36:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=198.175.65.14 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779874583; cv=none; b=Btwyg16YUZsJSZ60nRE4jDgamNZpsNBUVg+TgcZZ8JRAiBxUCbBeuV0AISTKwI79p8XhYB2kp+37ZY3MLy/TWYlGUoh9fL2N4blNznkWMOln7Xf5ziAvW3Eh2FsuFg/HwNCH42dWKYuOfecsvskQvmqrWqG1rQUeqLKaKPjRhds= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779874583; c=relaxed/simple; bh=pPiaKSUwSaSffRlP6UeDvcygcSTSfOt+tPjv8ZZMI78=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=IYVJzUWzuiucWNVMw4/omzDkv4WU1zQzFpVXYIuLDmMYwcI+in1/PTtXyPw4tfljC5QilQ3u1HWww3JPxKdLV0BUUlZQo6tlm4PE48rlZeDr36HskilEfTw/KuyA1nYZztpXNhy62noAGMPJRxfnMbjZDv/gA/NNLTbKGiMruLI= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com; spf=pass smtp.mailfrom=intel.com; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b=I5UKOMQX; arc=none smtp.client-ip=198.175.65.14 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=intel.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=intel.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=intel.com header.i=@intel.com header.b="I5UKOMQX" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1779874582; x=1811410582; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=pPiaKSUwSaSffRlP6UeDvcygcSTSfOt+tPjv8ZZMI78=; b=I5UKOMQXwo79l7TM36R68QWa0EzDKkMbuF+yYs7sXOCxe4H8+Yt/nLSf cwIwHbsN5Yg8m63UOvlJVi5w8uWHAcuUk7asHpRK91RkYU6SzIRUme8FL wb9dyDsCipLUbxchkkKcBIAJGQYdG469ovnnd+KZW+TkCs88NeyRB0xRl 7H2KcaArggmJdDNF2ptlstsfOTnWaTnsjOHrbeLC7hC/lj0eODfpY3CBj lYKChye0NUd1giLkBQiheCyUWXsqOciX/IDTObktZ2eKML2c0Rds8P24I WjF41jB2SQd9y6rTyJmvAlmLg0uoiROdUWlsX/EpX3ioQPVjhkDZykaGq Q==; X-CSE-ConnectionGUID: 25Im2jytR7+2oFrnfuYazg== X-CSE-MsgGUID: vlQkwGx4TLmR96C0/fSJHA== X-IronPort-AV: E=McAfee;i="6800,10657,11798"; a="84591758" X-IronPort-AV: E=Sophos;i="6.24,171,1774335600"; d="scan'208";a="84591758" Received: from orviesa002.jf.intel.com ([10.64.159.142]) by orvoesa106.jf.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 27 May 2026 02:36:21 -0700 X-CSE-ConnectionGUID: 4WLU2jTMTzOcCQSy3K2i/A== X-CSE-MsgGUID: hIkZd05QRJ2WwQUTLaX0gQ== X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="6.24,171,1774335600"; d="scan'208";a="272517079" Received: from chenyu-dev.sh.intel.com ([10.239.62.107]) by orviesa002.jf.intel.com with ESMTP; 27 May 2026 02:36:18 -0700 From: Chen Yu To: tony.luck@intel.com, reinette.chatre@intel.com Cc: x86@kernel.org, linux-kernel@vger.kernel.org, tglx@kernel.org, mingo@redhat.com, bp@alien8.de, dave.hansen@linux.intel.com, hpa@zytor.com, Dave.Martin@arm.com, james.morse@arm.com, babu.moger@amd.com, fenghuay@nvidia.com, Chen Yu Subject: [RFC PATCH 1/6] x86/resctrl: Parse ACPI ERDT table and map RMDD domains by L3 cache ID Date: Wed, 27 May 2026 17:26:54 +0800 Message-Id: <44d0db816f964ffb51367cbf0a4bb982786cac8c.1779872016.git.yu.c.chen@intel.com> X-Mailer: git-send-email 2.25.1 In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Anil S Keshavamurthy ERDT(Enhanced RDT) introduces a new top-level ACPI structure (the ERDT) that the kernel must parse before any enhanced RDT feature can be used. The ERDT improves the existing RDT by switching low-level register access from MSR-based to MMIO-based, which is more efficient. The ERDT structure may include several sub ACPI tables: - Resource Management Domain Description Structure (RMDD) - CPU Agent Collection Description Structure (CACD) - Cache Monitoring Registers for CPU Agents Description Structure (CMRC) There is one ERDT table per platform. Each RMDD substructure in ERDT represents one resource management domain (RMD), also known as an L3 domain. Thus, the total number of RMDDs equals the number of L3 domains on the platform. Each RMDD contains information such as MMIO addresses. This address is used to retrieve RDT metrics like L3 occupancy. Add basic ERDT ACPI table and sub-table parsing, and store the relevant tables for later processing. Among these sub-tables, RMDD requires special handling. There is one RMDD per domain, and the domain ID reuses the L3 cache ID. Many code paths need to retrieve an RMDD efficiently by domain ID (L3 cache ID). Because L3 cache IDs are derived from x2APIC IDs and are not contiguous, using a plain array indexed by domain ID would waste memory. As a trade-off, an xarray is used to store these tables, with the L3 cache ID as the key. Suggested-by: Tony Luck Co-developed-by: Chen Yu Signed-off-by: Chen Yu Signed-off-by: Anil S Keshavamurthy --- arch/x86/Kconfig | 4 +- arch/x86/include/asm/resctrl.h | 2 + arch/x86/kernel/cpu/resctrl/Makefile | 1 + arch/x86/kernel/cpu/resctrl/core.c | 3 + arch/x86/kernel/cpu/resctrl/erdt.c | 321 +++++++++++++++++++++++++ arch/x86/kernel/cpu/resctrl/internal.h | 3 + 6 files changed, 332 insertions(+), 2 deletions(-) create mode 100644 arch/x86/kernel/cpu/resctrl/erdt.c diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index f3f7cb01d69d..97d210bd9bb5 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -515,7 +515,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 @@ -538,7 +538,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. diff --git a/arch/x86/include/asm/resctrl.h b/arch/x86/include/asm/resctrl.h index 575f8408a9e7..97c2f6bc7a5f 100644 --- a/arch/x86/include/asm/resctrl.h +++ b/arch/x86/include/asm/resctrl.h @@ -40,6 +40,8 @@ struct resctrl_pqr_state { u32 default_closid; }; +bool erdt_enabled(void); + DECLARE_PER_CPU(struct resctrl_pqr_state, pqr_state); extern bool rdt_alloc_capable; 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 7667cf7c4e94..893d2efe76f0 100644 --- a/arch/x86/kernel/cpu/resctrl/core.c +++ b/arch/x86/kernel/cpu/resctrl/core.c @@ -1012,6 +1012,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(); @@ -1165,6 +1166,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..2813f48f1411 --- /dev/null +++ b/arch/x86/kernel/cpu/resctrl/erdt.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Enhanced Resource Director Technology(ERDT) + * + * Copyright (C) 2026 Intel Corporation + * + */ + +#define pr_fmt(fmt) "resctrl: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +enum erdt_mmio_type { + ERDT_MMIO_RMDD_CREG, + ERDT_MMIO_CMRC_BASE, + ERDT_MMIO_MAX +}; + +struct erdt_domain_info { + struct acpi_erdt_cacd *cacd; + struct acpi_erdt_cmrc *cmrc; + /* MMIO address */ + void __iomem *base[ERDT_MMIO_MAX]; +}; + +/* true if ERDT table is present and valid */ +static bool erdt_available; + +/* Global variable to hold ERDT ACPI table information for later processing */ +static DEFINE_XARRAY(erdt_domain_xa); /* Indexed by L3 cache ID */ + +#define ERDT_VALID_VERSION 1 + +static u32 valid_subtbl_mask; + +/* + * erdt_enabled - Check if the ERDT table is present and enabled + */ +bool erdt_enabled(void) +{ + return erdt_available; +} + +/* + * lookup_logical_cpu_by_x2apicid - Map x2APIC ID to logical CPU number + */ +static __init int lookup_logical_cpu_by_x2apicid(u32 x2apicid) +{ + int cpu; + + for_each_possible_cpu(cpu) { + if (cpu_physical_id(cpu) == x2apicid) + return cpu; + } + + return -1; +} + +/* + * get_l3_cache_id_from_cacd - Resolve L3 cache ID from CACD subtable + * @cacd: Pointer to the ACPI ERDT CACD structure + * + * Parses the X2APIC ID list in the given CACD subtable to + * identify an online logical CPU and uses it to query the associated + * L3 cache ID. The first valid CPU found is used for this lookup. + * + * The L3 cache ID is used as a unique domain key for ERDT domain + * registration and lookup. + * + * Return: + * L3 cache ID for the first matching CPU, or + * -1 if no valid CPU or L3 cache ID could be determined. + */ +static __init int get_l3_cache_id_from_cacd(struct acpi_erdt_cacd *cacd) +{ + int num_ids, cpu, online_cpu = -1, cache_id = -1, tmp; + struct cacheinfo *ci; + + if (cacd->header.length < sizeof(*cacd) + sizeof(cacd->X2APICIDS[0])) { + pr_warn(FW_BUG "Invalid x2apicid CACD table\n"); + return -1; + } + + num_ids = (cacd->header.length - sizeof(*cacd)) / sizeof(cacd->X2APICIDS[0]); + + guard(cpus_read_lock)(); + + for (int i = 0; i < num_ids; i++) { + cpu = lookup_logical_cpu_by_x2apicid(cacd->X2APICIDS[i]); + if (cpu == -1) { + pr_warn(FW_BUG "Unknown x2apicid 0x%x\n", cacd->X2APICIDS[i]); + + return -1; + } + + if (!cpu_online(cpu)) + continue; + + tmp = get_cpu_cacheinfo_id(cpu, RESCTRL_L3_CACHE); + if (tmp == -1) { + pr_warn(FW_BUG "Can not find L3 cache id for CPU%d\n", cpu); + return -1; + } + + if (cache_id == -1) + cache_id = tmp; + + if (tmp != cache_id) { + pr_warn(FW_BUG "CACD references multiple L3 cache instances\n"); + return -1; + } + online_cpu = cpu; + } + + if (online_cpu == -1) + return -1; + + /* + * Check if CACD lists all CPUs in the LLC domain. + */ + ci = get_cpu_cacheinfo_level(online_cpu, RESCTRL_L3_CACHE); + if (!ci || num_ids != cpumask_weight(&ci->shared_cpu_map)) { + pr_warn(FW_BUG "CACD does not list all the CPUs in L3 domain\n"); + return -1; + } + + return cache_id; +} + +static void __iomem *erdt_ioremap_checked(phys_addr_t base, u32 size, + const char *desc) +{ + void __iomem *addr = ioremap(base, size << 12); + + if (!addr) + pr_err("ERDT: Failed to map %s at phys addr %#llx (size: %u pages)\n", + desc, (unsigned long long)base, size); + return addr; +} + +static void erdt_iounmap_domain(struct erdt_domain_info *domain) +{ + for (int i = 0; i < ERDT_MMIO_MAX; 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); +} + +static __init bool cacd_init(struct erdt_domain_info *d, + struct acpi_subtbl_hdr_16 *subtbl, + int *l3_cache_id) +{ + *l3_cache_id = get_l3_cache_id_from_cacd((struct acpi_erdt_cacd *)subtbl); + + return *l3_cache_id != -1; +} + +static __init bool parse_rmdd_entry(struct acpi_subtbl_hdr_16 *rmdd_hdr) +{ + struct acpi_erdt_rmdd *rmdd; + struct erdt_domain_info *domain_info; + struct acpi_subtbl_hdr_16 *subtbl; + int l3_cache_id = -1; + u32 subtbl_mask = 0; + void *rmdd_end; + + if (rmdd_hdr->length < sizeof(*rmdd)) { + pr_info(FW_BUG "Invalid RMDD length %u\n", rmdd_hdr->length); + return false; + } + + rmdd = (struct acpi_erdt_rmdd *)rmdd_hdr; + + /* Quietly ignore non-CPU-based L3 domains (bit 0 set) */ + if (!(rmdd->flags & 0x1)) + return true; + + domain_info = kzalloc(sizeof(*domain_info), GFP_KERNEL); + if (!domain_info) + return false; + + domain_info->base[ERDT_MMIO_RMDD_CREG] = erdt_ioremap_checked(rmdd->creg_base, rmdd->creg_size, + "RMDD ctrl base"); + if (!domain_info->base[ERDT_MMIO_RMDD_CREG]) + goto cleanup; + + rmdd_end = (void *)rmdd + rmdd->header.length; + + /* Iterate through all sub-structures inside this RMDD block */ + for (subtbl = (void *)rmdd + sizeof(*rmdd); + (void *)subtbl + sizeof(*subtbl) <= rmdd_end; + subtbl = (void *)subtbl + subtbl->length) { + if (subtbl->length < sizeof(*subtbl) || + (void *)subtbl + subtbl->length > rmdd_end) { + pr_info("ERDT: Invalid subtable length in RMDD domain %d\n", + rmdd->domain_id); + + goto cleanup; + } + + switch (subtbl->type) { + case ACPI_ERDT_TYPE_CACD: + if (cacd_init(domain_info, subtbl, &l3_cache_id)) + subtbl_mask |= BIT(ACPI_ERDT_TYPE_CACD); + break; + default: + break; + } + } + + if (l3_cache_id == -1) { + pr_info("ERDT: Failed to resolve L3 cache ID for RMDD domain %d\n", + rmdd->domain_id); + + goto cleanup; + } + + /* Require all RMDDs to support same set of sub-tables */ + if (!valid_subtbl_mask) { + valid_subtbl_mask = subtbl_mask; + } else if (subtbl_mask != valid_subtbl_mask) { + pr_info(FW_BUG "Mismatch domain\n"); + goto cleanup; + } + + if (xa_insert(&erdt_domain_xa, l3_cache_id, domain_info, GFP_KERNEL)) { + pr_info("ERDT: Failed to store domain info for RMDD domain %d\n", + rmdd->domain_id); + goto cleanup; + } + + return true; + +cleanup: + cleanup_one_domain(domain_info); + return false; +} + +static void erdt_cleanup(void) +{ + struct erdt_domain_info *d; + unsigned long index; + + xa_for_each(&erdt_domain_xa, index, d) + cleanup_one_domain(d); + xa_destroy(&erdt_domain_xa); +} + +/* + * enumerate_erdt_table - Store pointer to ERDT and begin domain parsing + */ +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("Unknown ERDT table revision %d\n", erdt->header.revision); + return -EINVAL; + } + + if (erdt->header.length < sizeof(*erdt)) { + pr_info(FW_BUG "ERDT: Invalid table length %u\n", erdt->header.length); + return -EINVAL; + } + + subtbl = (void *)erdt + sizeof(struct acpi_table_erdt); + table_end = (void *)erdt + erdt->header.length; + + while ((void *)subtbl + sizeof(*subtbl) <= table_end) { + if (subtbl->length < sizeof(*subtbl) || + (void *)subtbl + subtbl->length > table_end) { + pr_info("ERDT: Invalid subtable length\n"); + goto cleanup; + } + + if (subtbl->type == ACPI_ERDT_TYPE_RMDD) + if (!parse_rmdd_entry(subtbl)) + goto cleanup; + + subtbl = (void *)subtbl + subtbl->length; + } + + erdt_available = true; + + return 0; + +cleanup: + erdt_cleanup(); + return -EINVAL; +} + +/* + * erdt_init - ACPI ERDT table initialization entry point + */ +int __init erdt_init(void) +{ + return acpi_table_parse(ACPI_SIG_ERDT, enumerate_erdt_table); +} + +void __exit erdt_exit(void) +{ + erdt_cleanup(); +} diff --git a/arch/x86/kernel/cpu/resctrl/internal.h b/arch/x86/kernel/cpu/resctrl/internal.h index e3cfa0c10e92..9c59bd5e028e 100644 --- a/arch/x86/kernel/cpu/resctrl/internal.h +++ b/arch/x86/kernel/cpu/resctrl/internal.h @@ -253,4 +253,7 @@ 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_init(void); +void erdt_exit(void); + #endif /* _ASM_X86_RESCTRL_INTERNAL_H */ -- 2.25.1