Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# AMD Address Translation Library Kconfig
#
# Copyright (c) 2023, Advanced Micro Devices, Inc.
# All Rights Reserved.
#
# Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
config AMD_ATL
tristate "AMD Address Translation Library"
depends on AMD_NB && X86_64 && RAS
depends on AMD_NODE
depends on MEMORY_FAILURE
help
This library includes support for implementation-specific
address translation procedures needed for various error
handling cases.
Enable this option if using DRAM ECC on Zen-based systems
and OS-based error handling.
config AMD_ATL_PRM
depends on AMD_ATL && ACPI_PRMT
def_bool y
+20
View File
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-or-later
#
# AMD Address Translation Library Makefile
#
# Copyright (c) 2023, Advanced Micro Devices, Inc.
# All Rights Reserved.
#
# Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
amd_atl-y := access.o
amd_atl-y += core.o
amd_atl-y += dehash.o
amd_atl-y += denormalize.o
amd_atl-y += map.o
amd_atl-y += system.o
amd_atl-y += umc.o
amd_atl-$(CONFIG_AMD_ATL_PRM) += prm.o
obj-$(CONFIG_AMD_ATL) += amd_atl.o
+137
View File
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* access.c : DF Indirect Access functions
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include "internal.h"
/* Protect the PCI config register pairs used for DF indirect access. */
static DEFINE_MUTEX(df_indirect_mutex);
/*
* Data Fabric Indirect Access uses FICAA/FICAD.
*
* Fabric Indirect Configuration Access Address (FICAA): constructed based
* on the device's Instance Id and the PCI function and register offset of
* the desired register.
*
* Fabric Indirect Configuration Access Data (FICAD): there are FICAD
* low and high registers but so far only the low register is needed.
*
* Use Instance Id 0xFF to indicate a broadcast read.
*/
#define DF_BROADCAST 0xFF
#define DF_FICAA_INST_EN BIT(0)
#define DF_FICAA_REG_NUM GENMASK(10, 1)
#define DF_FICAA_FUNC_NUM GENMASK(13, 11)
#define DF_FICAA_INST_ID GENMASK(23, 16)
#define DF_FICAA_REG_NUM_LEGACY GENMASK(10, 2)
static u16 get_accessible_node(u16 node)
{
/*
* On heterogeneous systems, not all AMD Nodes are accessible
* through software-visible registers. The Node ID needs to be
* adjusted for register accesses. But its value should not be
* changed for the translation methods.
*/
if (df_cfg.flags.heterogeneous) {
/* Only Node 0 is accessible on DF3.5 systems. */
if (df_cfg.rev == DF3p5)
node = 0;
/*
* Only the first Node in each Socket is accessible on
* DF4.5 systems, and this is visible to software as one
* Fabric per Socket. The Socket ID can be derived from
* the Node ID and global shift values.
*/
if (df_cfg.rev == DF4p5)
node >>= df_cfg.socket_id_shift - df_cfg.node_id_shift;
}
return node;
}
static int __df_indirect_read(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
{
u32 ficaa_addr = 0x8C, ficad_addr = 0xB8;
struct pci_dev *F4;
int err = -ENODEV;
u32 ficaa = 0;
node = get_accessible_node(node);
if (node >= amd_nb_num()) {
pr_debug("Node %u is out of bounds\n", node);
goto out;
}
F4 = node_to_amd_nb(node)->link;
if (!F4) {
pr_debug("DF function 4 not found\n");
goto out;
}
/* Enable instance-specific access. */
if (instance_id != DF_BROADCAST) {
ficaa |= FIELD_PREP(DF_FICAA_INST_EN, 1);
ficaa |= FIELD_PREP(DF_FICAA_INST_ID, instance_id);
}
/*
* The two least-significant bits are masked when inputing the
* register offset to FICAA.
*/
reg >>= 2;
if (df_cfg.flags.legacy_ficaa) {
ficaa_addr = 0x5C;
ficad_addr = 0x98;
ficaa |= FIELD_PREP(DF_FICAA_REG_NUM_LEGACY, reg);
} else {
ficaa |= FIELD_PREP(DF_FICAA_REG_NUM, reg);
}
ficaa |= FIELD_PREP(DF_FICAA_FUNC_NUM, func);
mutex_lock(&df_indirect_mutex);
err = pci_write_config_dword(F4, ficaa_addr, ficaa);
if (err) {
pr_warn("Error writing DF Indirect FICAA, FICAA=0x%x\n", ficaa);
goto out_unlock;
}
err = pci_read_config_dword(F4, ficad_addr, lo);
if (err)
pr_warn("Error reading DF Indirect FICAD LO, FICAA=0x%x.\n", ficaa);
pr_debug("node=%u inst=0x%x func=0x%x reg=0x%x val=0x%x",
node, instance_id, func, reg << 2, *lo);
out_unlock:
mutex_unlock(&df_indirect_mutex);
out:
return err;
}
int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
{
return __df_indirect_read(node, func, reg, instance_id, lo);
}
int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo)
{
return __df_indirect_read(node, func, reg, DF_BROADCAST, lo);
}
+232
View File
@@ -0,0 +1,232 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* core.c : Module init and base translation functions
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include <linux/module.h>
#include <asm/cpu_device_id.h>
#include "internal.h"
struct df_config df_cfg __read_mostly;
static int addr_over_limit(struct addr_ctx *ctx)
{
u64 dram_limit_addr;
if (df_cfg.rev >= DF4)
dram_limit_addr = FIELD_GET(DF4_DRAM_LIMIT_ADDR, ctx->map.limit);
else
dram_limit_addr = FIELD_GET(DF2_DRAM_LIMIT_ADDR, ctx->map.limit);
dram_limit_addr <<= DF_DRAM_BASE_LIMIT_LSB;
dram_limit_addr |= GENMASK(DF_DRAM_BASE_LIMIT_LSB - 1, 0);
/* Is calculated system address above DRAM limit address? */
if (ctx->ret_addr > dram_limit_addr) {
atl_debug(ctx, "Calculated address (0x%016llx) > DRAM limit (0x%016llx)",
ctx->ret_addr, dram_limit_addr);
return -EINVAL;
}
return 0;
}
static bool legacy_hole_en(struct addr_ctx *ctx)
{
u32 reg = ctx->map.base;
if (df_cfg.rev >= DF4)
reg = ctx->map.ctl;
return FIELD_GET(DF_LEGACY_MMIO_HOLE_EN, reg);
}
static u64 add_legacy_hole(struct addr_ctx *ctx, u64 addr)
{
if (!legacy_hole_en(ctx))
return addr;
if (addr >= df_cfg.dram_hole_base)
addr += (BIT_ULL(32) - df_cfg.dram_hole_base);
return addr;
}
static u64 remove_legacy_hole(struct addr_ctx *ctx, u64 addr)
{
if (!legacy_hole_en(ctx))
return addr;
if (addr >= df_cfg.dram_hole_base)
addr -= (BIT_ULL(32) - df_cfg.dram_hole_base);
return addr;
}
static u64 get_base_addr(struct addr_ctx *ctx)
{
u64 base_addr;
if (df_cfg.rev >= DF4)
base_addr = FIELD_GET(DF4_BASE_ADDR, ctx->map.base);
else
base_addr = FIELD_GET(DF2_BASE_ADDR, ctx->map.base);
return base_addr << DF_DRAM_BASE_LIMIT_LSB;
}
u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr)
{
return add_legacy_hole(ctx, addr + get_base_addr(ctx));
}
u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr)
{
return remove_legacy_hole(ctx, addr) - get_base_addr(ctx);
}
static bool late_hole_remove(struct addr_ctx *ctx)
{
if (df_cfg.rev == DF3p5)
return true;
if (df_cfg.rev == DF4)
return true;
if (ctx->map.intlv_mode == DF3_6CHAN)
return true;
return false;
}
unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr)
{
struct addr_ctx ctx;
if (df_cfg.rev == UNKNOWN)
return -EINVAL;
memset(&ctx, 0, sizeof(ctx));
/* Start from the normalized address */
ctx.ret_addr = addr;
ctx.inst_id = coh_st_inst_id;
ctx.inputs.norm_addr = addr;
ctx.inputs.socket_id = socket_id;
ctx.inputs.die_id = die_id;
ctx.inputs.coh_st_inst_id = coh_st_inst_id;
if (legacy_hole_en(&ctx) && !df_cfg.dram_hole_base)
return -EINVAL;
if (determine_node_id(&ctx, socket_id, die_id))
return -EINVAL;
if (get_address_map(&ctx))
return -EINVAL;
if (denormalize_address(&ctx))
return -EINVAL;
if (!late_hole_remove(&ctx))
ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);
if (dehash_address(&ctx))
return -EINVAL;
if (late_hole_remove(&ctx))
ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr);
if (addr_over_limit(&ctx))
return -EINVAL;
return ctx.ret_addr;
}
static void check_for_legacy_df_access(void)
{
/*
* All Zen-based systems before Family 19h use the legacy
* DF Indirect Access (FICAA/FICAD) offsets.
*/
if (boot_cpu_data.x86 < 0x19) {
df_cfg.flags.legacy_ficaa = true;
return;
}
/* All systems after Family 19h use the current offsets. */
if (boot_cpu_data.x86 > 0x19)
return;
/* Some Family 19h systems use the legacy offsets. */
switch (boot_cpu_data.x86_model) {
case 0x00 ... 0x0f:
case 0x20 ... 0x5f:
df_cfg.flags.legacy_ficaa = true;
}
}
/*
* This library provides functionality for AMD-based systems with a Data Fabric.
* The set of systems with a Data Fabric is equivalent to the set of Zen-based systems
* and the set of systems with the Scalable MCA feature at this time. However, these
* are technically independent things.
*
* It's possible to match on the PCI IDs of the Data Fabric devices, but this will be
* an ever expanding list. Instead, match on the SMCA and Zen features to cover all
* relevant systems.
*/
static const struct x86_cpu_id amd_atl_cpuids[] = {
X86_MATCH_FEATURE(X86_FEATURE_SMCA, NULL),
X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL),
{ }
};
MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids);
static int __init amd_atl_init(void)
{
int ret;
if (!x86_match_cpu(amd_atl_cpuids))
return -ENODEV;
if (!amd_nb_num())
return -ENODEV;
check_for_legacy_df_access();
ret = get_df_system_info();
if (ret)
return ret;
/* Increment this module's recount so that it can't be easily unloaded. */
__module_get(THIS_MODULE);
amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr);
pr_info("AMD Address Translation Library initialized\n");
return 0;
}
/*
* Exit function is only needed for testing and debug. Module unload must be
* forced to override refcount check.
*/
static void __exit amd_atl_exit(void)
{
amd_atl_unregister_decoder();
}
module_init(amd_atl_init);
module_exit(amd_atl_exit);
MODULE_DESCRIPTION("AMD Address Translation Library");
MODULE_LICENSE("GPL");
+457
View File
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* dehash.c : Functions to account for hashing bits
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include "internal.h"
static int df2_dehash_addr(struct addr_ctx *ctx)
{
u8 hashed_bit, intlv_bit, intlv_bit_pos;
intlv_bit_pos = ctx->map.intlv_bit_pos;
intlv_bit = !!(BIT_ULL(intlv_bit_pos) & ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(12), ctx->ret_addr);
hashed_bit ^= FIELD_GET(BIT_ULL(18), ctx->ret_addr);
hashed_bit ^= FIELD_GET(BIT_ULL(21), ctx->ret_addr);
hashed_bit ^= FIELD_GET(BIT_ULL(30), ctx->ret_addr);
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(intlv_bit_pos);
return 0;
}
static int df3_dehash_addr(struct addr_ctx *ctx)
{
bool hash_ctl_64k, hash_ctl_2M, hash_ctl_1G;
u8 hashed_bit, intlv_bit, intlv_bit_pos;
hash_ctl_64k = FIELD_GET(DF3_HASH_CTL_64K, ctx->map.ctl);
hash_ctl_2M = FIELD_GET(DF3_HASH_CTL_2M, ctx->map.ctl);
hash_ctl_1G = FIELD_GET(DF3_HASH_CTL_1G, ctx->map.ctl);
intlv_bit_pos = ctx->map.intlv_bit_pos;
intlv_bit = !!(BIT_ULL(intlv_bit_pos) & ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(14), ctx->ret_addr);
hashed_bit ^= FIELD_GET(BIT_ULL(18), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(23), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(32), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(intlv_bit_pos);
/* Calculation complete for 2 channels. Continue for 4 and 8 channels. */
if (ctx->map.intlv_mode == DF3_COD4_2CHAN_HASH)
return 0;
intlv_bit = FIELD_GET(BIT_ULL(12), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(16), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(21), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(30), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(12);
/* Calculation complete for 4 channels. Continue for 8 channels. */
if (ctx->map.intlv_mode == DF3_COD2_4CHAN_HASH)
return 0;
intlv_bit = FIELD_GET(BIT_ULL(13), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(17), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(22), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(31), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(13);
return 0;
}
static int df3_6chan_dehash_addr(struct addr_ctx *ctx)
{
u8 intlv_bit_pos = ctx->map.intlv_bit_pos;
u8 hashed_bit, intlv_bit, num_intlv_bits;
bool hash_ctl_2M, hash_ctl_1G;
if (ctx->map.intlv_mode != DF3_6CHAN) {
atl_debug_on_bad_intlv_mode(ctx);
return -EINVAL;
}
num_intlv_bits = ilog2(ctx->map.num_intlv_chan) + 1;
hash_ctl_2M = FIELD_GET(DF3_HASH_CTL_2M, ctx->map.ctl);
hash_ctl_1G = FIELD_GET(DF3_HASH_CTL_1G, ctx->map.ctl);
intlv_bit = !!(BIT_ULL(intlv_bit_pos) & ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= !!(BIT_ULL(intlv_bit_pos + num_intlv_bits) & ctx->ret_addr);
hashed_bit ^= FIELD_GET(BIT_ULL(23), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(32), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(intlv_bit_pos);
intlv_bit_pos++;
intlv_bit = !!(BIT_ULL(intlv_bit_pos) & ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(21), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(30), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(intlv_bit_pos);
intlv_bit_pos++;
intlv_bit = !!(BIT_ULL(intlv_bit_pos) & ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(22), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(31), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(intlv_bit_pos);
return 0;
}
static int df4_dehash_addr(struct addr_ctx *ctx)
{
bool hash_ctl_64k, hash_ctl_2M, hash_ctl_1G;
u8 hashed_bit, intlv_bit;
hash_ctl_64k = FIELD_GET(DF4_HASH_CTL_64K, ctx->map.ctl);
hash_ctl_2M = FIELD_GET(DF4_HASH_CTL_2M, ctx->map.ctl);
hash_ctl_1G = FIELD_GET(DF4_HASH_CTL_1G, ctx->map.ctl);
intlv_bit = FIELD_GET(BIT_ULL(8), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(16), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(21), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(30), ctx->ret_addr) & hash_ctl_1G;
if (ctx->map.num_intlv_sockets == 1)
hashed_bit ^= FIELD_GET(BIT_ULL(14), ctx->ret_addr);
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(8);
/*
* Hashing is possible with socket interleaving, so check the total number
* of channels in the system rather than DRAM map interleaving mode.
*
* Calculation complete for 2 channels. Continue for 4, 8, and 16 channels.
*/
if (ctx->map.total_intlv_chan <= 2)
return 0;
intlv_bit = FIELD_GET(BIT_ULL(12), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(17), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(22), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(31), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(12);
/* Calculation complete for 4 channels. Continue for 8 and 16 channels. */
if (ctx->map.total_intlv_chan <= 4)
return 0;
intlv_bit = FIELD_GET(BIT_ULL(13), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(18), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(23), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(32), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(13);
/* Calculation complete for 8 channels. Continue for 16 channels. */
if (ctx->map.total_intlv_chan <= 8)
return 0;
intlv_bit = FIELD_GET(BIT_ULL(14), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(19), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(24), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(33), ctx->ret_addr) & hash_ctl_1G;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(14);
return 0;
}
static int df4p5_dehash_addr(struct addr_ctx *ctx)
{
bool hash_ctl_64k, hash_ctl_2M, hash_ctl_1G, hash_ctl_1T;
u8 hashed_bit, intlv_bit;
u64 rehash_vector;
hash_ctl_64k = FIELD_GET(DF4_HASH_CTL_64K, ctx->map.ctl);
hash_ctl_2M = FIELD_GET(DF4_HASH_CTL_2M, ctx->map.ctl);
hash_ctl_1G = FIELD_GET(DF4_HASH_CTL_1G, ctx->map.ctl);
hash_ctl_1T = FIELD_GET(DF4p5_HASH_CTL_1T, ctx->map.ctl);
/*
* Generate a unique address to determine which bits
* need to be dehashed.
*
* Start with a contiguous bitmask for the total
* number of channels starting at bit 8.
*
* Then make a gap in the proper place based on
* interleave mode.
*/
rehash_vector = ctx->map.total_intlv_chan - 1;
rehash_vector <<= 8;
if (ctx->map.intlv_mode == DF4p5_NPS2_4CHAN_1K_HASH ||
ctx->map.intlv_mode == DF4p5_NPS1_8CHAN_1K_HASH ||
ctx->map.intlv_mode == DF4p5_NPS1_16CHAN_1K_HASH)
rehash_vector = expand_bits(10, 2, rehash_vector);
else
rehash_vector = expand_bits(9, 3, rehash_vector);
if (rehash_vector & BIT_ULL(8)) {
intlv_bit = FIELD_GET(BIT_ULL(8), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(16), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(21), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(30), ctx->ret_addr) & hash_ctl_1G;
hashed_bit ^= FIELD_GET(BIT_ULL(40), ctx->ret_addr) & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(8);
}
if (rehash_vector & BIT_ULL(9)) {
intlv_bit = FIELD_GET(BIT_ULL(9), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(17), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(22), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(31), ctx->ret_addr) & hash_ctl_1G;
hashed_bit ^= FIELD_GET(BIT_ULL(41), ctx->ret_addr) & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(9);
}
if (rehash_vector & BIT_ULL(12)) {
intlv_bit = FIELD_GET(BIT_ULL(12), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(18), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(23), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(32), ctx->ret_addr) & hash_ctl_1G;
hashed_bit ^= FIELD_GET(BIT_ULL(42), ctx->ret_addr) & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(12);
}
if (rehash_vector & BIT_ULL(13)) {
intlv_bit = FIELD_GET(BIT_ULL(13), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(19), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(24), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(33), ctx->ret_addr) & hash_ctl_1G;
hashed_bit ^= FIELD_GET(BIT_ULL(43), ctx->ret_addr) & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(13);
}
if (rehash_vector & BIT_ULL(14)) {
intlv_bit = FIELD_GET(BIT_ULL(14), ctx->ret_addr);
hashed_bit = intlv_bit;
hashed_bit ^= FIELD_GET(BIT_ULL(20), ctx->ret_addr) & hash_ctl_64k;
hashed_bit ^= FIELD_GET(BIT_ULL(25), ctx->ret_addr) & hash_ctl_2M;
hashed_bit ^= FIELD_GET(BIT_ULL(34), ctx->ret_addr) & hash_ctl_1G;
hashed_bit ^= FIELD_GET(BIT_ULL(44), ctx->ret_addr) & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(14);
}
return 0;
}
/*
* MI300 hash bits
* 4K 64K 2M 1G 1T 1T
* COH_ST_Select[0] = XOR of addr{8, 12, 15, 22, 29, 36, 43}
* COH_ST_Select[1] = XOR of addr{9, 13, 16, 23, 30, 37, 44}
* COH_ST_Select[2] = XOR of addr{10, 14, 17, 24, 31, 38, 45}
* COH_ST_Select[3] = XOR of addr{11, 18, 25, 32, 39, 46}
* COH_ST_Select[4] = XOR of addr{14, 19, 26, 33, 40, 47} aka Stack
* DieID[0] = XOR of addr{12, 20, 27, 34, 41 }
* DieID[1] = XOR of addr{13, 21, 28, 35, 42 }
*/
static int mi300_dehash_addr(struct addr_ctx *ctx)
{
bool hash_ctl_4k, hash_ctl_64k, hash_ctl_2M, hash_ctl_1G, hash_ctl_1T;
bool hashed_bit, intlv_bit, test_bit;
u8 num_intlv_bits, base_bit, i;
hash_ctl_4k = FIELD_GET(DF4p5_HASH_CTL_4K, ctx->map.ctl);
hash_ctl_64k = FIELD_GET(DF4_HASH_CTL_64K, ctx->map.ctl);
hash_ctl_2M = FIELD_GET(DF4_HASH_CTL_2M, ctx->map.ctl);
hash_ctl_1G = FIELD_GET(DF4_HASH_CTL_1G, ctx->map.ctl);
hash_ctl_1T = FIELD_GET(DF4p5_HASH_CTL_1T, ctx->map.ctl);
/* Channel bits */
num_intlv_bits = ilog2(ctx->map.num_intlv_chan);
for (i = 0; i < num_intlv_bits; i++) {
base_bit = 8 + i;
/* COH_ST_Select[4] jumps to a base bit of 14. */
if (i == 4)
base_bit = 14;
intlv_bit = BIT_ULL(base_bit) & ctx->ret_addr;
hashed_bit = intlv_bit;
/* 4k hash bit only applies to the first 3 bits. */
if (i <= 2) {
test_bit = BIT_ULL(12 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_4k;
}
/* Use temporary 'test_bit' value to avoid Sparse warnings. */
test_bit = BIT_ULL(15 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_64k;
test_bit = BIT_ULL(22 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_2M;
test_bit = BIT_ULL(29 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_1G;
test_bit = BIT_ULL(36 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_1T;
test_bit = BIT_ULL(43 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(base_bit);
}
/* Die bits */
num_intlv_bits = ilog2(ctx->map.num_intlv_dies);
for (i = 0; i < num_intlv_bits; i++) {
base_bit = 12 + i;
intlv_bit = BIT_ULL(base_bit) & ctx->ret_addr;
hashed_bit = intlv_bit;
test_bit = BIT_ULL(20 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_64k;
test_bit = BIT_ULL(27 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_2M;
test_bit = BIT_ULL(34 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_1G;
test_bit = BIT_ULL(41 + i) & ctx->ret_addr;
hashed_bit ^= test_bit & hash_ctl_1T;
if (hashed_bit != intlv_bit)
ctx->ret_addr ^= BIT_ULL(base_bit);
}
return 0;
}
int dehash_address(struct addr_ctx *ctx)
{
switch (ctx->map.intlv_mode) {
/* No hashing cases. */
case NONE:
case NOHASH_2CHAN:
case NOHASH_4CHAN:
case NOHASH_8CHAN:
case NOHASH_16CHAN:
case NOHASH_32CHAN:
/* Hashing bits handled earlier during CS ID calculation. */
case DF4_NPS4_3CHAN_HASH:
case DF4_NPS2_5CHAN_HASH:
case DF4_NPS2_6CHAN_HASH:
case DF4_NPS1_10CHAN_HASH:
case DF4_NPS1_12CHAN_HASH:
case DF4p5_NPS2_6CHAN_1K_HASH:
case DF4p5_NPS2_6CHAN_2K_HASH:
case DF4p5_NPS1_10CHAN_1K_HASH:
case DF4p5_NPS1_10CHAN_2K_HASH:
case DF4p5_NPS1_12CHAN_1K_HASH:
case DF4p5_NPS1_12CHAN_2K_HASH:
case DF4p5_NPS0_24CHAN_1K_HASH:
case DF4p5_NPS0_24CHAN_2K_HASH:
/* No hash physical address bits, so nothing to do. */
case DF4p5_NPS4_3CHAN_1K_HASH:
case DF4p5_NPS4_3CHAN_2K_HASH:
case DF4p5_NPS2_5CHAN_1K_HASH:
case DF4p5_NPS2_5CHAN_2K_HASH:
return 0;
case DF2_2CHAN_HASH:
return df2_dehash_addr(ctx);
case DF3_COD4_2CHAN_HASH:
case DF3_COD2_4CHAN_HASH:
case DF3_COD1_8CHAN_HASH:
return df3_dehash_addr(ctx);
case DF3_6CHAN:
return df3_6chan_dehash_addr(ctx);
case DF4_NPS4_2CHAN_HASH:
case DF4_NPS2_4CHAN_HASH:
case DF4_NPS1_8CHAN_HASH:
return df4_dehash_addr(ctx);
case DF4p5_NPS4_2CHAN_1K_HASH:
case DF4p5_NPS4_2CHAN_2K_HASH:
case DF4p5_NPS2_4CHAN_2K_HASH:
case DF4p5_NPS2_4CHAN_1K_HASH:
case DF4p5_NPS1_8CHAN_1K_HASH:
case DF4p5_NPS1_8CHAN_2K_HASH:
case DF4p5_NPS1_16CHAN_1K_HASH:
case DF4p5_NPS1_16CHAN_2K_HASH:
return df4p5_dehash_addr(ctx);
case MI3_HASH_8CHAN:
case MI3_HASH_16CHAN:
case MI3_HASH_32CHAN:
return mi300_dehash_addr(ctx);
default:
atl_debug_on_bad_intlv_mode(ctx);
return -EINVAL;
}
}
File diff suppressed because it is too large Load Diff
+372
View File
@@ -0,0 +1,372 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* AMD Address Translation Library
*
* internal.h : Helper functions and common defines
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#ifndef __AMD_ATL_INTERNAL_H__
#define __AMD_ATL_INTERNAL_H__
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/ras.h>
#include <asm/amd/nb.h>
#include <asm/amd/node.h>
#include "reg_fields.h"
#undef pr_fmt
#define pr_fmt(fmt) "amd_atl: " fmt
/* Maximum possible number of Coherent Stations within a single Data Fabric. */
#define MAX_COH_ST_CHANNELS 32
/* PCI ID for Zen4 Server DF Function 0. */
#define DF_FUNC0_ID_ZEN4_SERVER 0x14AD1022
/* PCI IDs for MI300 DF Function 0. */
#define DF_FUNC0_ID_MI300 0x15281022
/* Shift needed for adjusting register values to true values. */
#define DF_DRAM_BASE_LIMIT_LSB 28
#define MI300_DRAM_LIMIT_LSB 20
#define INVALID_SPA ~0ULL
enum df_revisions {
UNKNOWN,
DF2,
DF3,
DF3p5,
DF4,
DF4p5,
};
/* These are mapped 1:1 to the hardware values. Special cases are set at > 0x20. */
enum intlv_modes {
NONE = 0x00,
NOHASH_2CHAN = 0x01,
NOHASH_4CHAN = 0x03,
NOHASH_8CHAN = 0x05,
DF3_6CHAN = 0x06,
NOHASH_16CHAN = 0x07,
NOHASH_32CHAN = 0x08,
DF3_COD4_2CHAN_HASH = 0x0C,
DF3_COD2_4CHAN_HASH = 0x0D,
DF3_COD1_8CHAN_HASH = 0x0E,
DF4_NPS4_2CHAN_HASH = 0x10,
DF4_NPS2_4CHAN_HASH = 0x11,
DF4_NPS1_8CHAN_HASH = 0x12,
DF4_NPS4_3CHAN_HASH = 0x13,
DF4_NPS2_6CHAN_HASH = 0x14,
DF4_NPS1_12CHAN_HASH = 0x15,
DF4_NPS2_5CHAN_HASH = 0x16,
DF4_NPS1_10CHAN_HASH = 0x17,
MI3_HASH_8CHAN = 0x18,
MI3_HASH_16CHAN = 0x19,
MI3_HASH_32CHAN = 0x1A,
DF2_2CHAN_HASH = 0x21,
/* DF4.5 modes are all IntLvNumChan + 0x20 */
DF4p5_NPS1_16CHAN_1K_HASH = 0x2C,
DF4p5_NPS0_24CHAN_1K_HASH = 0x2E,
DF4p5_NPS4_2CHAN_1K_HASH = 0x30,
DF4p5_NPS2_4CHAN_1K_HASH = 0x31,
DF4p5_NPS1_8CHAN_1K_HASH = 0x32,
DF4p5_NPS4_3CHAN_1K_HASH = 0x33,
DF4p5_NPS2_6CHAN_1K_HASH = 0x34,
DF4p5_NPS1_12CHAN_1K_HASH = 0x35,
DF4p5_NPS2_5CHAN_1K_HASH = 0x36,
DF4p5_NPS1_10CHAN_1K_HASH = 0x37,
DF4p5_NPS4_2CHAN_2K_HASH = 0x40,
DF4p5_NPS2_4CHAN_2K_HASH = 0x41,
DF4p5_NPS1_8CHAN_2K_HASH = 0x42,
DF4p5_NPS1_16CHAN_2K_HASH = 0x43,
DF4p5_NPS4_3CHAN_2K_HASH = 0x44,
DF4p5_NPS2_6CHAN_2K_HASH = 0x45,
DF4p5_NPS1_12CHAN_2K_HASH = 0x46,
DF4p5_NPS0_24CHAN_2K_HASH = 0x47,
DF4p5_NPS2_5CHAN_2K_HASH = 0x48,
DF4p5_NPS1_10CHAN_2K_HASH = 0x49,
};
struct df4p5_denorm_ctx {
/* Indicates the number of "lost" bits. This will be 1, 2, or 3. */
u8 perm_shift;
/* A mask indicating the bits that need to be rehashed. */
u16 rehash_vector;
/*
* Represents the value that the high bits of the normalized address
* are divided by during normalization. This value will be 3 for
* interleave modes with a number of channels divisible by 3 or the
* value will be 5 for interleave modes with a number of channels
* divisible by 5. Power-of-two interleave modes are handled
* separately.
*/
u8 mod_value;
/*
* Represents the bits that can be directly pulled from the normalized
* address. In each case, pass through bits [7:0] of the normalized
* address. The other bits depend on the interleave bit position which
* will be bit 10 for 1K interleave stripe cases and bit 11 for 2K
* interleave stripe cases.
*/
u64 base_denorm_addr;
/*
* Represents the high bits of the physical address that have been
* divided by the mod_value.
*/
u64 div_addr;
u64 current_spa;
u64 resolved_spa;
u16 coh_st_fabric_id;
};
struct df_flags {
__u8 legacy_ficaa : 1,
socket_id_shift_quirk : 1,
heterogeneous : 1,
prm_only : 1,
__reserved_0 : 4;
};
struct df_config {
enum df_revisions rev;
/*
* These masks operate on the 16-bit Coherent Station IDs,
* e.g. Instance, Fabric, Destination, etc.
*/
u16 component_id_mask;
u16 die_id_mask;
u16 node_id_mask;
u16 socket_id_mask;
/*
* Least-significant bit of Node ID portion of the
* system-wide Coherent Station Fabric ID.
*/
u8 node_id_shift;
/*
* Least-significant bit of Die portion of the Node ID.
* Adjusted to include the Node ID shift in order to apply
* to the Coherent Station Fabric ID.
*/
u8 die_id_shift;
/*
* Least-significant bit of Socket portion of the Node ID.
* Adjusted to include the Node ID shift in order to apply
* to the Coherent Station Fabric ID.
*/
u8 socket_id_shift;
/* Number of DRAM Address maps visible in a Coherent Station. */
u8 num_coh_st_maps;
u32 dram_hole_base;
/* Global flags to handle special cases. */
struct df_flags flags;
};
extern struct df_config df_cfg;
struct dram_addr_map {
/*
* Each DRAM Address Map can operate independently
* in different interleaving modes.
*/
enum intlv_modes intlv_mode;
/* System-wide number for this address map. */
u8 num;
/* Raw register values */
u32 base;
u32 limit;
u32 ctl;
u32 intlv;
/*
* Logical to Physical Coherent Station Remapping array
*
* Index: Logical Coherent Station Instance ID
* Value: Physical Coherent Station Instance ID
*
* phys_coh_st_inst_id = remap_array[log_coh_st_inst_id]
*/
u8 remap_array[MAX_COH_ST_CHANNELS];
/*
* Number of bits covering DRAM Address map 0
* when interleaving is non-power-of-2.
*
* Used only for DF3_6CHAN.
*/
u8 np2_bits;
/* Position of the 'interleave bit'. */
u8 intlv_bit_pos;
/* Number of channels interleaved in this map. */
u8 num_intlv_chan;
/* Number of dies interleaved in this map. */
u8 num_intlv_dies;
/* Number of sockets interleaved in this map. */
u8 num_intlv_sockets;
/*
* Total number of channels interleaved accounting
* for die and socket interleaving.
*/
u8 total_intlv_chan;
/* Total bits needed to cover 'total_intlv_chan'. */
u8 total_intlv_bits;
};
/* Original input values cached for debug printing. */
struct addr_ctx_inputs {
u64 norm_addr;
u8 socket_id;
u8 die_id;
u8 coh_st_inst_id;
};
struct addr_ctx {
u64 ret_addr;
struct addr_ctx_inputs inputs;
struct dram_addr_map map;
/* AMD Node ID calculated from Socket and Die IDs. */
u8 node_id;
/*
* Coherent Station Instance ID
* Local ID used within a 'node'.
*/
u16 inst_id;
/*
* Coherent Station Fabric ID
* System-wide ID that includes 'node' bits.
*/
u16 coh_st_fabric_id;
};
int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo);
int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo);
int get_df_system_info(void);
int determine_node_id(struct addr_ctx *ctx, u8 socket_num, u8 die_num);
int get_umc_info_mi300(void);
int get_address_map(struct addr_ctx *ctx);
int denormalize_address(struct addr_ctx *ctx);
int dehash_address(struct addr_ctx *ctx);
unsigned long norm_to_sys_addr(u8 socket_id, u8 die_id, u8 coh_st_inst_id, unsigned long addr);
unsigned long convert_umc_mca_addr_to_sys_addr(struct atl_err *err);
u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr);
u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr);
/* GUIDs for PRM handlers */
extern const guid_t norm_to_sys_guid;
#ifdef CONFIG_AMD_ATL_PRM
unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 umc_bank_inst_id, unsigned long addr);
#else
static inline unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 umc_bank_inst_id,
unsigned long addr)
{
return -ENODEV;
}
#endif
/*
* Make a gap in @data that is @num_bits long starting at @bit_num.
* e.g. data = 11111111'b
* bit_num = 3
* num_bits = 2
* result = 1111100111'b
*/
static inline u64 expand_bits(u8 bit_num, u8 num_bits, u64 data)
{
u64 temp1, temp2;
if (!num_bits)
return data;
if (!bit_num) {
WARN_ON_ONCE(num_bits >= BITS_PER_LONG);
return data << num_bits;
}
WARN_ON_ONCE(bit_num >= BITS_PER_LONG);
temp1 = data & GENMASK_ULL(bit_num - 1, 0);
temp2 = data & GENMASK_ULL(63, bit_num);
temp2 <<= num_bits;
return temp1 | temp2;
}
/*
* Remove bits in @data between @low_bit and @high_bit inclusive.
* e.g. data = XXXYYZZZ'b
* low_bit = 3
* high_bit = 4
* result = XXXZZZ'b
*/
static inline u64 remove_bits(u8 low_bit, u8 high_bit, u64 data)
{
u64 temp1, temp2;
WARN_ON_ONCE(high_bit >= BITS_PER_LONG);
WARN_ON_ONCE(low_bit >= BITS_PER_LONG);
WARN_ON_ONCE(low_bit > high_bit);
if (!low_bit)
return data >> (high_bit++);
temp1 = GENMASK_ULL(low_bit - 1, 0) & data;
temp2 = GENMASK_ULL(63, high_bit + 1) & data;
temp2 >>= high_bit - low_bit + 1;
return temp1 | temp2;
}
#define atl_debug(ctx, fmt, arg...) \
pr_debug("socket_id=%u die_id=%u coh_st_inst_id=%u norm_addr=0x%016llx: " fmt,\
(ctx)->inputs.socket_id, (ctx)->inputs.die_id,\
(ctx)->inputs.coh_st_inst_id, (ctx)->inputs.norm_addr, ##arg)
static inline void atl_debug_on_bad_df_rev(void)
{
pr_debug("Unrecognized DF rev: %u", df_cfg.rev);
}
static inline void atl_debug_on_bad_intlv_mode(struct addr_ctx *ctx)
{
atl_debug(ctx, "Unrecognized interleave mode: %u", ctx->map.intlv_mode);
}
#define MI300_UMC_MCA_COL GENMASK(5, 1)
#define MI300_UMC_MCA_ROW13 BIT(23)
#endif /* __AMD_ATL_INTERNAL_H__ */
+779
View File
@@ -0,0 +1,779 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* map.c : Functions to read and decode DRAM address maps
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include "internal.h"
static int df2_get_intlv_mode(struct addr_ctx *ctx)
{
ctx->map.intlv_mode = FIELD_GET(DF2_INTLV_NUM_CHAN, ctx->map.base);
if (ctx->map.intlv_mode == 8)
ctx->map.intlv_mode = DF2_2CHAN_HASH;
if (ctx->map.intlv_mode != NONE &&
ctx->map.intlv_mode != NOHASH_2CHAN &&
ctx->map.intlv_mode != DF2_2CHAN_HASH)
return -EINVAL;
return 0;
}
static int df3_get_intlv_mode(struct addr_ctx *ctx)
{
ctx->map.intlv_mode = FIELD_GET(DF3_INTLV_NUM_CHAN, ctx->map.base);
return 0;
}
static int df3p5_get_intlv_mode(struct addr_ctx *ctx)
{
ctx->map.intlv_mode = FIELD_GET(DF3p5_INTLV_NUM_CHAN, ctx->map.base);
if (ctx->map.intlv_mode == DF3_6CHAN)
return -EINVAL;
return 0;
}
static int df4_get_intlv_mode(struct addr_ctx *ctx)
{
ctx->map.intlv_mode = FIELD_GET(DF4_INTLV_NUM_CHAN, ctx->map.intlv);
if (ctx->map.intlv_mode == DF3_COD4_2CHAN_HASH ||
ctx->map.intlv_mode == DF3_COD2_4CHAN_HASH ||
ctx->map.intlv_mode == DF3_COD1_8CHAN_HASH ||
ctx->map.intlv_mode == DF3_6CHAN)
return -EINVAL;
return 0;
}
static int df4p5_get_intlv_mode(struct addr_ctx *ctx)
{
ctx->map.intlv_mode = FIELD_GET(DF4p5_INTLV_NUM_CHAN, ctx->map.intlv);
if (ctx->map.intlv_mode <= NOHASH_32CHAN)
return 0;
if (ctx->map.intlv_mode >= MI3_HASH_8CHAN &&
ctx->map.intlv_mode <= MI3_HASH_32CHAN)
return 0;
/*
* Modes matching the ranges above are returned as-is.
*
* All other modes are "fixed up" by adding 20h to make a unique value.
*/
ctx->map.intlv_mode += 0x20;
return 0;
}
static int get_intlv_mode(struct addr_ctx *ctx)
{
int ret;
switch (df_cfg.rev) {
case DF2:
ret = df2_get_intlv_mode(ctx);
break;
case DF3:
ret = df3_get_intlv_mode(ctx);
break;
case DF3p5:
ret = df3p5_get_intlv_mode(ctx);
break;
case DF4:
ret = df4_get_intlv_mode(ctx);
break;
case DF4p5:
ret = df4p5_get_intlv_mode(ctx);
break;
default:
ret = -EINVAL;
}
if (ret)
atl_debug_on_bad_df_rev();
return ret;
}
static u64 get_hi_addr_offset(u32 reg_dram_offset)
{
u8 shift = DF_DRAM_BASE_LIMIT_LSB;
u64 hi_addr_offset;
switch (df_cfg.rev) {
case DF2:
hi_addr_offset = FIELD_GET(DF2_HI_ADDR_OFFSET, reg_dram_offset);
break;
case DF3:
case DF3p5:
hi_addr_offset = FIELD_GET(DF3_HI_ADDR_OFFSET, reg_dram_offset);
break;
case DF4:
case DF4p5:
hi_addr_offset = FIELD_GET(DF4_HI_ADDR_OFFSET, reg_dram_offset);
break;
default:
hi_addr_offset = 0;
atl_debug_on_bad_df_rev();
}
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous)
shift = MI300_DRAM_LIMIT_LSB;
return hi_addr_offset << shift;
}
/*
* Returns: 0 if offset is disabled.
* 1 if offset is enabled.
* -EINVAL on error.
*/
static int get_dram_offset(struct addr_ctx *ctx, u64 *norm_offset)
{
u32 reg_dram_offset;
u8 map_num;
/* Should not be called for map 0. */
if (!ctx->map.num) {
atl_debug(ctx, "Trying to find DRAM offset for map 0");
return -EINVAL;
}
/*
* DramOffset registers don't exist for map 0, so the base register
* actually refers to map 1.
* Adjust the map_num for the register offsets.
*/
map_num = ctx->map.num - 1;
if (df_cfg.rev >= DF4) {
/* Read D18F7x140 (DramOffset) */
if (df_indirect_read_instance(ctx->node_id, 7, 0x140 + (4 * map_num),
ctx->inst_id, &reg_dram_offset))
return -EINVAL;
} else {
/* Read D18F0x1B4 (DramOffset) */
if (df_indirect_read_instance(ctx->node_id, 0, 0x1B4 + (4 * map_num),
ctx->inst_id, &reg_dram_offset))
return -EINVAL;
}
if (!FIELD_GET(DF_HI_ADDR_OFFSET_EN, reg_dram_offset))
return 0;
*norm_offset = get_hi_addr_offset(reg_dram_offset);
return 1;
}
static int df3_6ch_get_dram_addr_map(struct addr_ctx *ctx)
{
u16 dst_fabric_id = FIELD_GET(DF3_DST_FABRIC_ID, ctx->map.limit);
u8 i, j, shift = 4, mask = 0xF;
u32 reg, offset = 0x60;
u16 dst_node_id;
/* Get Socket 1 register. */
if (dst_fabric_id & df_cfg.socket_id_mask)
offset = 0x68;
/* Read D18F0x06{0,8} (DF::Skt0CsTargetRemap0)/(DF::Skt0CsTargetRemap1) */
if (df_indirect_read_broadcast(ctx->node_id, 0, offset, &reg))
return -EINVAL;
/* Save 8 remap entries. */
for (i = 0, j = 0; i < 8; i++, j++)
ctx->map.remap_array[i] = (reg >> (j * shift)) & mask;
dst_node_id = dst_fabric_id & df_cfg.node_id_mask;
dst_node_id >>= df_cfg.node_id_shift;
/* Read D18F2x090 (DF::Np2ChannelConfig) */
if (df_indirect_read_broadcast(dst_node_id, 2, 0x90, &reg))
return -EINVAL;
ctx->map.np2_bits = FIELD_GET(DF_LOG2_ADDR_64K_SPACE0, reg);
return 0;
}
static int df2_get_dram_addr_map(struct addr_ctx *ctx)
{
/* Read D18F0x110 (DramBaseAddress). */
if (df_indirect_read_instance(ctx->node_id, 0, 0x110 + (8 * ctx->map.num),
ctx->inst_id, &ctx->map.base))
return -EINVAL;
/* Read D18F0x114 (DramLimitAddress). */
if (df_indirect_read_instance(ctx->node_id, 0, 0x114 + (8 * ctx->map.num),
ctx->inst_id, &ctx->map.limit))
return -EINVAL;
return 0;
}
static int df3_get_dram_addr_map(struct addr_ctx *ctx)
{
if (df2_get_dram_addr_map(ctx))
return -EINVAL;
/* Read D18F0x3F8 (DfGlobalCtl). */
if (df_indirect_read_instance(ctx->node_id, 0, 0x3F8,
ctx->inst_id, &ctx->map.ctl))
return -EINVAL;
return 0;
}
static int df4_get_dram_addr_map(struct addr_ctx *ctx)
{
u8 remap_sel, i, j, shift = 4, mask = 0xF;
u32 remap_reg;
/* Read D18F7xE00 (DramBaseAddress). */
if (df_indirect_read_instance(ctx->node_id, 7, 0xE00 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.base))
return -EINVAL;
/* Read D18F7xE04 (DramLimitAddress). */
if (df_indirect_read_instance(ctx->node_id, 7, 0xE04 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.limit))
return -EINVAL;
/* Read D18F7xE08 (DramAddressCtl). */
if (df_indirect_read_instance(ctx->node_id, 7, 0xE08 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.ctl))
return -EINVAL;
/* Read D18F7xE0C (DramAddressIntlv). */
if (df_indirect_read_instance(ctx->node_id, 7, 0xE0C + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.intlv))
return -EINVAL;
/* Check if Remap Enable bit is valid. */
if (!FIELD_GET(DF4_REMAP_EN, ctx->map.ctl))
return 0;
/* Fill with bogus values, because '0' is a valid value. */
memset(&ctx->map.remap_array, 0xFF, sizeof(ctx->map.remap_array));
/* Get Remap registers. */
remap_sel = FIELD_GET(DF4_REMAP_SEL, ctx->map.ctl);
/* Read D18F7x180 (CsTargetRemap0A). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x180 + (8 * remap_sel),
ctx->inst_id, &remap_reg))
return -EINVAL;
/* Save first 8 remap entries. */
for (i = 0, j = 0; i < 8; i++, j++)
ctx->map.remap_array[i] = (remap_reg >> (j * shift)) & mask;
/* Read D18F7x184 (CsTargetRemap0B). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x184 + (8 * remap_sel),
ctx->inst_id, &remap_reg))
return -EINVAL;
/* Save next 8 remap entries. */
for (i = 8, j = 0; i < 16; i++, j++)
ctx->map.remap_array[i] = (remap_reg >> (j * shift)) & mask;
return 0;
}
static int df4p5_get_dram_addr_map(struct addr_ctx *ctx)
{
u8 remap_sel, i, j, shift = 5, mask = 0x1F;
u32 remap_reg;
/* Read D18F7x200 (DramBaseAddress). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x200 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.base))
return -EINVAL;
/* Read D18F7x204 (DramLimitAddress). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x204 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.limit))
return -EINVAL;
/* Read D18F7x208 (DramAddressCtl). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x208 + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.ctl))
return -EINVAL;
/* Read D18F7x20C (DramAddressIntlv). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x20C + (16 * ctx->map.num),
ctx->inst_id, &ctx->map.intlv))
return -EINVAL;
/* Check if Remap Enable bit is valid. */
if (!FIELD_GET(DF4_REMAP_EN, ctx->map.ctl))
return 0;
/* Fill with bogus values, because '0' is a valid value. */
memset(&ctx->map.remap_array, 0xFF, sizeof(ctx->map.remap_array));
/* Get Remap registers. */
remap_sel = FIELD_GET(DF4p5_REMAP_SEL, ctx->map.ctl);
/* Read D18F7x180 (CsTargetRemap0A). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x180 + (24 * remap_sel),
ctx->inst_id, &remap_reg))
return -EINVAL;
/* Save first 6 remap entries. */
for (i = 0, j = 0; i < 6; i++, j++)
ctx->map.remap_array[i] = (remap_reg >> (j * shift)) & mask;
/* Read D18F7x184 (CsTargetRemap0B). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x184 + (24 * remap_sel),
ctx->inst_id, &remap_reg))
return -EINVAL;
/* Save next 6 remap entries. */
for (i = 6, j = 0; i < 12; i++, j++)
ctx->map.remap_array[i] = (remap_reg >> (j * shift)) & mask;
/* Read D18F7x188 (CsTargetRemap0C). */
if (df_indirect_read_instance(ctx->node_id, 7, 0x188 + (24 * remap_sel),
ctx->inst_id, &remap_reg))
return -EINVAL;
/* Save next 6 remap entries. */
for (i = 12, j = 0; i < 18; i++, j++)
ctx->map.remap_array[i] = (remap_reg >> (j * shift)) & mask;
return 0;
}
static int get_dram_addr_map(struct addr_ctx *ctx)
{
switch (df_cfg.rev) {
case DF2: return df2_get_dram_addr_map(ctx);
case DF3:
case DF3p5: return df3_get_dram_addr_map(ctx);
case DF4: return df4_get_dram_addr_map(ctx);
case DF4p5: return df4p5_get_dram_addr_map(ctx);
default:
atl_debug_on_bad_df_rev();
return -EINVAL;
}
}
static int get_coh_st_fabric_id(struct addr_ctx *ctx)
{
u32 reg;
/*
* On MI300 systems, the Coherent Station Fabric ID is derived
* later. And it does not depend on the register value.
*/
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous)
return 0;
/* Read D18F0x50 (FabricBlockInstanceInformation3). */
if (df_indirect_read_instance(ctx->node_id, 0, 0x50, ctx->inst_id, &reg))
return -EINVAL;
if (df_cfg.rev < DF4p5)
ctx->coh_st_fabric_id = FIELD_GET(DF2_COH_ST_FABRIC_ID, reg);
else
ctx->coh_st_fabric_id = FIELD_GET(DF4p5_COH_ST_FABRIC_ID, reg);
return 0;
}
static int find_normalized_offset(struct addr_ctx *ctx, u64 *norm_offset)
{
u64 last_offset = 0;
int ret;
for (ctx->map.num = 1; ctx->map.num < df_cfg.num_coh_st_maps; ctx->map.num++) {
ret = get_dram_offset(ctx, norm_offset);
if (ret < 0)
return ret;
/* Continue search if this map's offset is not enabled. */
if (!ret)
continue;
/* Enabled offsets should never be 0. */
if (*norm_offset == 0) {
atl_debug(ctx, "Enabled map %u offset is 0", ctx->map.num);
return -EINVAL;
}
/* Offsets should always increase from one map to the next. */
if (*norm_offset <= last_offset) {
atl_debug(ctx, "Map %u offset (0x%016llx) <= previous (0x%016llx)",
ctx->map.num, *norm_offset, last_offset);
return -EINVAL;
}
/* Match if this map's offset is less than the current calculated address. */
if (ctx->ret_addr >= *norm_offset)
break;
last_offset = *norm_offset;
}
/*
* Finished search without finding a match.
* Reset to map 0 and no offset.
*/
if (ctx->map.num >= df_cfg.num_coh_st_maps) {
ctx->map.num = 0;
*norm_offset = 0;
}
return 0;
}
static bool valid_map(struct addr_ctx *ctx)
{
if (df_cfg.rev >= DF4)
return FIELD_GET(DF_ADDR_RANGE_VAL, ctx->map.ctl);
else
return FIELD_GET(DF_ADDR_RANGE_VAL, ctx->map.base);
}
static int get_address_map_common(struct addr_ctx *ctx)
{
u64 norm_offset = 0;
if (get_coh_st_fabric_id(ctx))
return -EINVAL;
if (find_normalized_offset(ctx, &norm_offset))
return -EINVAL;
if (get_dram_addr_map(ctx))
return -EINVAL;
if (!valid_map(ctx))
return -EINVAL;
ctx->ret_addr -= norm_offset;
return 0;
}
static u8 get_num_intlv_chan(struct addr_ctx *ctx)
{
switch (ctx->map.intlv_mode) {
case NONE:
return 1;
case NOHASH_2CHAN:
case DF2_2CHAN_HASH:
case DF3_COD4_2CHAN_HASH:
case DF4_NPS4_2CHAN_HASH:
case DF4p5_NPS4_2CHAN_1K_HASH:
case DF4p5_NPS4_2CHAN_2K_HASH:
return 2;
case DF4_NPS4_3CHAN_HASH:
case DF4p5_NPS4_3CHAN_1K_HASH:
case DF4p5_NPS4_3CHAN_2K_HASH:
return 3;
case NOHASH_4CHAN:
case DF3_COD2_4CHAN_HASH:
case DF4_NPS2_4CHAN_HASH:
case DF4p5_NPS2_4CHAN_1K_HASH:
case DF4p5_NPS2_4CHAN_2K_HASH:
return 4;
case DF4_NPS2_5CHAN_HASH:
case DF4p5_NPS2_5CHAN_1K_HASH:
case DF4p5_NPS2_5CHAN_2K_HASH:
return 5;
case DF3_6CHAN:
case DF4_NPS2_6CHAN_HASH:
case DF4p5_NPS2_6CHAN_1K_HASH:
case DF4p5_NPS2_6CHAN_2K_HASH:
return 6;
case NOHASH_8CHAN:
case DF3_COD1_8CHAN_HASH:
case DF4_NPS1_8CHAN_HASH:
case MI3_HASH_8CHAN:
case DF4p5_NPS1_8CHAN_1K_HASH:
case DF4p5_NPS1_8CHAN_2K_HASH:
return 8;
case DF4_NPS1_10CHAN_HASH:
case DF4p5_NPS1_10CHAN_1K_HASH:
case DF4p5_NPS1_10CHAN_2K_HASH:
return 10;
case DF4_NPS1_12CHAN_HASH:
case DF4p5_NPS1_12CHAN_1K_HASH:
case DF4p5_NPS1_12CHAN_2K_HASH:
return 12;
case NOHASH_16CHAN:
case MI3_HASH_16CHAN:
case DF4p5_NPS1_16CHAN_1K_HASH:
case DF4p5_NPS1_16CHAN_2K_HASH:
return 16;
case DF4p5_NPS0_24CHAN_1K_HASH:
case DF4p5_NPS0_24CHAN_2K_HASH:
return 24;
case NOHASH_32CHAN:
case MI3_HASH_32CHAN:
return 32;
default:
atl_debug_on_bad_intlv_mode(ctx);
return 0;
}
}
static void calculate_intlv_bits(struct addr_ctx *ctx)
{
ctx->map.num_intlv_chan = get_num_intlv_chan(ctx);
ctx->map.total_intlv_chan = ctx->map.num_intlv_chan;
ctx->map.total_intlv_chan *= ctx->map.num_intlv_dies;
ctx->map.total_intlv_chan *= ctx->map.num_intlv_sockets;
/*
* Get the number of bits needed to cover this many channels.
* order_base_2() rounds up automatically.
*/
ctx->map.total_intlv_bits = order_base_2(ctx->map.total_intlv_chan);
}
static u8 get_intlv_bit_pos(struct addr_ctx *ctx)
{
u8 addr_sel = 0;
switch (df_cfg.rev) {
case DF2:
addr_sel = FIELD_GET(DF2_INTLV_ADDR_SEL, ctx->map.base);
break;
case DF3:
case DF3p5:
addr_sel = FIELD_GET(DF3_INTLV_ADDR_SEL, ctx->map.base);
break;
case DF4:
case DF4p5:
addr_sel = FIELD_GET(DF4_INTLV_ADDR_SEL, ctx->map.intlv);
break;
default:
atl_debug_on_bad_df_rev();
break;
}
/* Add '8' to get the 'interleave bit position'. */
return addr_sel + 8;
}
static u8 get_num_intlv_dies(struct addr_ctx *ctx)
{
u8 dies = 0;
switch (df_cfg.rev) {
case DF2:
dies = FIELD_GET(DF2_INTLV_NUM_DIES, ctx->map.limit);
break;
case DF3:
dies = FIELD_GET(DF3_INTLV_NUM_DIES, ctx->map.base);
break;
case DF3p5:
dies = FIELD_GET(DF3p5_INTLV_NUM_DIES, ctx->map.base);
break;
case DF4:
case DF4p5:
dies = FIELD_GET(DF4_INTLV_NUM_DIES, ctx->map.intlv);
break;
default:
atl_debug_on_bad_df_rev();
break;
}
/* Register value is log2, e.g. 0 -> 1 die, 1 -> 2 dies, etc. */
return 1 << dies;
}
static u8 get_num_intlv_sockets(struct addr_ctx *ctx)
{
u8 sockets = 0;
switch (df_cfg.rev) {
case DF2:
sockets = FIELD_GET(DF2_INTLV_NUM_SOCKETS, ctx->map.limit);
break;
case DF3:
case DF3p5:
sockets = FIELD_GET(DF2_INTLV_NUM_SOCKETS, ctx->map.base);
break;
case DF4:
case DF4p5:
sockets = FIELD_GET(DF4_INTLV_NUM_SOCKETS, ctx->map.intlv);
break;
default:
atl_debug_on_bad_df_rev();
break;
}
/* Register value is log2, e.g. 0 -> 1 sockets, 1 -> 2 sockets, etc. */
return 1 << sockets;
}
static int get_global_map_data(struct addr_ctx *ctx)
{
if (get_intlv_mode(ctx))
return -EINVAL;
if (ctx->map.intlv_mode == DF3_6CHAN &&
df3_6ch_get_dram_addr_map(ctx))
return -EINVAL;
ctx->map.intlv_bit_pos = get_intlv_bit_pos(ctx);
ctx->map.num_intlv_dies = get_num_intlv_dies(ctx);
ctx->map.num_intlv_sockets = get_num_intlv_sockets(ctx);
calculate_intlv_bits(ctx);
return 0;
}
/*
* Verify the interleave bits are correct in the different interleaving
* settings.
*
* If @num_intlv_dies and/or @num_intlv_sockets are 1, it means the
* respective interleaving is disabled.
*/
static inline bool map_bits_valid(struct addr_ctx *ctx, u8 bit1, u8 bit2,
u8 num_intlv_dies, u8 num_intlv_sockets)
{
if (!(ctx->map.intlv_bit_pos == bit1 || ctx->map.intlv_bit_pos == bit2)) {
pr_debug("Invalid interleave bit: %u", ctx->map.intlv_bit_pos);
return false;
}
if (ctx->map.num_intlv_dies > num_intlv_dies) {
pr_debug("Invalid number of interleave dies: %u", ctx->map.num_intlv_dies);
return false;
}
if (ctx->map.num_intlv_sockets > num_intlv_sockets) {
pr_debug("Invalid number of interleave sockets: %u", ctx->map.num_intlv_sockets);
return false;
}
return true;
}
static int validate_address_map(struct addr_ctx *ctx)
{
switch (ctx->map.intlv_mode) {
case DF2_2CHAN_HASH:
case DF3_COD4_2CHAN_HASH:
case DF3_COD2_4CHAN_HASH:
case DF3_COD1_8CHAN_HASH:
if (!map_bits_valid(ctx, 8, 9, 1, 1))
goto err;
break;
case DF4_NPS4_2CHAN_HASH:
case DF4_NPS2_4CHAN_HASH:
case DF4_NPS1_8CHAN_HASH:
case DF4p5_NPS4_2CHAN_1K_HASH:
case DF4p5_NPS4_2CHAN_2K_HASH:
case DF4p5_NPS2_4CHAN_1K_HASH:
case DF4p5_NPS2_4CHAN_2K_HASH:
case DF4p5_NPS1_8CHAN_1K_HASH:
case DF4p5_NPS1_8CHAN_2K_HASH:
case DF4p5_NPS1_16CHAN_1K_HASH:
case DF4p5_NPS1_16CHAN_2K_HASH:
if (!map_bits_valid(ctx, 8, 8, 1, 2))
goto err;
break;
case DF4p5_NPS4_3CHAN_1K_HASH:
case DF4p5_NPS4_3CHAN_2K_HASH:
case DF4p5_NPS2_5CHAN_1K_HASH:
case DF4p5_NPS2_5CHAN_2K_HASH:
case DF4p5_NPS2_6CHAN_1K_HASH:
case DF4p5_NPS2_6CHAN_2K_HASH:
case DF4p5_NPS1_10CHAN_1K_HASH:
case DF4p5_NPS1_10CHAN_2K_HASH:
case DF4p5_NPS1_12CHAN_1K_HASH:
case DF4p5_NPS1_12CHAN_2K_HASH:
if (ctx->map.num_intlv_sockets != 1 || !map_bits_valid(ctx, 8, 0, 1, 1))
goto err;
break;
case DF4p5_NPS0_24CHAN_1K_HASH:
case DF4p5_NPS0_24CHAN_2K_HASH:
if (ctx->map.num_intlv_sockets < 2 || !map_bits_valid(ctx, 8, 0, 1, 2))
goto err;
break;
case MI3_HASH_8CHAN:
case MI3_HASH_16CHAN:
case MI3_HASH_32CHAN:
if (!map_bits_valid(ctx, 8, 8, 4, 1))
goto err;
break;
/* Nothing to do for modes that don't need special validation checks. */
default:
break;
}
return 0;
err:
atl_debug(ctx, "Inconsistent address map");
return -EINVAL;
}
static void dump_address_map(struct dram_addr_map *map)
{
u8 i;
pr_debug("intlv_mode=0x%x", map->intlv_mode);
pr_debug("num=0x%x", map->num);
pr_debug("base=0x%x", map->base);
pr_debug("limit=0x%x", map->limit);
pr_debug("ctl=0x%x", map->ctl);
pr_debug("intlv=0x%x", map->intlv);
for (i = 0; i < MAX_COH_ST_CHANNELS; i++)
pr_debug("remap_array[%u]=0x%x", i, map->remap_array[i]);
pr_debug("intlv_bit_pos=%u", map->intlv_bit_pos);
pr_debug("num_intlv_chan=%u", map->num_intlv_chan);
pr_debug("num_intlv_dies=%u", map->num_intlv_dies);
pr_debug("num_intlv_sockets=%u", map->num_intlv_sockets);
pr_debug("total_intlv_chan=%u", map->total_intlv_chan);
pr_debug("total_intlv_bits=%u", map->total_intlv_bits);
}
int get_address_map(struct addr_ctx *ctx)
{
int ret;
ret = get_address_map_common(ctx);
if (ret)
return ret;
ret = get_global_map_data(ctx);
if (ret)
return ret;
dump_address_map(&ctx->map);
ret = validate_address_map(ctx);
if (ret)
return ret;
return ret;
}
+53
View File
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* prm.c : Plumbing code for ACPI Platform Runtime Mechanism (PRM)
*
* Information on AMD PRM modules and handlers including the GUIDs and buffer
* structures used here are defined in the AMD ACPI Porting Guide in the
* chapter "Platform Runtime Mechanism Table (PRMT)"
*
* Copyright (c) 2024, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: John Allen <john.allen@amd.com>
*/
#include "internal.h"
#include <linux/prmt.h>
/*
* PRM parameter buffer - normalized to system physical address, as described
* in the "PRM Parameter Buffer" section of the AMD ACPI Porting Guide.
*/
struct norm_to_sys_param_buf {
u64 norm_addr;
u8 socket;
u64 bank_id;
void *out_buf;
} __packed;
unsigned long prm_umc_norm_to_sys_addr(u8 socket_id, u64 bank_id, unsigned long addr)
{
struct norm_to_sys_param_buf p_buf;
unsigned long ret_addr;
int ret;
p_buf.norm_addr = addr;
p_buf.socket = socket_id;
p_buf.bank_id = bank_id;
p_buf.out_buf = &ret_addr;
ret = acpi_call_prm_handler(norm_to_sys_guid, &p_buf);
if (!ret)
return ret_addr;
if (ret == -ENODEV)
pr_debug("PRM module/handler not available\n");
else
pr_notice_once("PRM address translation failed\n");
return ret;
}
@@ -0,0 +1,606 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* AMD Address Translation Library
*
* reg_fields.h : Register field definitions
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
/*
* Notes on naming:
* 1) Use "DF_" prefix for fields that are the same for all revisions.
* 2) Use "DFx_" prefix for fields that differ between revisions.
* a) "x" is the first major revision where the new field appears.
* b) E.g., if DF2 and DF3 have the same field, then call it DF2.
* c) E.g., if DF3p5 and DF4 have the same field, then call it DF4.
*/
/*
* Coherent Station Fabric ID
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x50 [Fabric Block Instance Information 3]
* DF2 BlockFabricId [19:8]
* DF3 BlockFabricId [19:8]
* DF3p5 BlockFabricId [19:8]
* DF4 BlockFabricId [19:8]
* DF4p5 BlockFabricId [15:8]
*/
#define DF2_COH_ST_FABRIC_ID GENMASK(19, 8)
#define DF4p5_COH_ST_FABRIC_ID GENMASK(15, 8)
/*
* Component ID Mask
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
*
* D18F1x208 [System Fabric ID Mask 0]
* DF3 ComponentIdMask [9:0]
*
* D18F1x150 [System Fabric ID Mask 0]
* DF3p5 ComponentIdMask [15:0]
*
* D18F4x1B0 [System Fabric ID Mask 0]
* DF4 ComponentIdMask [15:0]
* DF4p5 ComponentIdMask [15:0]
*/
#define DF3_COMPONENT_ID_MASK GENMASK(9, 0)
#define DF4_COMPONENT_ID_MASK GENMASK(15, 0)
/*
* Destination Fabric ID
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x114 [DRAM Limit Address]
* DF2 DstFabricID [7:0]
* DF3 DstFabricID [9:0]
* DF3 DstFabricID [11:0]
*
* D18F7xE08 [DRAM Address Control]
* DF4 DstFabricID [27:16]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 DstFabricID [23:16]
*/
#define DF2_DST_FABRIC_ID GENMASK(7, 0)
#define DF3_DST_FABRIC_ID GENMASK(9, 0)
#define DF3p5_DST_FABRIC_ID GENMASK(11, 0)
#define DF4_DST_FABRIC_ID GENMASK(27, 16)
#define DF4p5_DST_FABRIC_ID GENMASK(23, 16)
/*
* Die ID Mask
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* D18F1x208 [System Fabric ID Mask]
* DF2 DieIdMask [15:8]
*
* D18F1x20C [System Fabric ID Mask 1]
* DF3 DieIdMask [18:16]
*
* D18F1x158 [System Fabric ID Mask 2]
* DF3p5 DieIdMask [15:0]
*
* D18F4x1B8 [System Fabric ID Mask 2]
* DF4 DieIdMask [15:0]
* DF4p5 DieIdMask [15:0]
*/
#define DF2_DIE_ID_MASK GENMASK(15, 8)
#define DF3_DIE_ID_MASK GENMASK(18, 16)
#define DF4_DIE_ID_MASK GENMASK(15, 0)
/*
* Die ID Shift
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* D18F1x208 [System Fabric ID Mask]
* DF2 DieIdShift [27:24]
*
* DF3 N/A
* DF3p5 N/A
* DF4 N/A
* DF4p5 N/A
*/
#define DF2_DIE_ID_SHIFT GENMASK(27, 24)
/*
* DRAM Address Range Valid
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x110 [DRAM Base Address]
* DF2 AddrRngVal [0]
* DF3 AddrRngVal [0]
* DF3p5 AddrRngVal [0]
*
* D18F7xE08 [DRAM Address Control]
* DF4 AddrRngVal [0]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 AddrRngVal [0]
*/
#define DF_ADDR_RANGE_VAL BIT(0)
/*
* DRAM Base Address
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x110 [DRAM Base Address]
* DF2 DramBaseAddr [31:12]
* DF3 DramBaseAddr [31:12]
* DF3p5 DramBaseAddr [31:12]
*
* D18F7xE00 [DRAM Base Address]
* DF4 DramBaseAddr [27:0]
*
* D18F7x200 [DRAM Base Address]
* DF4p5 DramBaseAddr [27:0]
*/
#define DF2_BASE_ADDR GENMASK(31, 12)
#define DF4_BASE_ADDR GENMASK(27, 0)
/*
* DRAM Hole Base
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* D18F0x104 [DRAM Hole Control]
* DF2 DramHoleBase [31:24]
* DF3 DramHoleBase [31:24]
* DF3p5 DramHoleBase [31:24]
*
* D18F7x104 [DRAM Hole Control]
* DF4 DramHoleBase [31:24]
* DF4p5 DramHoleBase [31:24]
*/
#define DF_DRAM_HOLE_BASE_MASK GENMASK(31, 24)
/*
* DRAM Limit Address
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x114 [DRAM Limit Address]
* DF2 DramLimitAddr [31:12]
* DF3 DramLimitAddr [31:12]
* DF3p5 DramLimitAddr [31:12]
*
* D18F7xE04 [DRAM Limit Address]
* DF4 DramLimitAddr [27:0]
*
* D18F7x204 [DRAM Limit Address]
* DF4p5 DramLimitAddr [27:0]
*/
#define DF2_DRAM_LIMIT_ADDR GENMASK(31, 12)
#define DF4_DRAM_LIMIT_ADDR GENMASK(27, 0)
/*
* Hash Interleave Controls
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
*
* D18F0x3F8 [DF Global Control]
* DF3 GlbHashIntlvCtl64K [20]
* GlbHashIntlvCtl2M [21]
* GlbHashIntlvCtl1G [22]
*
* DF3p5 GlbHashIntlvCtl64K [20]
* GlbHashIntlvCtl2M [21]
* GlbHashIntlvCtl1G [22]
*
* D18F7xE08 [DRAM Address Control]
* DF4 HashIntlvCtl64K [8]
* HashIntlvCtl2M [9]
* HashIntlvCtl1G [10]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 HashIntlvCtl4K [7]
* HashIntlvCtl64K [8]
* HashIntlvCtl2M [9]
* HashIntlvCtl1G [10]
* HashIntlvCtl1T [15]
*/
#define DF3_HASH_CTL_64K BIT(20)
#define DF3_HASH_CTL_2M BIT(21)
#define DF3_HASH_CTL_1G BIT(22)
#define DF4_HASH_CTL_64K BIT(8)
#define DF4_HASH_CTL_2M BIT(9)
#define DF4_HASH_CTL_1G BIT(10)
#define DF4p5_HASH_CTL_4K BIT(7)
#define DF4p5_HASH_CTL_1T BIT(15)
/*
* High Address Offset
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x1B4 [DRAM Offset]
* DF2 HiAddrOffset [31:20]
* DF3 HiAddrOffset [31:12]
* DF3p5 HiAddrOffset [31:12]
*
* D18F7x140 [DRAM Offset]
* DF4 HiAddrOffset [24:1]
* DF4p5 HiAddrOffset [24:1]
* MI300 HiAddrOffset [31:1]
*/
#define DF2_HI_ADDR_OFFSET GENMASK(31, 20)
#define DF3_HI_ADDR_OFFSET GENMASK(31, 12)
/* Follow reference code by including reserved bits for simplicity. */
#define DF4_HI_ADDR_OFFSET GENMASK(31, 1)
/*
* High Address Offset Enable
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x1B4 [DRAM Offset]
* DF2 HiAddrOffsetEn [0]
* DF3 HiAddrOffsetEn [0]
* DF3p5 HiAddrOffsetEn [0]
*
* D18F7x140 [DRAM Offset]
* DF4 HiAddrOffsetEn [0]
* DF4p5 HiAddrOffsetEn [0]
*/
#define DF_HI_ADDR_OFFSET_EN BIT(0)
/*
* Interleave Address Select
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x110 [DRAM Base Address]
* DF2 IntLvAddrSel [10:8]
* DF3 IntLvAddrSel [11:9]
* DF3p5 IntLvAddrSel [11:9]
*
* D18F7xE0C [DRAM Address Interleave]
* DF4 IntLvAddrSel [2:0]
*
* D18F7x20C [DRAM Address Interleave]
* DF4p5 IntLvAddrSel [2:0]
*/
#define DF2_INTLV_ADDR_SEL GENMASK(10, 8)
#define DF3_INTLV_ADDR_SEL GENMASK(11, 9)
#define DF4_INTLV_ADDR_SEL GENMASK(2, 0)
/*
* Interleave Number of Channels
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x110 [DRAM Base Address]
* DF2 IntLvNumChan [7:4]
* DF3 IntLvNumChan [5:2]
* DF3p5 IntLvNumChan [6:2]
*
* D18F7xE0C [DRAM Address Interleave]
* DF4 IntLvNumChan [8:4]
*
* D18F7x20C [DRAM Address Interleave]
* DF4p5 IntLvNumChan [9:4]
*/
#define DF2_INTLV_NUM_CHAN GENMASK(7, 4)
#define DF3_INTLV_NUM_CHAN GENMASK(5, 2)
#define DF3p5_INTLV_NUM_CHAN GENMASK(6, 2)
#define DF4_INTLV_NUM_CHAN GENMASK(8, 4)
#define DF4p5_INTLV_NUM_CHAN GENMASK(9, 4)
/*
* Interleave Number of Dies
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x114 [DRAM Limit Address]
* DF2 IntLvNumDies [11:10]
*
* D18F0x110 [DRAM Base Address]
* DF3 IntLvNumDies [7:6]
* DF3p5 IntLvNumDies [7]
*
* D18F7xE0C [DRAM Address Interleave]
* DF4 IntLvNumDies [13:12]
*
* D18F7x20C [DRAM Address Interleave]
* DF4p5 IntLvNumDies [13:12]
*/
#define DF2_INTLV_NUM_DIES GENMASK(11, 10)
#define DF3_INTLV_NUM_DIES GENMASK(7, 6)
#define DF3p5_INTLV_NUM_DIES BIT(7)
#define DF4_INTLV_NUM_DIES GENMASK(13, 12)
/*
* Interleave Number of Sockets
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x114 [DRAM Limit Address]
* DF2 IntLvNumSockets [8]
*
* D18F0x110 [DRAM Base Address]
* DF3 IntLvNumSockets [8]
* DF3p5 IntLvNumSockets [8]
*
* D18F7xE0C [DRAM Address Interleave]
* DF4 IntLvNumSockets [18]
*
* D18F7x20C [DRAM Address Interleave]
* DF4p5 IntLvNumSockets [18]
*/
#define DF2_INTLV_NUM_SOCKETS BIT(8)
#define DF4_INTLV_NUM_SOCKETS BIT(18)
/*
* Legacy MMIO Hole Enable
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* D18F0x110 [DRAM Base Address]
* DF2 LgcyMmioHoleEn [1]
* DF3 LgcyMmioHoleEn [1]
* DF3p5 LgcyMmioHoleEn [1]
*
* D18F7xE08 [DRAM Address Control]
* DF4 LgcyMmioHoleEn [1]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 LgcyMmioHoleEn [1]
*/
#define DF_LEGACY_MMIO_HOLE_EN BIT(1)
/*
* Log2 Address 64K Space 0
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
*
* D18F2x90 [Non-power-of-2 channel Configuration Register for COH_ST DRAM Address Maps]
* DF3 Log2Addr64KSpace0 [5:0]
*
* DF3p5 N/A
* DF4 N/A
* DF4p5 N/A
*/
#define DF_LOG2_ADDR_64K_SPACE0 GENMASK(5, 0)
/*
* Major Revision
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
* DF3 N/A
* DF3p5 N/A
*
* D18F0x040 [Fabric Block Instance Count]
* DF4 MajorRevision [27:24]
* DF4p5 MajorRevision [27:24]
*/
#define DF_MAJOR_REVISION GENMASK(27, 24)
/*
* Minor Revision
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
* DF3 N/A
* DF3p5 N/A
*
* D18F0x040 [Fabric Block Instance Count]
* DF4 MinorRevision [23:16]
* DF4p5 MinorRevision [23:16]
*/
#define DF_MINOR_REVISION GENMASK(23, 16)
/*
* Node ID Mask
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
*
* D18F1x208 [System Fabric ID Mask 0]
* DF3 NodeIdMask [25:16]
*
* D18F1x150 [System Fabric ID Mask 0]
* DF3p5 NodeIdMask [31:16]
*
* D18F4x1B0 [System Fabric ID Mask 0]
* DF4 NodeIdMask [31:16]
* DF4p5 NodeIdMask [31:16]
*/
#define DF3_NODE_ID_MASK GENMASK(25, 16)
#define DF4_NODE_ID_MASK GENMASK(31, 16)
/*
* Node ID Shift
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
*
* D18F1x20C [System Fabric ID Mask 1]
* DF3 NodeIdShift [3:0]
*
* D18F1x154 [System Fabric ID Mask 1]
* DF3p5 NodeIdShift [3:0]
*
* D18F4x1B4 [System Fabric ID Mask 1]
* DF4 NodeIdShift [3:0]
* DF4p5 NodeIdShift [3:0]
*/
#define DF3_NODE_ID_SHIFT GENMASK(3, 0)
/*
* Remap Enable
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
* DF3 N/A
* DF3p5 N/A
*
* D18F7xE08 [DRAM Address Control]
* DF4 RemapEn [4]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 RemapEn [4]
*/
#define DF4_REMAP_EN BIT(4)
/*
* Remap Select
*
* Access type: Instance
*
* Register
* Rev Fieldname Bits
*
* DF2 N/A
* DF3 N/A
* DF3p5 N/A
*
* D18F7xE08 [DRAM Address Control]
* DF4 RemapSel [7:5]
*
* D18F7x208 [DRAM Address Control]
* DF4p5 RemapSel [6:5]
*/
#define DF4_REMAP_SEL GENMASK(7, 5)
#define DF4p5_REMAP_SEL GENMASK(6, 5)
/*
* Socket ID Mask
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* D18F1x208 [System Fabric ID Mask]
* DF2 SocketIdMask [23:16]
*
* D18F1x20C [System Fabric ID Mask 1]
* DF3 SocketIdMask [26:24]
*
* D18F1x158 [System Fabric ID Mask 2]
* DF3p5 SocketIdMask [31:16]
*
* D18F4x1B8 [System Fabric ID Mask 2]
* DF4 SocketIdMask [31:16]
* DF4p5 SocketIdMask [31:16]
*/
#define DF2_SOCKET_ID_MASK GENMASK(23, 16)
#define DF3_SOCKET_ID_MASK GENMASK(26, 24)
#define DF4_SOCKET_ID_MASK GENMASK(31, 16)
/*
* Socket ID Shift
*
* Access type: Broadcast
*
* Register
* Rev Fieldname Bits
*
* D18F1x208 [System Fabric ID Mask]
* DF2 SocketIdShift [31:28]
*
* D18F1x20C [System Fabric ID Mask 1]
* DF3 SocketIdShift [9:8]
*
* D18F1x158 [System Fabric ID Mask 2]
* DF3p5 SocketIdShift [11:8]
*
* D18F4x1B4 [System Fabric ID Mask 1]
* DF4 SocketIdShift [11:8]
* DF4p5 SocketIdShift [11:8]
*/
#define DF2_SOCKET_ID_SHIFT GENMASK(31, 28)
#define DF3_SOCKET_ID_SHIFT GENMASK(9, 8)
#define DF4_SOCKET_ID_SHIFT GENMASK(11, 8)
+321
View File
@@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* system.c : Functions to read and save system-wide data
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include "internal.h"
#include <linux/prmt.h>
const guid_t norm_to_sys_guid = GUID_INIT(0xE7180659, 0xA65D, 0x451D,
0x92, 0xCD, 0x2B, 0x56, 0xF1,
0x2B, 0xEB, 0xA6);
int determine_node_id(struct addr_ctx *ctx, u8 socket_id, u8 die_id)
{
u16 socket_id_bits, die_id_bits;
if (socket_id > 0 && df_cfg.socket_id_mask == 0) {
atl_debug(ctx, "Invalid socket inputs: socket_id=%u socket_id_mask=0x%x",
socket_id, df_cfg.socket_id_mask);
return -EINVAL;
}
/* Do each step independently to avoid shift out-of-bounds issues. */
socket_id_bits = socket_id;
socket_id_bits <<= df_cfg.socket_id_shift;
socket_id_bits &= df_cfg.socket_id_mask;
if (die_id > 0 && df_cfg.die_id_mask == 0) {
atl_debug(ctx, "Invalid die inputs: die_id=%u die_id_mask=0x%x",
die_id, df_cfg.die_id_mask);
return -EINVAL;
}
/* Do each step independently to avoid shift out-of-bounds issues. */
die_id_bits = die_id;
die_id_bits <<= df_cfg.die_id_shift;
die_id_bits &= df_cfg.die_id_mask;
ctx->node_id = (socket_id_bits | die_id_bits) >> df_cfg.node_id_shift;
return 0;
}
static void df2_get_masks_shifts(u32 mask0)
{
df_cfg.socket_id_shift = FIELD_GET(DF2_SOCKET_ID_SHIFT, mask0);
df_cfg.socket_id_mask = FIELD_GET(DF2_SOCKET_ID_MASK, mask0);
df_cfg.die_id_shift = FIELD_GET(DF2_DIE_ID_SHIFT, mask0);
df_cfg.die_id_mask = FIELD_GET(DF2_DIE_ID_MASK, mask0);
df_cfg.node_id_shift = df_cfg.die_id_shift;
df_cfg.node_id_mask = df_cfg.socket_id_mask | df_cfg.die_id_mask;
df_cfg.component_id_mask = ~df_cfg.node_id_mask;
}
static void df3_get_masks_shifts(u32 mask0, u32 mask1)
{
df_cfg.component_id_mask = FIELD_GET(DF3_COMPONENT_ID_MASK, mask0);
df_cfg.node_id_mask = FIELD_GET(DF3_NODE_ID_MASK, mask0);
df_cfg.node_id_shift = FIELD_GET(DF3_NODE_ID_SHIFT, mask1);
df_cfg.socket_id_shift = FIELD_GET(DF3_SOCKET_ID_SHIFT, mask1);
df_cfg.socket_id_mask = FIELD_GET(DF3_SOCKET_ID_MASK, mask1);
df_cfg.die_id_mask = FIELD_GET(DF3_DIE_ID_MASK, mask1);
}
static void df3p5_get_masks_shifts(u32 mask0, u32 mask1, u32 mask2)
{
df_cfg.component_id_mask = FIELD_GET(DF4_COMPONENT_ID_MASK, mask0);
df_cfg.node_id_mask = FIELD_GET(DF4_NODE_ID_MASK, mask0);
df_cfg.node_id_shift = FIELD_GET(DF3_NODE_ID_SHIFT, mask1);
df_cfg.socket_id_shift = FIELD_GET(DF4_SOCKET_ID_SHIFT, mask1);
df_cfg.socket_id_mask = FIELD_GET(DF4_SOCKET_ID_MASK, mask2);
df_cfg.die_id_mask = FIELD_GET(DF4_DIE_ID_MASK, mask2);
}
static void df4_get_masks_shifts(u32 mask0, u32 mask1, u32 mask2)
{
df3p5_get_masks_shifts(mask0, mask1, mask2);
if (!(df_cfg.flags.socket_id_shift_quirk && df_cfg.socket_id_shift == 1))
return;
df_cfg.socket_id_shift = 0;
df_cfg.socket_id_mask = 1;
df_cfg.die_id_shift = 0;
df_cfg.die_id_mask = 0;
df_cfg.node_id_shift = 8;
df_cfg.node_id_mask = 0x100;
}
static int df4_get_fabric_id_mask_registers(void)
{
u32 mask0, mask1, mask2;
/* Read D18F4x1B0 (SystemFabricIdMask0) */
if (df_indirect_read_broadcast(0, 4, 0x1B0, &mask0))
return -EINVAL;
/* Read D18F4x1B4 (SystemFabricIdMask1) */
if (df_indirect_read_broadcast(0, 4, 0x1B4, &mask1))
return -EINVAL;
/* Read D18F4x1B8 (SystemFabricIdMask2) */
if (df_indirect_read_broadcast(0, 4, 0x1B8, &mask2))
return -EINVAL;
df4_get_masks_shifts(mask0, mask1, mask2);
return 0;
}
static int df4_determine_df_rev(u32 reg)
{
df_cfg.rev = FIELD_GET(DF_MINOR_REVISION, reg) < 5 ? DF4 : DF4p5;
/* Check for special cases or quirks based on Device/Vendor IDs.*/
/* Read D18F0x000 (DeviceVendorId0) */
if (df_indirect_read_broadcast(0, 0, 0, &reg))
return -EINVAL;
if (reg == DF_FUNC0_ID_ZEN4_SERVER)
df_cfg.flags.socket_id_shift_quirk = 1;
if (reg == DF_FUNC0_ID_MI300) {
df_cfg.flags.heterogeneous = 1;
if (get_umc_info_mi300())
return -EINVAL;
}
return df4_get_fabric_id_mask_registers();
}
static int determine_df_rev_legacy(void)
{
u32 fabric_id_mask0, fabric_id_mask1, fabric_id_mask2;
/*
* Check for DF3.5.
*
* Component ID Mask must be non-zero. Register D18F1x150 is
* reserved pre-DF3.5, so value will be Read-as-Zero.
*/
/* Read D18F1x150 (SystemFabricIdMask0). */
if (df_indirect_read_broadcast(0, 1, 0x150, &fabric_id_mask0))
return -EINVAL;
if (FIELD_GET(DF4_COMPONENT_ID_MASK, fabric_id_mask0)) {
df_cfg.rev = DF3p5;
/* Read D18F1x154 (SystemFabricIdMask1) */
if (df_indirect_read_broadcast(0, 1, 0x154, &fabric_id_mask1))
return -EINVAL;
/* Read D18F1x158 (SystemFabricIdMask2) */
if (df_indirect_read_broadcast(0, 1, 0x158, &fabric_id_mask2))
return -EINVAL;
df3p5_get_masks_shifts(fabric_id_mask0, fabric_id_mask1, fabric_id_mask2);
return 0;
}
/*
* Check for DF3.
*
* Component ID Mask must be non-zero. Field is Read-as-Zero on DF2.
*/
/* Read D18F1x208 (SystemFabricIdMask). */
if (df_indirect_read_broadcast(0, 1, 0x208, &fabric_id_mask0))
return -EINVAL;
if (FIELD_GET(DF3_COMPONENT_ID_MASK, fabric_id_mask0)) {
df_cfg.rev = DF3;
/* Read D18F1x20C (SystemFabricIdMask1) */
if (df_indirect_read_broadcast(0, 1, 0x20C, &fabric_id_mask1))
return -EINVAL;
df3_get_masks_shifts(fabric_id_mask0, fabric_id_mask1);
return 0;
}
/* Default to DF2. */
df_cfg.rev = DF2;
df2_get_masks_shifts(fabric_id_mask0);
return 0;
}
static int determine_df_rev(void)
{
u32 reg;
u8 rev;
if (df_cfg.rev != UNKNOWN)
return 0;
/* Read D18F0x40 (FabricBlockInstanceCount). */
if (df_indirect_read_broadcast(0, 0, 0x40, &reg))
return -EINVAL;
/*
* Revision fields added for DF4 and later.
*
* Major revision of '0' is found pre-DF4. Field is Read-as-Zero.
*/
rev = FIELD_GET(DF_MAJOR_REVISION, reg);
if (!rev)
return determine_df_rev_legacy();
if (rev == 4)
return df4_determine_df_rev(reg);
/* All other systems should have PRM handlers. */
if (!acpi_prm_handler_available(&norm_to_sys_guid)) {
pr_debug("PRM not available\n");
return -ENODEV;
}
df_cfg.flags.prm_only = true;
return 0;
}
static int get_dram_hole_base(void)
{
u8 func = 0;
if (df_cfg.rev >= DF4)
func = 7;
if (df_indirect_read_broadcast(0, func, 0x104, &df_cfg.dram_hole_base))
return -EINVAL;
df_cfg.dram_hole_base &= DF_DRAM_HOLE_BASE_MASK;
return 0;
}
static void get_num_maps(void)
{
switch (df_cfg.rev) {
case DF2:
case DF3:
case DF3p5:
df_cfg.num_coh_st_maps = 2;
break;
case DF4:
case DF4p5:
df_cfg.num_coh_st_maps = 4;
break;
default:
atl_debug_on_bad_df_rev();
}
}
static void apply_node_id_shift(void)
{
if (df_cfg.rev == DF2)
return;
df_cfg.die_id_shift = df_cfg.node_id_shift;
df_cfg.die_id_mask <<= df_cfg.node_id_shift;
df_cfg.socket_id_mask <<= df_cfg.node_id_shift;
df_cfg.socket_id_shift += df_cfg.node_id_shift;
}
static void dump_df_cfg(void)
{
pr_debug("rev=0x%x", df_cfg.rev);
pr_debug("component_id_mask=0x%x", df_cfg.component_id_mask);
pr_debug("die_id_mask=0x%x", df_cfg.die_id_mask);
pr_debug("node_id_mask=0x%x", df_cfg.node_id_mask);
pr_debug("socket_id_mask=0x%x", df_cfg.socket_id_mask);
pr_debug("die_id_shift=0x%x", df_cfg.die_id_shift);
pr_debug("node_id_shift=0x%x", df_cfg.node_id_shift);
pr_debug("socket_id_shift=0x%x", df_cfg.socket_id_shift);
pr_debug("num_coh_st_maps=%u", df_cfg.num_coh_st_maps);
pr_debug("dram_hole_base=0x%x", df_cfg.dram_hole_base);
pr_debug("flags.legacy_ficaa=%u", df_cfg.flags.legacy_ficaa);
pr_debug("flags.socket_id_shift_quirk=%u", df_cfg.flags.socket_id_shift_quirk);
}
int get_df_system_info(void)
{
int ret;
ret = determine_df_rev();
if (ret) {
pr_warn("Failed to determine DF Revision");
df_cfg.rev = UNKNOWN;
return ret;
}
if (df_cfg.flags.prm_only)
return 0;
apply_node_id_shift();
get_num_maps();
if (get_dram_hole_base())
pr_warn("Failed to read DRAM hole base");
dump_df_cfg();
return 0;
}
+418
View File
@@ -0,0 +1,418 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Address Translation Library
*
* umc.c : Unified Memory Controller (UMC) topology helpers
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
*/
#include "internal.h"
/*
* MI300 has a fixed, model-specific mapping between a UMC instance and
* its related Data Fabric Coherent Station instance.
*
* The MCA_IPID_UMC[InstanceId] field holds a unique identifier for the
* UMC instance within a Node. Use this to find the appropriate Coherent
* Station ID.
*
* Redundant bits were removed from the map below.
*/
static const u16 umc_coh_st_map[32] = {
0x393, 0x293, 0x193, 0x093,
0x392, 0x292, 0x192, 0x092,
0x391, 0x291, 0x191, 0x091,
0x390, 0x290, 0x190, 0x090,
0x793, 0x693, 0x593, 0x493,
0x792, 0x692, 0x592, 0x492,
0x791, 0x691, 0x591, 0x491,
0x790, 0x690, 0x590, 0x490,
};
#define UMC_ID_MI300 GENMASK(23, 12)
static u8 get_coh_st_inst_id_mi300(struct atl_err *err)
{
u16 umc_id = FIELD_GET(UMC_ID_MI300, err->ipid);
u8 i;
for (i = 0; i < ARRAY_SIZE(umc_coh_st_map); i++) {
if (umc_id == umc_coh_st_map[i])
break;
}
WARN_ON_ONCE(i >= ARRAY_SIZE(umc_coh_st_map));
return i;
}
struct xor_bits {
bool xor_enable;
u16 col_xor;
u32 row_xor;
};
#define NUM_BANK_BITS 4
#define NUM_COL_BITS 5
#define NUM_SID_BITS 2
static struct {
/* UMC::CH::AddrHashBank */
struct xor_bits bank[NUM_BANK_BITS];
/* UMC::CH::AddrHashPC */
struct xor_bits pc;
/* UMC::CH::AddrHashPC2 */
u8 bank_xor;
} addr_hash;
static struct {
u8 bank[NUM_BANK_BITS];
u8 col[NUM_COL_BITS];
u8 sid[NUM_SID_BITS];
u8 num_row_lo;
u8 num_row_hi;
u8 row_lo;
u8 row_hi;
u8 pc;
} bit_shifts;
#define MI300_UMC_CH_BASE 0x90000
#define MI300_ADDR_CFG (MI300_UMC_CH_BASE + 0x30)
#define MI300_ADDR_SEL (MI300_UMC_CH_BASE + 0x40)
#define MI300_COL_SEL_LO (MI300_UMC_CH_BASE + 0x50)
#define MI300_ADDR_SEL_2 (MI300_UMC_CH_BASE + 0xA4)
#define MI300_ADDR_HASH_BANK0 (MI300_UMC_CH_BASE + 0xC8)
#define MI300_ADDR_HASH_PC (MI300_UMC_CH_BASE + 0xE0)
#define MI300_ADDR_HASH_PC2 (MI300_UMC_CH_BASE + 0xE4)
#define ADDR_HASH_XOR_EN BIT(0)
#define ADDR_HASH_COL_XOR GENMASK(13, 1)
#define ADDR_HASH_ROW_XOR GENMASK(31, 14)
#define ADDR_HASH_BANK_XOR GENMASK(5, 0)
#define ADDR_CFG_NUM_ROW_LO GENMASK(11, 8)
#define ADDR_CFG_NUM_ROW_HI GENMASK(15, 12)
#define ADDR_SEL_BANK0 GENMASK(3, 0)
#define ADDR_SEL_BANK1 GENMASK(7, 4)
#define ADDR_SEL_BANK2 GENMASK(11, 8)
#define ADDR_SEL_BANK3 GENMASK(15, 12)
#define ADDR_SEL_BANK4 GENMASK(20, 16)
#define ADDR_SEL_ROW_LO GENMASK(27, 24)
#define ADDR_SEL_ROW_HI GENMASK(31, 28)
#define COL_SEL_LO_COL0 GENMASK(3, 0)
#define COL_SEL_LO_COL1 GENMASK(7, 4)
#define COL_SEL_LO_COL2 GENMASK(11, 8)
#define COL_SEL_LO_COL3 GENMASK(15, 12)
#define COL_SEL_LO_COL4 GENMASK(19, 16)
#define ADDR_SEL_2_BANK5 GENMASK(4, 0)
#define ADDR_SEL_2_CHAN GENMASK(15, 12)
/*
* Read UMC::CH::AddrHash{Bank,PC,PC2} registers to get XOR bits used
* for hashing.
*
* Also, read UMC::CH::Addr{Cfg,Sel,Sel2} and UMC::CH:ColSelLo registers to
* get the values needed to reconstruct the normalized address. Apply additional
* offsets to the raw register values, as needed.
*
* Do this during module init, since the values will not change during run time.
*
* These registers are instantiated for each UMC across each AMD Node.
* However, they should be identically programmed due to the fixed hardware
* design of MI300 systems. So read the values from Node 0 UMC 0 and keep a
* single global structure for simplicity.
*/
int get_umc_info_mi300(void)
{
u32 temp;
int ret;
u8 i;
for (i = 0; i < NUM_BANK_BITS; i++) {
ret = amd_smn_read(0, MI300_ADDR_HASH_BANK0 + (i * 4), &temp);
if (ret)
return ret;
addr_hash.bank[i].xor_enable = FIELD_GET(ADDR_HASH_XOR_EN, temp);
addr_hash.bank[i].col_xor = FIELD_GET(ADDR_HASH_COL_XOR, temp);
addr_hash.bank[i].row_xor = FIELD_GET(ADDR_HASH_ROW_XOR, temp);
}
ret = amd_smn_read(0, MI300_ADDR_HASH_PC, &temp);
if (ret)
return ret;
addr_hash.pc.xor_enable = FIELD_GET(ADDR_HASH_XOR_EN, temp);
addr_hash.pc.col_xor = FIELD_GET(ADDR_HASH_COL_XOR, temp);
addr_hash.pc.row_xor = FIELD_GET(ADDR_HASH_ROW_XOR, temp);
ret = amd_smn_read(0, MI300_ADDR_HASH_PC2, &temp);
if (ret)
return ret;
addr_hash.bank_xor = FIELD_GET(ADDR_HASH_BANK_XOR, temp);
ret = amd_smn_read(0, MI300_ADDR_CFG, &temp);
if (ret)
return ret;
bit_shifts.num_row_hi = FIELD_GET(ADDR_CFG_NUM_ROW_HI, temp);
bit_shifts.num_row_lo = 10 + FIELD_GET(ADDR_CFG_NUM_ROW_LO, temp);
ret = amd_smn_read(0, MI300_ADDR_SEL, &temp);
if (ret)
return ret;
bit_shifts.bank[0] = 5 + FIELD_GET(ADDR_SEL_BANK0, temp);
bit_shifts.bank[1] = 5 + FIELD_GET(ADDR_SEL_BANK1, temp);
bit_shifts.bank[2] = 5 + FIELD_GET(ADDR_SEL_BANK2, temp);
bit_shifts.bank[3] = 5 + FIELD_GET(ADDR_SEL_BANK3, temp);
/* Use BankBit4 for the SID0 position. */
bit_shifts.sid[0] = 5 + FIELD_GET(ADDR_SEL_BANK4, temp);
bit_shifts.row_lo = 12 + FIELD_GET(ADDR_SEL_ROW_LO, temp);
bit_shifts.row_hi = 24 + FIELD_GET(ADDR_SEL_ROW_HI, temp);
ret = amd_smn_read(0, MI300_COL_SEL_LO, &temp);
if (ret)
return ret;
bit_shifts.col[0] = 2 + FIELD_GET(COL_SEL_LO_COL0, temp);
bit_shifts.col[1] = 2 + FIELD_GET(COL_SEL_LO_COL1, temp);
bit_shifts.col[2] = 2 + FIELD_GET(COL_SEL_LO_COL2, temp);
bit_shifts.col[3] = 2 + FIELD_GET(COL_SEL_LO_COL3, temp);
bit_shifts.col[4] = 2 + FIELD_GET(COL_SEL_LO_COL4, temp);
ret = amd_smn_read(0, MI300_ADDR_SEL_2, &temp);
if (ret)
return ret;
/* Use BankBit5 for the SID1 position. */
bit_shifts.sid[1] = 5 + FIELD_GET(ADDR_SEL_2_BANK5, temp);
bit_shifts.pc = 5 + FIELD_GET(ADDR_SEL_2_CHAN, temp);
return 0;
}
/*
* MI300 systems report a DRAM address in MCA_ADDR for DRAM ECC errors. This must
* be converted to the intermediate normalized address (NA) before translating to a
* system physical address.
*
* The DRAM address includes bank, row, and column. Also included are bits for
* pseudochannel (PC) and stack ID (SID).
*
* Abbreviations: (S)tack ID, (P)seudochannel, (R)ow, (B)ank, (C)olumn, (Z)ero
*
* The MCA address format is as follows:
* MCA_ADDR[27:0] = {S[1:0], P[0], R[14:0], B[3:0], C[4:0], Z[0]}
*
* Additionally, the PC and Bank bits may be hashed. This must be accounted for before
* reconstructing the normalized address.
*/
#define MI300_UMC_MCA_BANK GENMASK(9, 6)
#define MI300_UMC_MCA_ROW GENMASK(24, 10)
#define MI300_UMC_MCA_PC BIT(25)
#define MI300_UMC_MCA_SID GENMASK(27, 26)
static unsigned long convert_dram_to_norm_addr_mi300(unsigned long addr)
{
u16 i, col, row, bank, pc, sid;
u32 temp;
col = FIELD_GET(MI300_UMC_MCA_COL, addr);
bank = FIELD_GET(MI300_UMC_MCA_BANK, addr);
row = FIELD_GET(MI300_UMC_MCA_ROW, addr);
pc = FIELD_GET(MI300_UMC_MCA_PC, addr);
sid = FIELD_GET(MI300_UMC_MCA_SID, addr);
/* Calculate hash for each Bank bit. */
for (i = 0; i < NUM_BANK_BITS; i++) {
if (!addr_hash.bank[i].xor_enable)
continue;
temp = hweight16(col & addr_hash.bank[i].col_xor) & 1;
temp ^= hweight16(row & addr_hash.bank[i].row_xor) & 1;
bank ^= temp << i;
}
/* Calculate hash for PC bit. */
if (addr_hash.pc.xor_enable) {
temp = hweight16(col & addr_hash.pc.col_xor) & 1;
temp ^= hweight16(row & addr_hash.pc.row_xor) & 1;
/* Bits SID[1:0] act as Bank[5:4] for PC hash, so apply them here. */
temp ^= hweight16((bank | sid << NUM_BANK_BITS) & addr_hash.bank_xor) & 1;
pc ^= temp;
}
/* Reconstruct the normalized address starting with NA[4:0] = 0 */
addr = 0;
/* Column bits */
for (i = 0; i < NUM_COL_BITS; i++) {
temp = (col >> i) & 0x1;
addr |= temp << bit_shifts.col[i];
}
/* Bank bits */
for (i = 0; i < NUM_BANK_BITS; i++) {
temp = (bank >> i) & 0x1;
addr |= temp << bit_shifts.bank[i];
}
/* Row lo bits */
for (i = 0; i < bit_shifts.num_row_lo; i++) {
temp = (row >> i) & 0x1;
addr |= temp << (i + bit_shifts.row_lo);
}
/* Row hi bits */
for (i = 0; i < bit_shifts.num_row_hi; i++) {
temp = (row >> (i + bit_shifts.num_row_lo)) & 0x1;
addr |= temp << (i + bit_shifts.row_hi);
}
/* PC bit */
addr |= pc << bit_shifts.pc;
/* SID bits */
for (i = 0; i < NUM_SID_BITS; i++) {
temp = (sid >> i) & 0x1;
addr |= temp << bit_shifts.sid[i];
}
pr_debug("Addr=0x%016lx", addr);
pr_debug("Bank=%u Row=%u Column=%u PC=%u SID=%u", bank, row, col, pc, sid);
return addr;
}
/*
* When a DRAM ECC error occurs on MI300 systems, it is recommended to retire
* all memory within that DRAM row. This applies to the memory with a DRAM
* bank.
*
* To find the memory addresses, loop through permutations of the DRAM column
* bits and find the System Physical address of each. The column bits are used
* to calculate the intermediate Normalized address, so all permutations should
* be checked.
*
* See amd_atl::convert_dram_to_norm_addr_mi300() for MI300 address formats.
*/
#define MI300_NUM_COL BIT(HWEIGHT(MI300_UMC_MCA_COL))
static void _retire_row_mi300(struct atl_err *a_err)
{
unsigned long addr;
struct page *p;
u8 col;
for (col = 0; col < MI300_NUM_COL; col++) {
a_err->addr &= ~MI300_UMC_MCA_COL;
a_err->addr |= FIELD_PREP(MI300_UMC_MCA_COL, col);
addr = amd_convert_umc_mca_addr_to_sys_addr(a_err);
if (IS_ERR_VALUE(addr))
continue;
addr = PHYS_PFN(addr);
/*
* Skip invalid or already poisoned pages to avoid unnecessary
* error messages from memory_failure().
*/
p = pfn_to_online_page(addr);
if (!p)
continue;
if (PageHWPoison(p))
continue;
memory_failure(addr, 0);
}
}
/*
* In addition to the column bits, the row[13] bit should also be included when
* calculating addresses affected by a physical row.
*
* Instead of running through another loop over a single bit, just run through
* the column bits twice and flip the row[13] bit in-between.
*
* See MI300_UMC_MCA_ROW for the row bits in MCA_ADDR_UMC value.
*/
static void retire_row_mi300(struct atl_err *a_err)
{
_retire_row_mi300(a_err);
a_err->addr ^= MI300_UMC_MCA_ROW13;
_retire_row_mi300(a_err);
}
void amd_retire_dram_row(struct atl_err *a_err)
{
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous)
return retire_row_mi300(a_err);
}
EXPORT_SYMBOL_GPL(amd_retire_dram_row);
static unsigned long get_addr(unsigned long addr)
{
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous)
return convert_dram_to_norm_addr_mi300(addr);
return addr;
}
#define MCA_IPID_INST_ID_HI GENMASK_ULL(47, 44)
static u8 get_die_id(struct atl_err *err)
{
/*
* AMD Node ID is provided in MCA_IPID[InstanceIdHi], and this
* needs to be divided by 4 to get the internal Die ID.
*/
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous) {
u8 node_id = FIELD_GET(MCA_IPID_INST_ID_HI, err->ipid);
return node_id >> 2;
}
/*
* For CPUs, this is the AMD Node ID modulo the number
* of AMD Nodes per socket.
*/
return topology_amd_node_id(err->cpu) % topology_amd_nodes_per_pkg();
}
#define UMC_CHANNEL_NUM GENMASK(31, 20)
static u8 get_coh_st_inst_id(struct atl_err *err)
{
if (df_cfg.rev == DF4p5 && df_cfg.flags.heterogeneous)
return get_coh_st_inst_id_mi300(err);
return FIELD_GET(UMC_CHANNEL_NUM, err->ipid);
}
unsigned long convert_umc_mca_addr_to_sys_addr(struct atl_err *err)
{
u8 socket_id = topology_physical_package_id(err->cpu);
u8 coh_st_inst_id = get_coh_st_inst_id(err);
unsigned long addr = get_addr(err->addr);
u8 die_id = get_die_id(err);
unsigned long ret_addr;
pr_debug("socket_id=0x%x die_id=0x%x coh_st_inst_id=0x%x addr=0x%016lx",
socket_id, die_id, coh_st_inst_id, addr);
ret_addr = prm_umc_norm_to_sys_addr(socket_id, err->ipid, addr);
if (!IS_ERR_VALUE(ret_addr) || df_cfg.flags.prm_only)
return ret_addr;
return norm_to_sys_addr(socket_id, die_id, coh_st_inst_id, addr);
}
File diff suppressed because it is too large Load Diff