| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- // 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");
|