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