xref: /openbsd/sys/dev/pci/amas.c (revision 771fbea0)
1 /*	$OpenBSD: amas.c,v 1.6 2020/01/04 01:34:24 jsg 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 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
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
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
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
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