Skip to content

Commit 3f31749

Browse files
yghannambp3tk0v
authored andcommitted
RAS: Introduce AMD Address Translation Library
AMD Zen-based systems report memory errors through Machine Check banks representing Unified Memory Controllers (UMCs). The address value reported for DRAM ECC errors is a "normalized address" that is relative to the UMC. This normalized address must be converted to a system physical address to be usable by the OS. Support for this address translation was introduced to the MCA subsystem with Zen1 systems. The code was later moved to the AMD64 EDAC module, since this was the only user of the code at the time. However, there are uses for this translation outside of EDAC. The system physical address can be used in MCA for preemptive page offlining as done in some MCA notifier functions. Also, this translation is needed as the basis of similar functionality needed for some CXL configurations on AMD systems. Introduce a common address translation library that can be used for multiple subsystems including MCA, EDAC, and CXL. Include support for UMC normalized to system physical address translation for current CPU systems. The Data Fabric Indirect register access offsets and one of the register fields were changed. Default to the current offsets and register field definition. And fallback to the older values if running on a "legacy" system. Provide built-in code to facilitate the loading and unloading of the library module without affecting other modules or built-in code. Signed-off-by: Yazen Ghannam <[email protected]> Signed-off-by: Borislav Petkov (AMD) <[email protected]> Link: https://lore.kernel.org/r/[email protected]
1 parent 6613476 commit 3f31749

File tree

16 files changed

+3336
-0
lines changed

16 files changed

+3336
-0
lines changed

MAINTAINERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,12 @@ Q: https://patchwork.kernel.org/project/linux-rdma/list/
897897
F: drivers/infiniband/hw/efa/
898898
F: include/uapi/rdma/efa-abi.h
899899

900+
AMD ADDRESS TRANSLATION LIBRARY (ATL)
901+
M: Yazen Ghannam <[email protected]>
902+
903+
S: Supported
904+
F: drivers/ras/amd/atl/*
905+
900906
AMD AXI W1 DRIVER
901907
M: Kris Chaplin <[email protected]>
902908
R: Thomas Delev <[email protected]>

drivers/ras/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ menuconfig RAS
3232
if RAS
3333

3434
source "arch/x86/ras/Kconfig"
35+
source "drivers/ras/amd/atl/Kconfig"
3536

3637
endif

drivers/ras/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
obj-$(CONFIG_RAS) += ras.o
33
obj-$(CONFIG_DEBUG_FS) += debugfs.o
44
obj-$(CONFIG_RAS_CEC) += cec.o
5+
6+
obj-y += amd/atl/

drivers/ras/amd/atl/Kconfig

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# SPDX-License-Identifier: GPL-2.0-or-later
2+
#
3+
# AMD Address Translation Library Kconfig
4+
#
5+
# Copyright (c) 2023, Advanced Micro Devices, Inc.
6+
# All Rights Reserved.
7+
#
8+
# Author: Yazen Ghannam <[email protected]>
9+
10+
config AMD_ATL
11+
tristate "AMD Address Translation Library"
12+
depends on AMD_NB && X86_64 && RAS
13+
default N
14+
help
15+
This library includes support for implementation-specific
16+
address translation procedures needed for various error
17+
handling cases.
18+
19+
Enable this option if using DRAM ECC on Zen-based systems
20+
and OS-based error handling.

drivers/ras/amd/atl/Makefile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# SPDX-License-Identifier: GPL-2.0-or-later
2+
#
3+
# AMD Address Translation Library Makefile
4+
#
5+
# Copyright (c) 2023, Advanced Micro Devices, Inc.
6+
# All Rights Reserved.
7+
#
8+
# Author: Yazen Ghannam <[email protected]>
9+
10+
amd_atl-y := access.o
11+
amd_atl-y += core.o
12+
amd_atl-y += dehash.o
13+
amd_atl-y += denormalize.o
14+
amd_atl-y += map.o
15+
amd_atl-y += system.o
16+
amd_atl-y += umc.o
17+
18+
obj-$(CONFIG_AMD_ATL) += amd_atl.o

drivers/ras/amd/atl/access.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* AMD Address Translation Library
4+
*
5+
* access.c : DF Indirect Access functions
6+
*
7+
* Copyright (c) 2023, Advanced Micro Devices, Inc.
8+
* All Rights Reserved.
9+
*
10+
* Author: Yazen Ghannam <[email protected]>
11+
*/
12+
13+
#include "internal.h"
14+
15+
/* Protect the PCI config register pairs used for DF indirect access. */
16+
static DEFINE_MUTEX(df_indirect_mutex);
17+
18+
/*
19+
* Data Fabric Indirect Access uses FICAA/FICAD.
20+
*
21+
* Fabric Indirect Configuration Access Address (FICAA): constructed based
22+
* on the device's Instance Id and the PCI function and register offset of
23+
* the desired register.
24+
*
25+
* Fabric Indirect Configuration Access Data (FICAD): there are FICAD
26+
* low and high registers but so far only the low register is needed.
27+
*
28+
* Use Instance Id 0xFF to indicate a broadcast read.
29+
*/
30+
#define DF_BROADCAST 0xFF
31+
32+
#define DF_FICAA_INST_EN BIT(0)
33+
#define DF_FICAA_REG_NUM GENMASK(10, 1)
34+
#define DF_FICAA_FUNC_NUM GENMASK(13, 11)
35+
#define DF_FICAA_INST_ID GENMASK(23, 16)
36+
37+
#define DF_FICAA_REG_NUM_LEGACY GENMASK(10, 2)
38+
39+
static int __df_indirect_read(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
40+
{
41+
u32 ficaa_addr = 0x8C, ficad_addr = 0xB8;
42+
struct pci_dev *F4;
43+
int err = -ENODEV;
44+
u32 ficaa = 0;
45+
46+
if (node >= amd_nb_num())
47+
goto out;
48+
49+
F4 = node_to_amd_nb(node)->link;
50+
if (!F4)
51+
goto out;
52+
53+
/* Enable instance-specific access. */
54+
if (instance_id != DF_BROADCAST) {
55+
ficaa |= FIELD_PREP(DF_FICAA_INST_EN, 1);
56+
ficaa |= FIELD_PREP(DF_FICAA_INST_ID, instance_id);
57+
}
58+
59+
/*
60+
* The two least-significant bits are masked when inputing the
61+
* register offset to FICAA.
62+
*/
63+
reg >>= 2;
64+
65+
if (df_cfg.flags.legacy_ficaa) {
66+
ficaa_addr = 0x5C;
67+
ficad_addr = 0x98;
68+
69+
ficaa |= FIELD_PREP(DF_FICAA_REG_NUM_LEGACY, reg);
70+
} else {
71+
ficaa |= FIELD_PREP(DF_FICAA_REG_NUM, reg);
72+
}
73+
74+
ficaa |= FIELD_PREP(DF_FICAA_FUNC_NUM, func);
75+
76+
mutex_lock(&df_indirect_mutex);
77+
78+
err = pci_write_config_dword(F4, ficaa_addr, ficaa);
79+
if (err) {
80+
pr_warn("Error writing DF Indirect FICAA, FICAA=0x%x\n", ficaa);
81+
goto out_unlock;
82+
}
83+
84+
err = pci_read_config_dword(F4, ficad_addr, lo);
85+
if (err)
86+
pr_warn("Error reading DF Indirect FICAD LO, FICAA=0x%x.\n", ficaa);
87+
88+
pr_debug("node=%u inst=0x%x func=0x%x reg=0x%x val=0x%x",
89+
node, instance_id, func, reg << 2, *lo);
90+
91+
out_unlock:
92+
mutex_unlock(&df_indirect_mutex);
93+
94+
out:
95+
return err;
96+
}
97+
98+
int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
99+
{
100+
return __df_indirect_read(node, func, reg, instance_id, lo);
101+
}
102+
103+
int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo)
104+
{
105+
return __df_indirect_read(node, func, reg, DF_BROADCAST, lo);
106+
}

drivers/ras/amd/atl/core.c

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
/*
3+
* AMD Address Translation Library
4+
*
5+
* core.c : Module init and base translation functions
6+
*
7+
* Copyright (c) 2023, Advanced Micro Devices, Inc.
8+
* All Rights Reserved.
9+
*
10+
* Author: Yazen Ghannam <[email protected]>
11+
*/
12+
13+
#include <linux/module.h>
14+
#include <asm/cpu_device_id.h>
15+
16+
#include "internal.h"
17+
18+
struct df_config df_cfg __read_mostly;
19+
20+
static int addr_over_limit(struct addr_ctx *ctx)
21+
{
22+
u64 dram_limit_addr;
23+
24+
if (df_cfg.rev >= DF4)
25+
dram_limit_addr = FIELD_GET(DF4_DRAM_LIMIT_ADDR, ctx->map.limit);
26+
else
27+
dram_limit_addr = FIELD_GET(DF2_DRAM_LIMIT_ADDR, ctx->map.limit);
28+
29+
dram_limit_addr <<= DF_DRAM_BASE_LIMIT_LSB;
30+
dram_limit_addr |= GENMASK(DF_DRAM_BASE_LIMIT_LSB - 1, 0);
31+
32+
/* Is calculated system address above DRAM limit address? */
33+
if (ctx->ret_addr > dram_limit_addr) {
34+
atl_debug(ctx, "Calculated address (0x%016llx) > DRAM limit (0x%016llx)",
35+
ctx->ret_addr, dram_limit_addr);
36+
return -EINVAL;
37+
}
38+
39+
return 0;
40+
}
41+
42+
static bool legacy_hole_en(struct addr_ctx *ctx)
43+
{
44+
u32 reg = ctx->map.base;
45+
46+
if (df_cfg.rev >= DF4)
47+
reg = ctx->map.ctl;
48+
49+
return FIELD_GET(DF_LEGACY_MMIO_HOLE_EN, reg);
50+
}
51+
52+
static int add_legacy_hole(struct addr_ctx *ctx)
53+
{
54+
u32 dram_hole_base;
55+
u8 func = 0;
56+
57+
if (!legacy_hole_en(ctx))
58+
return 0;
59+
60+
if (df_cfg.rev >= DF4)
61+
func = 7;
62+
63+
if (df_indirect_read_broadcast(ctx->node_id, func, 0x104, &dram_hole_base))
64+
return -EINVAL;
65+
66+
dram_hole_base &= DF_DRAM_HOLE_BASE_MASK;
67+
68+
if (ctx->ret_addr >= dram_hole_base)
69+
ctx->ret_addr += (BIT_ULL(32) - dram_hole_base);
70+
71+
return 0;
72+
}
73+
74+
static u64 get_base_addr(struct addr_ctx *ctx)
75+
{
76+
u64 base_addr;
77+
78+
if (df_cfg.rev >= DF4)
79+
base_addr = FIELD_GET(DF4_BASE_ADDR, ctx->map.base);
80+
else
81+
base_addr = FIELD_GET(DF2_BASE_ADDR, ctx->map.base);
82+
83+
return base_addr << DF_DRAM_BASE_LIMIT_LSB;
84+
}
85+
86+
static int add_base_and_hole(struct addr_ctx *ctx)
87+
{
88+
ctx->ret_addr += get_base_addr(ctx);
89+
90+
if (add_legacy_hole(ctx))
91+
return -EINVAL;
92+
93+
return 0;
94+
}
95+
96+
static bool late_hole_remove(struct addr_ctx *ctx)
97+
{
98+
if (df_cfg.rev == DF3p5)
99+
return true;
100+
101+
if (df_cfg.rev == DF4)
102+
return true;
103+
104+
if (ctx->map.intlv_mode == DF3_6CHAN)
105+
return true;
106+
107+
return false;
108+
}
109+
110+
unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr)
111+
{
112+
struct addr_ctx ctx;
113+
114+
if (df_cfg.rev == UNKNOWN)
115+
return -EINVAL;
116+
117+
memset(&ctx, 0, sizeof(ctx));
118+
119+
/* Start from the normalized address */
120+
ctx.ret_addr = addr;
121+
ctx.inst_id = coh_st_inst_id;
122+
123+
ctx.inputs.norm_addr = addr;
124+
ctx.inputs.socket_id = socket_id;
125+
ctx.inputs.die_id = die_id;
126+
ctx.inputs.coh_st_inst_id = coh_st_inst_id;
127+
128+
if (determine_node_id(&ctx, socket_id, die_id))
129+
return -EINVAL;
130+
131+
if (get_address_map(&ctx))
132+
return -EINVAL;
133+
134+
if (denormalize_address(&ctx))
135+
return -EINVAL;
136+
137+
if (!late_hole_remove(&ctx) && add_base_and_hole(&ctx))
138+
return -EINVAL;
139+
140+
if (dehash_address(&ctx))
141+
return -EINVAL;
142+
143+
if (late_hole_remove(&ctx) && add_base_and_hole(&ctx))
144+
return -EINVAL;
145+
146+
if (addr_over_limit(&ctx))
147+
return -EINVAL;
148+
149+
return ctx.ret_addr;
150+
}
151+
152+
static void check_for_legacy_df_access(void)
153+
{
154+
/*
155+
* All Zen-based systems before Family 19h use the legacy
156+
* DF Indirect Access (FICAA/FICAD) offsets.
157+
*/
158+
if (boot_cpu_data.x86 < 0x19) {
159+
df_cfg.flags.legacy_ficaa = true;
160+
return;
161+
}
162+
163+
/* All systems after Family 19h use the current offsets. */
164+
if (boot_cpu_data.x86 > 0x19)
165+
return;
166+
167+
/* Some Family 19h systems use the legacy offsets. */
168+
switch (boot_cpu_data.x86_model) {
169+
case 0x00 ... 0x0f:
170+
case 0x20 ... 0x5f:
171+
df_cfg.flags.legacy_ficaa = true;
172+
}
173+
}
174+
175+
/*
176+
* This library provides functionality for AMD-based systems with a Data Fabric.
177+
* The set of systems with a Data Fabric is equivalent to the set of Zen-based systems
178+
* and the set of systems with the Scalable MCA feature at this time. However, these
179+
* are technically independent things.
180+
*
181+
* It's possible to match on the PCI IDs of the Data Fabric devices, but this will be
182+
* an ever expanding list. Instead, match on the SMCA and Zen features to cover all
183+
* relevant systems.
184+
*/
185+
static const struct x86_cpu_id amd_atl_cpuids[] = {
186+
X86_MATCH_FEATURE(X86_FEATURE_SMCA, NULL),
187+
X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL),
188+
{ }
189+
};
190+
MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids);
191+
192+
static int __init amd_atl_init(void)
193+
{
194+
if (!x86_match_cpu(amd_atl_cpuids))
195+
return -ENODEV;
196+
197+
if (!amd_nb_num())
198+
return -ENODEV;
199+
200+
check_for_legacy_df_access();
201+
202+
if (get_df_system_info())
203+
return -ENODEV;
204+
205+
/* Increment this module's recount so that it can't be easily unloaded. */
206+
__module_get(THIS_MODULE);
207+
amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr);
208+
209+
pr_info("AMD Address Translation Library initialized");
210+
return 0;
211+
}
212+
213+
/*
214+
* Exit function is only needed for testing and debug. Module unload must be
215+
* forced to override refcount check.
216+
*/
217+
static void __exit amd_atl_exit(void)
218+
{
219+
amd_atl_unregister_decoder();
220+
}
221+
222+
module_init(amd_atl_init);
223+
module_exit(amd_atl_exit);
224+
225+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)