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