xref: /freebsd/sys/dev/acpica/acpi_apei_bus.c (revision 9313f6b01485ad9a0b7cc59b459f5714533587c3)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2020 Alexander Motin <mav@FreeBSD.org>
5  * Copyright (c) 2025-2026 Netflix, Inc.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/types.h>
30 #include <sys/bus.h>
31 #include <sys/kernel.h>
32 #include <sys/malloc.h>
33 #include <sys/module.h>
34 #include <sys/rman.h>
35 
36 #include <contrib/dev/acpica/include/acpi.h>
37 
38 #include <dev/acpica/acpivar.h>
39 #include <dev/acpica/apeivar.h>
40 
41 /*
42  * Different APEI tables can reuse the same registers (and sometimes
43  * different views of the same register, e.g. 32- vs 64-bit mappings
44  * of the same register).  To enable this sharing, apei0 acts as a bus
45  * device managing a pool of allocated resources and hands out
46  * mappings to child devices.  Child devices handle individual tables.
47  */
48 
49 struct apei_register {
50 	struct resource *res;
51 	TAILQ_ENTRY(apei_register) link;
52 };
53 
54 struct apei_register_mapping {
55 	struct resource_map map;
56 	struct apei_register *reg;
57 	TAILQ_ENTRY(apei_register_mapping) link;
58 };
59 
60 struct apei_ivars {
61 	TAILQ_HEAD(, apei_register_mapping) mappings;
62 };
63 
64 TAILQ_HEAD(apei_register_list, apei_register);
65 
66 struct apei_softc {
67 	device_t dev;
68 	struct apei_register_list mem;
69 	struct apei_register_list io;
70 };
71 
72 static char *apei_ids[] = { "PNP0C33", NULL };
73 
74 static MALLOC_DEFINE(M_APEI, "apei", "ACPI platform error interface");
75 
76 static ACPI_STATUS
apei_find(ACPI_HANDLE handle,UINT32 level,void * context,void ** status)77 apei_find(ACPI_HANDLE handle, UINT32 level, void *context,
78     void **status)
79 {
80 	int *found = (int *)status;
81 	char **ids;
82 
83 	for (ids = apei_ids; *ids != NULL; ids++) {
84 		if (acpi_MatchHid(handle, *ids)) {
85 			*found = 1;
86 			break;
87 		}
88 	}
89 	return (AE_OK);
90 }
91 
92 static void
apei_identify(driver_t * driver,device_t parent)93 apei_identify(driver_t *driver, device_t parent)
94 {
95 	device_t	child;
96 	int		found;
97 
98 	if (acpi_disabled("apei"))
99 		return;
100 
101 	/* Only one APEI device can exist. */
102 	if (device_find_child(parent, "apei", DEVICE_UNIT_ANY) != NULL)
103 		return;
104 
105 	/* Search for ACPI error device to be used. */
106 	found = 0;
107 	AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
108 	    100, apei_find, NULL, NULL, (void *)&found);
109 	if (found)
110 		return;
111 
112 	/* If not found - create a fake one. */
113 	child = BUS_ADD_CHILD(parent, 2, "apei", 0);
114 	if (child == NULL)
115 		printf("%s: can't add child\n", __func__);
116 }
117 
118 static int
apei_probe(device_t dev)119 apei_probe(device_t dev)
120 {
121 	int rv;
122 
123 	if (acpi_disabled("apei"))
124 		return (ENXIO);
125 
126 	if (acpi_get_handle(dev) != NULL) {
127 		rv = ACPI_ID_PROBE(device_get_parent(dev), dev, apei_ids, NULL);
128 		if (rv > 0)
129 			return (rv);
130 	}
131 
132 	device_set_desc(dev, "ACPI Platform Error Interface");
133 	return (0);
134 }
135 
136 static int
apei_attach(device_t dev)137 apei_attach(device_t dev)
138 {
139 	struct apei_softc *sc = device_get_softc(dev);
140 
141 	sc->dev = dev;
142 	TAILQ_INIT(&sc->mem);
143 	TAILQ_INIT(&sc->io);
144 	bus_identify_children(dev);
145 	bus_attach_children(dev);
146 	return (0);
147 }
148 
149 static device_t
apei_add_child(device_t dev,u_int order,const char * name,int unit)150 apei_add_child(device_t dev, u_int order, const char *name, int unit)
151 {
152 	struct apei_ivars *ai;
153 	device_t child;
154 
155 	ai = malloc(sizeof(*ai), M_APEI, M_WAITOK);
156 	TAILQ_INIT(&ai->mappings);
157 	child = device_add_child_ordered(dev, order, name, unit);
158 	if (child != NULL)
159 		device_set_ivars(child, ai);
160 	else
161 		free(ai, M_APEI);
162 	return (child);
163 }
164 
165 static void
apei_child_deleted(device_t dev,device_t child)166 apei_child_deleted(device_t dev, device_t child)
167 {
168 	struct apei_ivars *ai = device_get_ivars(child);
169 
170 	MPASS(TAILQ_EMPTY(&ai->mappings));
171 	free(ai, M_APEI);
172 }
173 
174 static void
apei_child_detached(device_t dev,device_t child)175 apei_child_detached(device_t dev, device_t child)
176 {
177 	struct apei_ivars *ai = device_get_ivars(child);
178 
179 	while (!TAILQ_EMPTY(&ai->mappings)) {
180 		struct apei_register_mapping *m = TAILQ_FIRST(&ai->mappings);
181 
182 		device_printf(dev, "child leaked mapping of %s %#jx-%#jx\n",
183 		    rman_get_type(m->reg->res) == SYS_RES_MEMORY ? "iomem" :
184 		    "port", rman_get_start(m->reg->res),
185 		    rman_get_end(m->reg->res));
186 
187 		(void) apei_unmap_register(child, &m->map);
188 	}
189 }
190 
191 static int
apei_read_ivar(device_t dev,device_t child,int index,uintptr_t * result)192 apei_read_ivar(device_t dev, device_t child, int index, uintptr_t *result)
193 {
194 	switch (index) {
195 	case ACPI_IVAR_HANDLE:
196 		*(ACPI_HANDLE *)result = acpi_get_handle(dev);
197 		break;
198 	default:
199 		return (ENOENT);
200 	}
201 
202 	return (0);
203 }
204 
205 static struct apei_register *
apei_alloc_register(struct apei_softc * sc,int type,rman_res_t start,rman_res_t count)206 apei_alloc_register(struct apei_softc *sc, int type, rman_res_t start,
207     rman_res_t count)
208 {
209 	struct apei_register_list *list;
210 	struct apei_register *reg, *prev;
211 	rman_res_t end;
212 	const char *descr;
213 	int error, next_rid;
214 
215 	switch (type) {
216 	case SYS_RES_MEMORY:
217 		list = &sc->mem;
218 		descr = "iomem";
219 		break;
220 	case SYS_RES_IOPORT:
221 		list = &sc->io;
222 		descr = "port";
223 		break;
224 	default:
225 		return (NULL);
226 	}
227 
228 	end = start + (count - 1);
229 
230 	/* First, look for an existing resource. */
231 	prev = NULL;
232 	TAILQ_FOREACH(reg, list, link) {
233 		/* Does the existing register overlap with this request? */
234 		if (start > rman_get_end(reg->res) ||
235 		    end < rman_get_start(reg->res)) {
236 
237 			/* prev will be the location of the first free rid */
238 			if (prev == NULL)
239 				next_rid = 0;
240 			else
241 				next_rid = rman_get_rid(prev->res) + 1;
242 			if (rman_get_rid(reg->res) == next_rid)
243 				prev = reg;
244 			continue;
245 		}
246 
247 		/* Do we need to extend the existing register? */
248 		if (start < rman_get_start(reg->res) ||
249 		    end > rman_get_end(reg->res)) {
250 			rman_res_t new_end, new_start;
251 
252 			new_start = rman_get_start(reg->res);
253 			if (start < new_start)
254 				new_start = start;
255 			new_end = rman_get_end(reg->res);
256 			if (end > new_end)
257 				new_end = end;
258 			error = bus_adjust_resource(sc->dev, reg->res,
259 			    new_start, new_end);
260 			if (error != 0) {
261 				if (bootverbose)
262 					device_printf(sc->dev,
263 			    "failed to grow %s %#jx-%#jx for %#jx-%#jx: %d\n",
264 					    descr, rman_get_start(reg->res),
265 					    rman_get_end(reg->res), start, end,
266 					    error);
267 				return (NULL);
268 			}
269 		}
270 
271 		return (reg);
272 	}
273 
274 	/* Allocate a new resource. */
275 	if (prev == NULL)
276 		next_rid = 0;
277 	else
278 		next_rid = rman_get_rid(prev->res) + 1;
279 	error = bus_set_resource(sc->dev, type, next_rid, start, count);
280 	if (error != 0) {
281 		if (bootverbose)
282 			device_printf(sc->dev,
283 			    "failed to add %s %#jx-%#jx: %d\n", descr, start,
284 			    end, error);
285 		return (NULL);
286 	}
287 
288 	reg = malloc(sizeof(*reg), M_APEI, M_WAITOK);
289 	reg->res = bus_alloc_resource_any(sc->dev, type, next_rid, RF_ACTIVE |
290 	    RF_UNMAPPED);
291 	if (reg->res == NULL) {
292 		if (bootverbose)
293 			device_printf(sc->dev,
294 			    "failed to allocate %s %#jx-%#jx\n", descr, start,
295 			    end);
296 		free(reg, M_APEI);
297 		bus_delete_resource(sc->dev, type, next_rid);
298 		return (NULL);
299 	}
300 
301 	if (prev == NULL)
302 		TAILQ_INSERT_HEAD(list, reg, link);
303 	else
304 		TAILQ_INSERT_AFTER(list, prev, reg, link);
305 	return (reg);
306 }
307 
308 static struct resource_map *
apei_map_register_method(device_t dev,device_t child,int type,rman_res_t start,rman_res_t count)309 apei_map_register_method(device_t dev, device_t child, int type,
310     rman_res_t start, rman_res_t count)
311 {
312 	struct apei_softc *sc = device_get_softc(dev);
313 	struct apei_ivars *ai = device_get_ivars(child);
314 	struct apei_register_mapping *m;
315 	struct apei_register *reg;
316 	struct resource_map_request req;
317 	int error;
318 
319 	reg = apei_alloc_register(sc, type, start, count);
320 	if (reg == NULL)
321 		return (NULL);
322 
323 	m = malloc(sizeof(*m), M_APEI, M_WAITOK | M_ZERO);
324 	m->reg = reg;
325 	resource_init_map_request(&req);
326 	req.offset = start - rman_get_start(reg->res);
327 	req.length = count;
328 	error = bus_map_resource(dev, reg->res, &req, &m->map);
329 	if (error != 0) {
330 		if (bootverbose)
331 			device_printf(dev, "failed to map %s %#jx-%#jx: %d\n",
332 			    type == SYS_RES_MEMORY ? "iomem" : "port",
333 			    start,
334 			    start + (count - 1), error);
335 		free(m, M_APEI);
336 		return (NULL);
337 	}
338 	TAILQ_INSERT_TAIL(&ai->mappings, m, link);
339 	return (&m->map);
340 }
341 
342 static int
apei_unmap_register_method(device_t dev,device_t child,struct resource_map * map)343 apei_unmap_register_method(device_t dev, device_t child,
344     struct resource_map *map)
345 {
346 	struct apei_ivars *ai = device_get_ivars(child);
347 	struct apei_register_mapping *m;
348 	int error;
349 
350 	if (map == NULL)
351 		return (0);
352 
353 	TAILQ_FOREACH(m, &ai->mappings, link) {
354 		if (&m->map == map)
355 			break;
356 	}
357 
358 	if (m == NULL)
359 		return (EINVAL);
360 
361 	TAILQ_REMOVE(&ai->mappings, m, link);
362 	error = bus_unmap_resource(dev, m->reg->res, &m->map);
363 	if (error != 0 && bootverbose)
364 		device_printf(dev, "failed to unmap %s %#jx-%#jx: %d\n",
365 		    rman_get_type(m->reg->res) == SYS_RES_MEMORY ? "iomem" :
366 		    "port", rman_get_start(m->reg->res),
367 		    rman_get_end(m->reg->res), error);
368 	free(m, M_APEI);
369 	return (0);
370 }
371 
372 struct resource_map *
apei_map_register(device_t dev,ACPI_GENERIC_ADDRESS * gas)373 apei_map_register(device_t dev, ACPI_GENERIC_ADDRESS *gas)
374 {
375 	int type;
376 
377 	if (gas->Address == 0 || gas->BitWidth == 0 || gas->BitWidth % 8 != 0)
378 		return (NULL);
379 
380 	switch (gas->SpaceId) {
381 	case ACPI_ADR_SPACE_SYSTEM_MEMORY:
382 		type = SYS_RES_MEMORY;
383 		break;
384 	case ACPI_ADR_SPACE_SYSTEM_IO:
385 		type = SYS_RES_IOPORT;
386 		break;
387 	default:
388 		return (NULL);
389 	}
390 
391 	return (APEI_MAP_REGISTER(device_get_parent(dev), dev, type,
392 	    gas->Address, gas->BitWidth / 8));
393 }
394 
395 struct resource_map *
apei_map_memory(device_t dev,rman_res_t start,rman_res_t count)396 apei_map_memory(device_t dev, rman_res_t start, rman_res_t count)
397 {
398 	return (APEI_MAP_REGISTER(device_get_parent(dev), dev, SYS_RES_MEMORY,
399 	    start, count));
400 }
401 
402 int
apei_unmap_register(device_t dev,struct resource_map * map)403 apei_unmap_register(device_t dev, struct resource_map *map)
404 {
405 	return (APEI_UNMAP_REGISTER(device_get_parent(dev), dev, map));
406 }
407 
408 static device_method_t apei_methods[] = {
409 	/* Device interface */
410 	DEVMETHOD(device_identify,	apei_identify),
411 	DEVMETHOD(device_probe,		apei_probe),
412 	DEVMETHOD(device_attach,	apei_attach),
413 
414 	/* Bus interface */
415 	DEVMETHOD(bus_add_child,	apei_add_child),
416 	DEVMETHOD(bus_print_child,	bus_generic_print_child),
417 	DEVMETHOD(bus_child_deleted,	apei_child_deleted),
418 	DEVMETHOD(bus_child_detached,	apei_child_detached),
419 	DEVMETHOD(bus_read_ivar,	apei_read_ivar),
420 
421 	/* APEI interface */
422 	DEVMETHOD(apei_map_register,	apei_map_register_method),
423 	DEVMETHOD(apei_unmap_register,	apei_unmap_register_method),
424 
425 	DEVMETHOD_END
426 };
427 
428 static driver_t	apei_driver = {
429 	"apei",
430 	apei_methods,
431 	sizeof(struct apei_softc),
432 };
433 
434 DRIVER_MODULE(apei, acpi, apei_driver, NULL, NULL);
435 MODULE_DEPEND(apei, acpi, 1, 1, 1);
436