xref: /linux/drivers/ras/amd/atl/access.c (revision 1f20a5769446a1acae67ac9e63d07a594829a789)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * AMD Address Translation Library
4  *
5  * access.c : DF Indirect Access functions
6  *
7  * Copyright (c) 2023, Advanced Micro Devices, Inc.
8  * All Rights Reserved.
9  *
10  * Author: Yazen Ghannam <Yazen.Ghannam@amd.com>
11  */
12 
13 #include "internal.h"
14 
15 /* Protect the PCI config register pairs used for DF indirect access. */
16 static DEFINE_MUTEX(df_indirect_mutex);
17 
18 /*
19  * Data Fabric Indirect Access uses FICAA/FICAD.
20  *
21  * Fabric Indirect Configuration Access Address (FICAA): constructed based
22  * on the device's Instance Id and the PCI function and register offset of
23  * the desired register.
24  *
25  * Fabric Indirect Configuration Access Data (FICAD): there are FICAD
26  * low and high registers but so far only the low register is needed.
27  *
28  * Use Instance Id 0xFF to indicate a broadcast read.
29  */
30 #define DF_BROADCAST		0xFF
31 
32 #define DF_FICAA_INST_EN	BIT(0)
33 #define DF_FICAA_REG_NUM	GENMASK(10, 1)
34 #define DF_FICAA_FUNC_NUM	GENMASK(13, 11)
35 #define DF_FICAA_INST_ID	GENMASK(23, 16)
36 
37 #define DF_FICAA_REG_NUM_LEGACY	GENMASK(10, 2)
38 
39 static u16 get_accessible_node(u16 node)
40 {
41 	/*
42 	 * On heterogeneous systems, not all AMD Nodes are accessible
43 	 * through software-visible registers. The Node ID needs to be
44 	 * adjusted for register accesses. But its value should not be
45 	 * changed for the translation methods.
46 	 */
47 	if (df_cfg.flags.heterogeneous) {
48 		/* Only Node 0 is accessible on DF3.5 systems. */
49 		if (df_cfg.rev == DF3p5)
50 			node = 0;
51 
52 		/*
53 		 * Only the first Node in each Socket is accessible on
54 		 * DF4.5 systems, and this is visible to software as one
55 		 * Fabric per Socket.  The Socket ID can be derived from
56 		 * the Node ID and global shift values.
57 		 */
58 		if (df_cfg.rev == DF4p5)
59 			node >>= df_cfg.socket_id_shift - df_cfg.node_id_shift;
60 	}
61 
62 	return node;
63 }
64 
65 static int __df_indirect_read(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
66 {
67 	u32 ficaa_addr = 0x8C, ficad_addr = 0xB8;
68 	struct pci_dev *F4;
69 	int err = -ENODEV;
70 	u32 ficaa = 0;
71 
72 	node = get_accessible_node(node);
73 	if (node >= amd_nb_num())
74 		goto out;
75 
76 	F4 = node_to_amd_nb(node)->link;
77 	if (!F4)
78 		goto out;
79 
80 	/* Enable instance-specific access. */
81 	if (instance_id != DF_BROADCAST) {
82 		ficaa |= FIELD_PREP(DF_FICAA_INST_EN, 1);
83 		ficaa |= FIELD_PREP(DF_FICAA_INST_ID, instance_id);
84 	}
85 
86 	/*
87 	 * The two least-significant bits are masked when inputing the
88 	 * register offset to FICAA.
89 	 */
90 	reg >>= 2;
91 
92 	if (df_cfg.flags.legacy_ficaa) {
93 		ficaa_addr = 0x5C;
94 		ficad_addr = 0x98;
95 
96 		ficaa |= FIELD_PREP(DF_FICAA_REG_NUM_LEGACY, reg);
97 	} else {
98 		ficaa |= FIELD_PREP(DF_FICAA_REG_NUM, reg);
99 	}
100 
101 	ficaa |= FIELD_PREP(DF_FICAA_FUNC_NUM, func);
102 
103 	mutex_lock(&df_indirect_mutex);
104 
105 	err = pci_write_config_dword(F4, ficaa_addr, ficaa);
106 	if (err) {
107 		pr_warn("Error writing DF Indirect FICAA, FICAA=0x%x\n", ficaa);
108 		goto out_unlock;
109 	}
110 
111 	err = pci_read_config_dword(F4, ficad_addr, lo);
112 	if (err)
113 		pr_warn("Error reading DF Indirect FICAD LO, FICAA=0x%x.\n", ficaa);
114 
115 	pr_debug("node=%u inst=0x%x func=0x%x reg=0x%x val=0x%x",
116 		 node, instance_id, func, reg << 2, *lo);
117 
118 out_unlock:
119 	mutex_unlock(&df_indirect_mutex);
120 
121 out:
122 	return err;
123 }
124 
125 int df_indirect_read_instance(u16 node, u8 func, u16 reg, u8 instance_id, u32 *lo)
126 {
127 	return __df_indirect_read(node, func, reg, instance_id, lo);
128 }
129 
130 int df_indirect_read_broadcast(u16 node, u8 func, u16 reg, u32 *lo)
131 {
132 	return __df_indirect_read(node, func, reg, DF_BROADCAST, lo);
133 }
134