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 <Yazen.Ghannam@amd.com> 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 u64 add_legacy_hole(struct addr_ctx *ctx, u64 addr) 53 { 54 if (!legacy_hole_en(ctx)) 55 return addr; 56 57 if (addr >= df_cfg.dram_hole_base) 58 addr += (BIT_ULL(32) - df_cfg.dram_hole_base); 59 60 return addr; 61 } 62 63 static u64 remove_legacy_hole(struct addr_ctx *ctx, u64 addr) 64 { 65 if (!legacy_hole_en(ctx)) 66 return addr; 67 68 if (addr >= df_cfg.dram_hole_base) 69 addr -= (BIT_ULL(32) - df_cfg.dram_hole_base); 70 71 return addr; 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 u64 add_base_and_hole(struct addr_ctx *ctx, u64 addr) 87 { 88 return add_legacy_hole(ctx, addr + get_base_addr(ctx)); 89 } 90 91 u64 remove_base_and_hole(struct addr_ctx *ctx, u64 addr) 92 { 93 return remove_legacy_hole(ctx, addr) - get_base_addr(ctx); 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 (legacy_hole_en(&ctx) && !df_cfg.dram_hole_base) 129 return -EINVAL; 130 131 if (determine_node_id(&ctx, socket_id, die_id)) 132 return -EINVAL; 133 134 if (get_address_map(&ctx)) 135 return -EINVAL; 136 137 if (denormalize_address(&ctx)) 138 return -EINVAL; 139 140 if (!late_hole_remove(&ctx)) 141 ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr); 142 143 if (dehash_address(&ctx)) 144 return -EINVAL; 145 146 if (late_hole_remove(&ctx)) 147 ctx.ret_addr = add_base_and_hole(&ctx, ctx.ret_addr); 148 149 if (addr_over_limit(&ctx)) 150 return -EINVAL; 151 152 return ctx.ret_addr; 153 } 154 155 static void check_for_legacy_df_access(void) 156 { 157 /* 158 * All Zen-based systems before Family 19h use the legacy 159 * DF Indirect Access (FICAA/FICAD) offsets. 160 */ 161 if (boot_cpu_data.x86 < 0x19) { 162 df_cfg.flags.legacy_ficaa = true; 163 return; 164 } 165 166 /* All systems after Family 19h use the current offsets. */ 167 if (boot_cpu_data.x86 > 0x19) 168 return; 169 170 /* Some Family 19h systems use the legacy offsets. */ 171 switch (boot_cpu_data.x86_model) { 172 case 0x00 ... 0x0f: 173 case 0x20 ... 0x5f: 174 df_cfg.flags.legacy_ficaa = true; 175 } 176 } 177 178 /* 179 * This library provides functionality for AMD-based systems with a Data Fabric. 180 * The set of systems with a Data Fabric is equivalent to the set of Zen-based systems 181 * and the set of systems with the Scalable MCA feature at this time. However, these 182 * are technically independent things. 183 * 184 * It's possible to match on the PCI IDs of the Data Fabric devices, but this will be 185 * an ever expanding list. Instead, match on the SMCA and Zen features to cover all 186 * relevant systems. 187 */ 188 static const struct x86_cpu_id amd_atl_cpuids[] = { 189 X86_MATCH_FEATURE(X86_FEATURE_SMCA, NULL), 190 X86_MATCH_FEATURE(X86_FEATURE_ZEN, NULL), 191 { } 192 }; 193 MODULE_DEVICE_TABLE(x86cpu, amd_atl_cpuids); 194 195 static int __init amd_atl_init(void) 196 { 197 if (!x86_match_cpu(amd_atl_cpuids)) 198 return -ENODEV; 199 200 if (!amd_nb_num()) 201 return -ENODEV; 202 203 check_for_legacy_df_access(); 204 205 if (get_df_system_info()) 206 return -ENODEV; 207 208 /* Increment this module's recount so that it can't be easily unloaded. */ 209 __module_get(THIS_MODULE); 210 amd_atl_register_decoder(convert_umc_mca_addr_to_sys_addr); 211 212 pr_info("AMD Address Translation Library initialized\n"); 213 return 0; 214 } 215 216 /* 217 * Exit function is only needed for testing and debug. Module unload must be 218 * forced to override refcount check. 219 */ 220 static void __exit amd_atl_exit(void) 221 { 222 amd_atl_unregister_decoder(); 223 } 224 225 module_init(amd_atl_init); 226 module_exit(amd_atl_exit); 227 228 MODULE_DESCRIPTION("AMD Address Translation Library"); 229 MODULE_LICENSE("GPL"); 230