xref: /freebsd/sys/dev/nvdimm/nvdimm_e820.c (revision 315ee00f)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2019 Dell EMC Isilon
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include <sys/param.h>
30 #include <sys/bio.h>
31 #include <sys/bitstring.h>
32 #include <sys/bus.h>
33 #include <sys/efi.h>
34 #include <sys/kernel.h>
35 #include <sys/linker.h>
36 #include <sys/lock.h>
37 #include <sys/malloc.h>
38 #include <sys/module.h>
39 #include <sys/sbuf.h>
40 #include <sys/uuid.h>
41 
42 #include <vm/vm_param.h>
43 
44 #include <machine/metadata.h>
45 #include <machine/pc/bios.h>
46 
47 #include <contrib/dev/acpica/include/acpi.h>
48 
49 #include <dev/nvdimm/nvdimm_var.h>
50 
51 struct nvdimm_e820_bus {
52 	SLIST_HEAD(, SPA_mapping) spas;
53 };
54 
55 #define	NVDIMM_E820	"nvdimm_e820"
56 
57 static MALLOC_DEFINE(M_NVDIMM_E820, NVDIMM_E820, "NVDIMM e820 bus memory");
58 
59 static const struct bios_smap *smapbase;
60 static struct {
61 	vm_paddr_t start;
62 	vm_paddr_t size;
63 } pram_segments[VM_PHYSSEG_MAX];
64 static unsigned pram_nreg;
65 
66 static void
67 nvdimm_e820_dump_prams(device_t dev, const char *func, int hintunit)
68 {
69 	char buffer[256];
70 	struct sbuf sb;
71 	bool printed = false;
72 	unsigned i;
73 
74 	sbuf_new(&sb, buffer, sizeof(buffer), SBUF_FIXEDLEN);
75 	sbuf_set_drain(&sb, sbuf_printf_drain, NULL);
76 
77 	sbuf_printf(&sb, "%s: %s: ", device_get_nameunit(dev), func);
78 	if (hintunit < 0)
79 		sbuf_cat(&sb, "Found BIOS PRAM regions: ");
80 	else
81 		sbuf_printf(&sb, "Remaining unallocated PRAM regions after "
82 		    "hint %d: ", hintunit);
83 
84 	for (i = 0; i < pram_nreg; i++) {
85 		if (pram_segments[i].size == 0)
86 			continue;
87 		if (printed)
88 			sbuf_putc(&sb, ',');
89 		else
90 			printed = true;
91 		sbuf_printf(&sb, "0x%jx-0x%jx",
92 		    (uintmax_t)pram_segments[i].start,
93 		    (uintmax_t)pram_segments[i].start + pram_segments[i].size
94 		    - 1);
95 	}
96 
97 	if (!printed)
98 		sbuf_cat(&sb, "<none>");
99 	sbuf_putc(&sb, '\n');
100 	sbuf_finish(&sb);
101 	sbuf_delete(&sb);
102 }
103 
104 static int
105 nvdimm_e820_create_spas(device_t dev)
106 {
107 	static const vm_size_t HINT_ALL = (vm_size_t)-1;
108 
109 	ACPI_NFIT_SYSTEM_ADDRESS nfit_sa;
110 	struct SPA_mapping *spa_mapping;
111 	enum SPA_mapping_type spa_type;
112 	struct nvdimm_e820_bus *sc;
113 	const char *hinttype;
114 	long hintaddrl, hintsizel;
115 	vm_paddr_t hintaddr;
116 	vm_size_t hintsize;
117 	unsigned i, j;
118 	int error;
119 
120 	sc = device_get_softc(dev);
121 	error = 0;
122 	nfit_sa = (ACPI_NFIT_SYSTEM_ADDRESS) { 0 };
123 
124 	if (bootverbose)
125 		nvdimm_e820_dump_prams(dev, __func__, -1);
126 
127 	for (i = 0;
128 	    resource_long_value("nvdimm_spa", i, "maddr", &hintaddrl) == 0;
129 	    i++) {
130 		if (resource_long_value("nvdimm_spa", i, "msize", &hintsizel)
131 		    != 0) {
132 			device_printf(dev, "hint.nvdimm_spa.%u missing msize\n",
133 			    i);
134 			continue;
135 		}
136 
137 		hintaddr = (vm_paddr_t)hintaddrl;
138 		hintsize = (vm_size_t)hintsizel;
139 		if ((hintaddr & PAGE_MASK) != 0 ||
140 		    ((hintsize & PAGE_MASK) != 0 && hintsize != HINT_ALL)) {
141 			device_printf(dev, "hint.nvdimm_spa.%u addr or size "
142 			    "not page aligned\n", i);
143 			continue;
144 		}
145 
146 		if (resource_string_value("nvdimm_spa", i, "type", &hinttype)
147 		    != 0) {
148 			device_printf(dev, "hint.nvdimm_spa.%u missing type\n",
149 			    i);
150 			continue;
151 		}
152 		spa_type = nvdimm_spa_type_from_name(hinttype);
153 		if (spa_type == SPA_TYPE_UNKNOWN) {
154 			device_printf(dev, "hint.nvdimm_spa%u.type does not "
155 			    "match any known SPA types\n", i);
156 			continue;
157 		}
158 
159 		for (j = 0; j < pram_nreg; j++) {
160 			if (pram_segments[j].start <= hintaddr &&
161 			    (hintsize == HINT_ALL ||
162 			    (pram_segments[j].start + pram_segments[j].size) >=
163 			    (hintaddr + hintsize)))
164 				break;
165 		}
166 
167 		if (j == pram_nreg) {
168 			device_printf(dev, "hint.nvdimm_spa%u hint does not "
169 			    "match any region\n", i);
170 			continue;
171 		}
172 
173 		/* Carve off "SPA" from available regions. */
174 		if (pram_segments[j].start == hintaddr) {
175 			/* Easy case first: beginning of segment. */
176 			if (hintsize == HINT_ALL)
177 				hintsize = pram_segments[j].size;
178 			pram_segments[j].start += hintsize;
179 			pram_segments[j].size -= hintsize;
180 			/* We might leave an empty segment; who cares. */
181 		} else if (hintsize == HINT_ALL ||
182 		    (pram_segments[j].start + pram_segments[j].size) ==
183 		    (hintaddr + hintsize)) {
184 			/* 2nd easy case: end of segment. */
185 			if (hintsize == HINT_ALL)
186 				hintsize = pram_segments[j].size -
187 				    (hintaddr - pram_segments[j].start);
188 			pram_segments[j].size -= hintsize;
189 		} else {
190 			/* Hard case: mid segment. */
191 			if (pram_nreg == nitems(pram_segments)) {
192 				/* Improbable, but handle gracefully. */
193 				device_printf(dev, "Ran out of %zu segments\n",
194 				    nitems(pram_segments));
195 				error = ENOBUFS;
196 				break;
197 			}
198 
199 			if (j != pram_nreg - 1) {
200 				memmove(&pram_segments[j + 2],
201 				    &pram_segments[j + 1],
202 				    (pram_nreg - 1 - j) *
203 				    sizeof(pram_segments[0]));
204 			}
205 			pram_nreg++;
206 
207 			pram_segments[j + 1].start = hintaddr + hintsize;
208 			pram_segments[j + 1].size =
209 			    (pram_segments[j].start + pram_segments[j].size) -
210 			    (hintaddr + hintsize);
211 			pram_segments[j].size = hintaddr -
212 			    pram_segments[j].start;
213 		}
214 
215 		if (bootverbose)
216 			nvdimm_e820_dump_prams(dev, __func__, (int)i);
217 
218 		spa_mapping = malloc(sizeof(*spa_mapping), M_NVDIMM_E820,
219 		    M_WAITOK | M_ZERO);
220 
221 		/* Mock up a super primitive table for nvdimm_spa_init(). */
222 		nfit_sa.RangeIndex = i;
223 		nfit_sa.Flags = 0;
224 		nfit_sa.Address = hintaddr;
225 		nfit_sa.Length = hintsize;
226 		nfit_sa.MemoryMapping = EFI_MD_ATTR_WB | EFI_MD_ATTR_WT |
227 		    EFI_MD_ATTR_UC;
228 
229 		error = nvdimm_spa_init(spa_mapping, &nfit_sa, spa_type);
230 		if (error != 0) {
231 			nvdimm_spa_fini(spa_mapping);
232 			free(spa_mapping, M_NVDIMM_E820);
233 			break;
234 		}
235 
236 		SLIST_INSERT_HEAD(&sc->spas, spa_mapping, link);
237 	}
238 	return (error);
239 }
240 
241 static int
242 nvdimm_e820_remove_spas(device_t dev)
243 {
244 	struct nvdimm_e820_bus *sc;
245 	struct SPA_mapping *spa, *next;
246 
247 	sc = device_get_softc(dev);
248 
249 	SLIST_FOREACH_SAFE(spa, &sc->spas, link, next) {
250 		nvdimm_spa_fini(spa);
251 		SLIST_REMOVE_HEAD(&sc->spas, link);
252 		free(spa, M_NVDIMM_E820);
253 	}
254 	return (0);
255 }
256 
257 static void
258 nvdimm_e820_identify(driver_t *driver, device_t parent)
259 {
260 	device_t child;
261 	caddr_t kmdp;
262 
263 	if (resource_disabled(driver->name, 0))
264 		return;
265 	/* Just create a single instance of the fake bus. */
266 	if (device_find_child(parent, driver->name, -1) != NULL)
267 		return;
268 
269 	kmdp = preload_search_by_type("elf kernel");
270 	if (kmdp == NULL)
271 		kmdp = preload_search_by_type("elf64 kernel");
272 	smapbase = (const void *)preload_search_info(kmdp,
273 	    MODINFO_METADATA | MODINFOMD_SMAP);
274 
275 	/* Only supports BIOS SMAP for now. */
276 	if (smapbase == NULL)
277 		return;
278 
279 	child = BUS_ADD_CHILD(parent, 0, driver->name, -1);
280 	if (child == NULL)
281 		device_printf(parent, "add %s child failed\n", driver->name);
282 }
283 
284 static int
285 nvdimm_e820_probe(device_t dev)
286 {
287 	/*
288 	 * nexus panics if a child doesn't have ivars.  BUS_ADD_CHILD uses
289 	 * nexus_add_child, which creates fuckin ivars.  but sometimes if you
290 	 * unload and reload nvdimm_e820, the device node stays but the ivars
291 	 * are deleted??? avoid trivial panic but this is a kludge.
292 	 */
293 	if (device_get_ivars(dev) == NULL)
294 		return (ENXIO);
295 
296 	device_quiet(dev);
297 	device_set_desc(dev, "Legacy e820 NVDIMM root device");
298 	return (BUS_PROBE_NOWILDCARD);
299 }
300 
301 static int
302 nvdimm_e820_attach(device_t dev)
303 {
304 	const struct bios_smap *smapend, *smap;
305 	uint32_t smapsize;
306 	unsigned nregions;
307 	int error;
308 
309 	smapsize = *((const uint32_t *)smapbase - 1);
310 	smapend = (const void *)((const char *)smapbase + smapsize);
311 
312 	for (nregions = 0, smap = smapbase; smap < smapend; smap++) {
313 		if (smap->type != SMAP_TYPE_PRAM || smap->length == 0)
314 			continue;
315 		pram_segments[nregions].start = smap->base;
316 		pram_segments[nregions].size = smap->length;
317 
318 		device_printf(dev, "Found PRAM 0x%jx +0x%jx\n",
319 		    (uintmax_t)smap->base, (uintmax_t)smap->length);
320 
321 		nregions++;
322 	}
323 
324 	if (nregions == 0) {
325 		device_printf(dev, "No e820 PRAM regions detected\n");
326 		return (ENXIO);
327 	}
328 	pram_nreg = nregions;
329 
330 	error = nvdimm_e820_create_spas(dev);
331 	return (error);
332 }
333 
334 static int
335 nvdimm_e820_detach(device_t dev)
336 {
337 	int error;
338 
339 	error = nvdimm_e820_remove_spas(dev);
340 	return (error);
341 }
342 
343 static device_method_t nvdimm_e820_methods[] = {
344 	DEVMETHOD(device_identify, nvdimm_e820_identify),
345 	DEVMETHOD(device_probe, nvdimm_e820_probe),
346 	DEVMETHOD(device_attach, nvdimm_e820_attach),
347 	DEVMETHOD(device_detach, nvdimm_e820_detach),
348 	DEVMETHOD_END
349 };
350 
351 static driver_t	nvdimm_e820_driver = {
352 	NVDIMM_E820,
353 	nvdimm_e820_methods,
354 	sizeof(struct nvdimm_e820_bus),
355 };
356 
357 static int
358 nvdimm_e820_chainevh(struct module *m, int e, void *arg __unused)
359 {
360 	devclass_t dc;
361 	device_t dev, parent;
362 	int i, error, maxunit;
363 
364 	switch (e) {
365 	case MOD_UNLOAD:
366 		dc = devclass_find(nvdimm_e820_driver.name);
367 		maxunit = devclass_get_maxunit(dc);
368 		for (i = 0; i < maxunit; i++) {
369 			dev = devclass_get_device(dc, i);
370 			if (dev == NULL)
371 				continue;
372 			parent = device_get_parent(dev);
373 			if (parent == NULL) {
374 				/* Not sure how this would happen. */
375 				continue;
376 			}
377 			error = device_delete_child(parent, dev);
378 			if (error != 0)
379 				return (error);
380 		}
381 		break;
382 	default:
383 		/* Prevent compiler warning about unhandled cases. */
384 		break;
385 	}
386 	return (0);
387 }
388 
389 DRIVER_MODULE(nvdimm_e820, nexus, nvdimm_e820_driver,
390     nvdimm_e820_chainevh, NULL);
391