1 /* $OpenBSD: amas.c,v 1.7 2022/03/11 18:00:45 mpi Exp $ */
2
3 /*
4 * Copyright (c) 2009 Ariane van der Steldt <ariane@stack.nl>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 /*
20 * Device: amas (AMD memory access/address switch).
21 *
22 * Driver for the amd athlon/opteron 64 address map.
23 * This device is integrated in 64-bit Athlon and Opteron cpus
24 * and contains mappings for memory to processor nodes.
25 */
26
27 #include <dev/pci/amas.h>
28
29 #include <sys/param.h>
30 #include <sys/systm.h>
31 #include <sys/device.h>
32
33 #include <dev/pci/pcivar.h>
34 #include <dev/pci/pcireg.h>
35 #include <dev/pci/pcidevs.h>
36
37 int amas_match(struct device*, void*, void*);
38 void amas_attach(struct device*, struct device*, void*);
39
40 /*
41 * Amas device layout:
42 *
43 * - base/limit registers (on 0x0f, 0x10, 0x11)
44 * - extended base/limit registers (on 0x10)
45 *
46 * 0x0f, 0x10 support up to 8 nodes
47 * 0x11 supports up to 1 nodes
48 *
49 * base/limit registers use bits [31..16] to indicate address [39..24]
50 * extended base/limit registers use bits [7..0] to indicate address [47..40]
51 * base/limit addresses need to be shifted <<24 for memory address
52 * extended base/limit addresses need to be shifted <<40 for memory address
53 */
54
55 #define AMAS_REG_BASE(node) (0x0040 + 0x08 * (node))
56 #define AMAS_REG_LIMIT(node) (0x0044 + 0x08 * (node))
57 #define AMAS_REG_EXTBASE(node) (0x0140 + 0x08 * (node))
58 #define AMAS_REG_EXTLIMIT(node) (0x0144 + 0x08 * (node))
59
60 #define AMAS_REG_BL_ADDR(reg) (((reg) >> 16) & 0xffff)
61 #define AMAS_REG_EBL_ADDR(ereg) ((ereg) & 0xff)
62
63 #define AMAS_REG_BL_SHIFT (24)
64 #define AMAS_REG_EBL_SHIFT (40)
65
66 #define AMAS_REG_BL_PGSHIFT (AMAS_REG_BL_SHIFT - PAGE_SHIFT)
67 #define AMAS_REG_EBL_PGSHIFT (AMAS_REG_EBL_SHIFT - PAGE_SHIFT)
68
69 /*
70 * Convert an address in amas to a page number.
71 *
72 * The device uses an inclusive mapping, where the upper bound address
73 * must be all 1's after shifting.
74 * The device driver uses C-style array indices, hence the +1 in the _LIMIT
75 * macro.
76 */
77 #define AMAS_ADDR2PAGE_BASE(base, ebase) \
78 (((base) << AMAS_REG_BL_PGSHIFT) | ((ebase) << AMAS_REG_EBL_PGSHIFT))
79 #define AMAS_ADDR2PAGE_LIMIT(base, ebase) \
80 (((base + 1) << AMAS_REG_BL_PGSHIFT) | ((ebase) << AMAS_REG_EBL_PGSHIFT))
81
82 /*
83 * Node and interleave description.
84 * - base contains node selection [10..8] (on 0x0f, 0x10)
85 * - limit contains node selection bitmask [10..8] (on 0x0f, 0x10)
86 * - limit contains destination node [2..0] (on 0x0f, 0x10)
87 */
88 #define AMAS_DST_NODE(base, limit) ((limit) & 0x07)
89 #define AMAS_INTL_ENABLE(base, limit) (((base) >> 8) & 0x07)
90 #define AMAS_INTL_SELECTOR(base, limit) (((limit) >> 8) & 0x07)
91
92 /*
93 * Defines for family.
94 * Corresponds to the amas_feature[] constant below.
95 */
96 #define AMAS_FAM_0Fh (0)
97 #define AMAS_FAM_10h (1)
98 #define AMAS_FAM_11h (2)
99
100 /*
101 * Feature tests.
102 *
103 * 0x11 supports at max 1 node, 0x0f and 0x10 support up to 8 nodes.
104 * 0x11 has extended address registers.
105 * 0x0f, 0x10 can interleave memory.
106 */
107 struct amas_feature_t {
108 int maxnodes;
109 int can_intl;
110 int has_extended_bl;
111 };
112 static const struct amas_feature_t amas_feature[] = {
113 /* Family 0x0f */
114 { 8, 1, 0 },
115 /* Family 0x10 */
116 { 8, 1, 1 },
117 /* Family 0x11 */
118 { 1, 0, 0 },
119 };
120
121 /* Probe code. */
122 const struct cfattach amas_ca = {
123 sizeof(struct amas_softc),
124 amas_match,
125 amas_attach
126 };
127
128 struct cfdriver amas_cd = {
129 NULL,
130 "amas",
131 DV_DULL
132 };
133
134 const struct pci_matchid amas_devices[] = {
135 { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_0F_ADDR },
136 { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_10_ADDR },
137 { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_11_ADDR },
138 };
139
140 int
amas_match(struct device * parent,void * match,void * aux)141 amas_match(struct device *parent, void *match, void *aux)
142 {
143 struct pci_attach_args* pa = aux;
144
145 if (pci_matchbyid(pa, amas_devices, nitems(amas_devices)))
146 return 2; /* override pchb */
147 return 0;
148 }
149
150 void
amas_attach(struct device * parent,struct device * self,void * aux)151 amas_attach(struct device *parent, struct device *self, void *aux)
152 {
153 struct pci_attach_args *pa = aux;
154 struct amas_softc *amas = (struct amas_softc*)self;
155 #ifdef DEBUG
156 paddr_t start_pg, end_pg;
157 int nodes, i;
158 #endif /* DEBUG */
159
160 amas->pa_tag = pa->pa_tag;
161 amas->pa_pc = pa->pa_pc;
162
163 switch (PCI_PRODUCT(pa->pa_id)) {
164 case PCI_PRODUCT_AMD_0F_ADDR:
165 amas->family = AMAS_FAM_0Fh;
166 break;
167 case PCI_PRODUCT_AMD_10_ADDR:
168 amas->family = AMAS_FAM_10h;
169 break;
170 case PCI_PRODUCT_AMD_11_ADDR:
171 amas->family = AMAS_FAM_11h;
172 break;
173 }
174
175 #ifdef DEBUG
176 nodes = amas_intl_nodes(amas);
177
178 printf(":");
179 if (nodes != 0) {
180 printf(" interleaved");
181 } else {
182 for (i = 0; i < AMAS_MAX_NODES; i++) {
183 amas_get_pagerange(amas, i, &start_pg, &end_pg);
184
185 if (!(start_pg == 0 && end_pg == 0))
186 printf(" [%#lx, %#lx]", start_pg, end_pg);
187 }
188 }
189 #endif /* DEBUG */
190 printf("\n");
191
192 return;
193 }
194
195 /*
196 * Returns the number of nodes across which the memory is interleaved.
197 * Returns 0 if the memory is not interleaved.
198 */
199 int
amas_intl_nodes(struct amas_softc * amas)200 amas_intl_nodes(struct amas_softc *amas)
201 {
202 pcireg_t base_reg, limit_reg;
203 int mask;
204
205 if (!amas_feature[amas->family].can_intl)
206 return 0;
207
208 /*
209 * Use node 0 on amas device to find interleave information.
210 * Node 0 is always present.
211 */
212
213 base_reg = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_BASE(0));
214 limit_reg = pci_conf_read(amas->pa_pc, amas->pa_tag, AMAS_REG_LIMIT(0));
215 mask = AMAS_INTL_ENABLE(base_reg, limit_reg);
216
217 return mask == 0 ? 0 : mask + 1;
218 }
219
220 /*
221 * Returns the range of memory that is contained on the given node.
222 * If the memory is interleaved, the result is undefined.
223 *
224 * The range is written in {start,end}_pg_idx.
225 * Note that these are page numbers and that these use array indices:
226 * pages are in this range if start <= pg_no < end.
227 *
228 * This device supports at most 8 nodes.
229 */
230 void
amas_get_pagerange(struct amas_softc * amas,int node,paddr_t * start_pg_idx,paddr_t * end_pg_idx)231 amas_get_pagerange(struct amas_softc *amas, int node,
232 paddr_t *start_pg_idx, paddr_t *end_pg_idx)
233 {
234 pcireg_t base, ebase, limit, elimit;
235 paddr_t base_addr, ebase_addr, limit_addr, elimit_addr;
236
237 /* Sanity check: max AMAS_MAX_NODES supported. */
238 KASSERT(node >= 0 && node < AMAS_MAX_NODES);
239
240 if (node >= amas_feature[amas->family].maxnodes) {
241 /* Unsupported node: bail out early. */
242 *start_pg_idx = 0;
243 *end_pg_idx = 0;
244 return;
245 }
246
247 base = pci_conf_read(amas->pa_pc, amas->pa_tag,
248 AMAS_REG_BASE(node));
249 limit = pci_conf_read(amas->pa_pc, amas->pa_tag,
250 AMAS_REG_LIMIT(node));
251 base_addr = AMAS_REG_BL_ADDR(base);
252 limit_addr = AMAS_REG_BL_ADDR(limit);
253
254 ebase = 0;
255 elimit = 0;
256 ebase_addr = 0;
257 elimit_addr = 0;
258 #if 0 /* Needs extended pci registers. */
259 if (amas_feature[amas->family].has_extended_bl) {
260 ebase = pci_conf_read(amas->pa_pc, amas->pa_tag,
261 AMAS_REG_EXTBASE(node));
262 elimit = pci_conf_read(amas->pa_pc, amas->pa_tag,
263 AMAS_REG_EXTLIMIT(node));
264 ebase_addr = AMAS_REG_EBL_ADDR(ebase);
265 elimit_addr = AMAS_REG_EBL_ADDR(elimit);
266 }
267 #endif /* 0 */
268
269 if (ebase_addr > elimit_addr ||
270 (ebase_addr == elimit_addr && base_addr >= limit_addr)) {
271 /* no memory present */
272 *start_pg_idx = 0;
273 *end_pg_idx = 0;
274 return;
275 }
276
277 /* Guaranteed by spec. */
278 KASSERT(node == AMAS_DST_NODE(base, limit));
279
280 *start_pg_idx = AMAS_ADDR2PAGE_BASE(base_addr, ebase_addr);
281 *end_pg_idx = AMAS_ADDR2PAGE_LIMIT(limit_addr, elimit_addr);
282 return;
283 }
284