xref: /freebsd/sys/dev/bhnd/bhnd_erom.c (revision fdafd315)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
5  * Copyright (c) 2017 The FreeBSD Foundation
6  * All rights reserved.
7  *
8  * Portions of this software were developed by Landon Fuller
9  * under sponsorship from the FreeBSD Foundation.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer,
16  *    without modification.
17  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
18  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
19  *    redistribution must be conditioned upon including a substantially
20  *    similar Disclaimer requirement for further binary redistribution.
21  *
22  * NO WARRANTY
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
26  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
27  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
28  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
31  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
33  * THE POSSIBILITY OF SUCH DAMAGES.
34  */
35 
36 #include <sys/param.h>
37 #include <sys/bus.h>
38 #include <sys/kobj.h>
39 
40 #include <machine/bus.h>
41 #include <sys/rman.h>
42 #include <machine/resource.h>
43 
44 #include <dev/bhnd/bhndreg.h>
45 #include <dev/bhnd/bhndvar.h>
46 
47 #include <dev/bhnd/bhnd_erom.h>
48 #include <dev/bhnd/bhnd_eromvar.h>
49 
50 #include <dev/bhnd/cores/chipc/chipcreg.h>
51 
52 static int	bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,
53 		    bhnd_size_t size);
54 static int	bhnd_erom_iores_tell(struct bhnd_erom_io *eio,
55 		    bhnd_addr_t *addr, bhnd_size_t *size);
56 static uint32_t	bhnd_erom_iores_read(struct bhnd_erom_io *eio,
57 		    bhnd_size_t offset, u_int width);
58 static void	bhnd_erom_iores_fini(struct bhnd_erom_io *eio);
59 
60 static int	bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,
61 		    bhnd_size_t size);
62 static int	bhnd_erom_iobus_tell(struct bhnd_erom_io *eio,
63 		    bhnd_addr_t *addr, bhnd_size_t *size);
64 static uint32_t	bhnd_erom_iobus_read(struct bhnd_erom_io *eio,
65 		    bhnd_size_t offset, u_int width);
66 
67 /**
68  * An implementation of bhnd_erom_io that manages mappings via
69  * bhnd_alloc_resource() and bhnd_release_resource().
70  */
71 struct bhnd_erom_iores {
72 	struct bhnd_erom_io	 eio;
73 	device_t		 owner;		/**< device from which we'll allocate resources */
74 	int			 owner_rid;	/**< rid to use when allocating new mappings */
75 	struct bhnd_resource	*mapped;	/**< current mapping, or NULL */
76 	int			 mapped_rid;	/**< resource ID of current mapping, or -1 */
77 };
78 
79 /**
80  * Fetch the device enumeration parser class from all bhnd(4)-compatible drivers
81  * registered for @p bus_devclass, probe @p eio for supporting parser classes,
82  * and return the best available supporting enumeration parser class.
83  *
84  * @param	bus_devclass	The bus device class to be queried for
85  *				bhnd(4)-compatible drivers.
86  * @param	eio		An erom bus I/O instance, configured with a
87  *				mapping of the first bus core.
88  * @param	hint		Identification hint used to identify the device.
89  *				If the chipset supports standard chip
90  *				identification registers within the first core,
91  *				this parameter should be NULL.
92  * @param[out]	cid		On success, the probed chip identifier.
93  *
94  * @retval non-NULL	on success, the best available EROM class.
95  * @retval NULL		if no erom class returned a successful probe result for
96  *			@p eio.
97  */
98 bhnd_erom_class_t *
bhnd_erom_probe_driver_classes(devclass_t bus_devclass,struct bhnd_erom_io * eio,const struct bhnd_chipid * hint,struct bhnd_chipid * cid)99 bhnd_erom_probe_driver_classes(devclass_t bus_devclass,
100     struct bhnd_erom_io *eio, const struct bhnd_chipid *hint,
101     struct bhnd_chipid *cid)
102 {
103 	driver_t		**drivers;
104 	int			 drv_count;
105 	bhnd_erom_class_t	*erom_cls;
106 	int			 error, prio, result;
107 
108 	erom_cls = NULL;
109 	prio = 0;
110 
111 	/* Fetch all available drivers */
112 	error = devclass_get_drivers(bus_devclass, &drivers, &drv_count);
113 	if (error) {
114 		printf("error fetching bhnd(4) drivers for %s: %d\n",
115 		    devclass_get_name(bus_devclass), error);
116 		return (NULL);
117 	}
118 
119 	/* Enumerate the drivers looking for the best available EROM class */
120 	for (int i = 0; i < drv_count; i++) {
121 		struct bhnd_chipid	 pcid;
122 		bhnd_erom_class_t	*cls;
123 
124 		/* The default implementation of BHND_BUS_GET_EROM_CLASS()
125 		 * returns NULL if unimplemented; this should always be safe
126 		 * to call on arbitrary drivers */
127 		cls = bhnd_driver_get_erom_class(drivers[i]);
128 		if (cls == NULL)
129 			continue;
130 
131 		kobj_class_compile(cls);
132 
133 		/* Probe the bus */
134 		result = bhnd_erom_probe(cls, eio, hint, &pcid);
135 
136 		/* The parser did not match if an error was returned */
137 		if (result > 0)
138 			continue;
139 
140 		/* Check for a new highest priority match */
141 		if (erom_cls == NULL || result > prio) {
142 			prio = result;
143 
144 			*cid = pcid;
145 			erom_cls = cls;
146 		}
147 
148 		/* Terminate immediately on BUS_PROBE_SPECIFIC */
149 		if (result == BUS_PROBE_SPECIFIC)
150 			break;
151 	}
152 
153 	free(drivers, M_TEMP);
154 	return (erom_cls);
155 }
156 
157 /**
158  * Allocate and return a new device enumeration table parser.
159  *
160  * @param cls		The parser class for which an instance will be
161  *			allocated.
162  * @param eio		The bus I/O callbacks to use when reading the device
163  *			enumeration table.
164  * @param cid		The device's chip identifier.
165  *
166  * @retval non-NULL	success
167  * @retval NULL		if an error occurred allocating or initializing the
168  *			EROM parser.
169  */
170 bhnd_erom_t *
bhnd_erom_alloc(bhnd_erom_class_t * cls,const struct bhnd_chipid * cid,struct bhnd_erom_io * eio)171 bhnd_erom_alloc(bhnd_erom_class_t *cls, const struct bhnd_chipid *cid,
172     struct bhnd_erom_io *eio)
173 {
174 	bhnd_erom_t	*erom;
175 	int		 error;
176 
177 	erom = (bhnd_erom_t *)kobj_create((kobj_class_t)cls, M_BHND,
178 	    M_WAITOK|M_ZERO);
179 
180 	if ((error = BHND_EROM_INIT(erom, cid, eio))) {
181 		printf("error initializing %s parser at %#jx: %d\n", cls->name,
182 		    (uintmax_t)cid->enum_addr, error);
183 
184 		kobj_delete((kobj_t)erom, M_BHND);
185 		return (NULL);
186 	}
187 
188 	return (erom);
189 }
190 
191 /**
192  * Perform static initialization of a device enumeration table parser.
193  *
194  * This may be used to initialize a caller-allocated erom instance state
195  * during early boot, prior to malloc availability.
196  *
197  * @param cls		The parser class for which an instance will be
198  *			allocated.
199  * @param erom		The erom parser instance to initialize.
200  * @param esize		The total available number of bytes allocated for
201  *			@p erom. If this is less than is required by @p cls,
202  *			ENOMEM will be returned.
203  * @param cid		The device's chip identifier.
204  * @param eio		The bus I/O callbacks to use when reading the device
205  *			enumeration table.
206  *
207  * @retval 0		success
208  * @retval ENOMEM	if @p esize is smaller than required by @p cls.
209  * @retval non-zero	if an error occurs initializing the EROM parser,
210  *			a regular unix error code will be returned.
211  */
212 int
bhnd_erom_init_static(bhnd_erom_class_t * cls,bhnd_erom_t * erom,size_t esize,const struct bhnd_chipid * cid,struct bhnd_erom_io * eio)213 bhnd_erom_init_static(bhnd_erom_class_t *cls, bhnd_erom_t *erom, size_t esize,
214     const struct bhnd_chipid *cid, struct bhnd_erom_io *eio)
215 {
216 	kobj_class_t	kcls;
217 
218 	kcls = (kobj_class_t)cls;
219 
220 	/* Verify allocation size */
221 	if (kcls->size > esize)
222 		return (ENOMEM);
223 
224 	/* Perform instance initialization */
225 	kobj_init_static((kobj_t)erom, kcls);
226 	return (BHND_EROM_INIT(erom, cid, eio));
227 }
228 
229 /**
230  * Release any resources held by a @p erom parser previously
231  * initialized via bhnd_erom_init_static().
232  *
233  * @param	erom	An erom parser instance previously initialized via
234  *			bhnd_erom_init_static().
235  */
236 void
bhnd_erom_fini_static(bhnd_erom_t * erom)237 bhnd_erom_fini_static(bhnd_erom_t *erom)
238 {
239 	return (BHND_EROM_FINI(erom));
240 }
241 
242 /**
243  * Release all resources held by a @p erom parser previously
244  * allocated via bhnd_erom_alloc().
245  *
246  * @param	erom	An erom parser instance previously allocated via
247  *			bhnd_erom_alloc().
248  */
249 void
bhnd_erom_free(bhnd_erom_t * erom)250 bhnd_erom_free(bhnd_erom_t *erom)
251 {
252 	BHND_EROM_FINI(erom);
253 	kobj_delete((kobj_t)erom, M_BHND);
254 }
255 
256 /**
257  * Read the chip identification registers mapped by @p eio, popuating @p cid
258  * with the parsed result
259  *
260  * @param	eio		A bus I/O instance, configured with a mapping
261  *				of the ChipCommon core.
262  * @param[out]	cid		On success, the parsed chip identification.
263  *
264  * @warning
265  * On early siba(4) devices, the ChipCommon core does not provide
266  * a valid CHIPC_ID_NUMCORE field. On these ChipCommon revisions
267  * (see CHIPC_NCORES_MIN_HWREV()), this function will parse and return
268  * an invalid `ncores` value.
269  */
270 int
bhnd_erom_read_chipid(struct bhnd_erom_io * eio,struct bhnd_chipid * cid)271 bhnd_erom_read_chipid(struct bhnd_erom_io *eio, struct bhnd_chipid *cid)
272 {
273 	bhnd_addr_t	cc_addr;
274 	bhnd_size_t	cc_size;
275 	uint32_t	idreg, cc_caps;
276 	int		error;
277 
278 	/* Fetch ChipCommon address */
279 	if ((error = bhnd_erom_io_tell(eio, &cc_addr, &cc_size)))
280 		return (error);
281 
282 	/* Read chip identifier */
283 	idreg = bhnd_erom_io_read(eio, CHIPC_ID, 4);
284 
285 	/* Extract the basic chip info */
286 	cid->chip_id = CHIPC_GET_BITS(idreg, CHIPC_ID_CHIP);
287 	cid->chip_pkg = CHIPC_GET_BITS(idreg, CHIPC_ID_PKG);
288 	cid->chip_rev = CHIPC_GET_BITS(idreg, CHIPC_ID_REV);
289 	cid->chip_type = CHIPC_GET_BITS(idreg, CHIPC_ID_BUS);
290 	cid->ncores = CHIPC_GET_BITS(idreg, CHIPC_ID_NUMCORE);
291 
292 	/* Populate EROM address */
293 	if (BHND_CHIPTYPE_HAS_EROM(cid->chip_type)) {
294 		cid->enum_addr = bhnd_erom_io_read(eio, CHIPC_EROMPTR, 4);
295 	} else {
296 		cid->enum_addr = cc_addr;
297 	}
298 
299 	/* Populate capability flags */
300 	cc_caps = bhnd_erom_io_read(eio, CHIPC_CAPABILITIES, 4);
301 	cid->chip_caps = 0x0;
302 
303 	if (cc_caps & CHIPC_CAP_BKPLN64)
304 		cid->chip_caps |= BHND_CAP_BP64;
305 
306 	if (cc_caps & CHIPC_CAP_PMU)
307 		cid->chip_caps |= BHND_CAP_PMU;
308 
309 	return (0);
310 }
311 
312 /**
313  * Attempt to map @p size bytes at @p addr, replacing any existing
314  * @p eio mapping.
315  *
316  * @param eio	I/O instance state.
317  * @param addr	The address to be mapped.
318  * @param size	The number of bytes to be mapped at @p addr.
319  *
320  * @retval 0		success
321  * @retval non-zero	if mapping @p addr otherwise fails, a regular
322  *			unix error code should be returned.
323  */
324 int
bhnd_erom_io_map(struct bhnd_erom_io * eio,bhnd_addr_t addr,bhnd_size_t size)325 bhnd_erom_io_map(struct bhnd_erom_io *eio, bhnd_addr_t addr, bhnd_size_t size)
326 {
327 	return (eio->map(eio, addr, size));
328 }
329 
330 /**
331  * Return the address range mapped by @p eio, if any.
332  *
333  * @param	eio	I/O instance state.
334  * @param[out]	addr	The address mapped by @p eio.
335  * @param[out]	size	The number of bytes mapped at @p addr.
336  *
337  * @retval	0	success
338  * @retval	ENXIO	if @p eio has no mapping.
339  */
340 int
bhnd_erom_io_tell(struct bhnd_erom_io * eio,bhnd_addr_t * addr,bhnd_size_t * size)341 bhnd_erom_io_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,
342     bhnd_size_t *size)
343 {
344 	return (eio->tell(eio, addr, size));
345 }
346 
347 /**
348  * Read a 1, 2, or 4 byte data item from @p eio, at the given @p offset
349  * relative to @p eio's current mapping.
350  *
351  * @param eio		erom I/O callbacks
352  * @param offset	read offset.
353  * @param width		item width (1, 2, or 4 bytes).
354  */
355 uint32_t
bhnd_erom_io_read(struct bhnd_erom_io * eio,bhnd_size_t offset,u_int width)356 bhnd_erom_io_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)
357 {
358 	return (eio->read(eio, offset, width));
359 }
360 
361 /**
362  * Free all resources held by @p eio.
363  */
364 void
bhnd_erom_io_fini(struct bhnd_erom_io * eio)365 bhnd_erom_io_fini(struct bhnd_erom_io *eio)
366 {
367 	if (eio->fini != NULL)
368 		return (eio->fini(eio));
369 }
370 
371 /**
372  * Allocate, initialize, and return a new I/O instance that will perform
373  * mapping by allocating SYS_RES_MEMORY resources from @p dev using @p rid.
374  *
375  * @param dev	The device to pass to bhnd_alloc_resource() and
376  *		bhnd_release_resource() functions.
377  * @param rid	The resource ID to be used when allocating memory resources.
378  */
379 struct bhnd_erom_io *
bhnd_erom_iores_new(device_t dev,int rid)380 bhnd_erom_iores_new(device_t dev, int rid)
381 {
382 	struct bhnd_erom_iores	*iores;
383 
384 	iores = malloc(sizeof(*iores), M_BHND, M_WAITOK | M_ZERO);
385 	iores->eio.map = bhnd_erom_iores_map;
386 	iores->eio.tell = bhnd_erom_iores_tell;
387 	iores->eio.read = bhnd_erom_iores_read;
388 	iores->eio.fini = bhnd_erom_iores_fini;
389 
390 	iores->owner = dev;
391 	iores->owner_rid = rid;
392 	iores->mapped = NULL;
393 	iores->mapped_rid = -1;
394 
395 	return (&iores->eio);
396 }
397 
398 static int
bhnd_erom_iores_map(struct bhnd_erom_io * eio,bhnd_addr_t addr,bhnd_size_t size)399 bhnd_erom_iores_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,
400     bhnd_size_t size)
401 {
402 	struct bhnd_erom_iores *iores;
403 
404 	iores = (struct bhnd_erom_iores *)eio;
405 
406 	/* Sanity check the addr/size */
407 	if (size == 0)
408 		return (EINVAL);
409 
410 	if (BHND_ADDR_MAX - size < addr)
411 		return (EINVAL);	/* would overflow */
412 
413 	/* Check for an existing mapping */
414 	if (iores->mapped) {
415 		/* If already mapped, nothing else to do */
416 		if (rman_get_start(iores->mapped->res) == addr &&
417 		    rman_get_size(iores->mapped->res) == size)
418 		{
419 			return (0);
420 		}
421 
422 		/* Otherwise, we need to drop the existing mapping */
423 		bhnd_release_resource(iores->owner, SYS_RES_MEMORY,
424 		    iores->mapped_rid, iores->mapped);
425 		iores->mapped = NULL;
426 		iores->mapped_rid = -1;
427 	}
428 
429 	/* Try to allocate the new mapping */
430 	iores->mapped_rid = iores->owner_rid;
431 	iores->mapped = bhnd_alloc_resource(iores->owner, SYS_RES_MEMORY,
432 	    &iores->mapped_rid, addr, addr+size-1, size,
433 	    RF_ACTIVE|RF_SHAREABLE);
434 	if (iores->mapped == NULL) {
435 		iores->mapped_rid = -1;
436 		return (ENXIO);
437 	}
438 
439 	return (0);
440 }
441 
442 static int
bhnd_erom_iores_tell(struct bhnd_erom_io * eio,bhnd_addr_t * addr,bhnd_size_t * size)443 bhnd_erom_iores_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,
444     bhnd_size_t *size)
445 {
446 	struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;
447 
448 	if (iores->mapped == NULL)
449 		return (ENXIO);
450 
451 	*addr = rman_get_start(iores->mapped->res);
452 	*size = rman_get_size(iores->mapped->res);
453 
454 	return (0);
455 }
456 
457 static uint32_t
bhnd_erom_iores_read(struct bhnd_erom_io * eio,bhnd_size_t offset,u_int width)458 bhnd_erom_iores_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)
459 {
460 	struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;
461 
462 	if (iores->mapped == NULL)
463 		panic("read with invalid mapping");
464 
465 	switch (width) {
466 	case 1:
467 		return (bhnd_bus_read_1(iores->mapped, offset));
468 	case 2:
469 		return (bhnd_bus_read_2(iores->mapped, offset));
470 	case 4:
471 		return (bhnd_bus_read_4(iores->mapped, offset));
472 	default:
473 		panic("invalid width %u", width);
474 	}
475 }
476 
477 static void
bhnd_erom_iores_fini(struct bhnd_erom_io * eio)478 bhnd_erom_iores_fini(struct bhnd_erom_io *eio)
479 {
480 	struct bhnd_erom_iores *iores = (struct bhnd_erom_iores *)eio;
481 
482 	/* Release any mapping */
483 	if (iores->mapped) {
484 		bhnd_release_resource(iores->owner, SYS_RES_MEMORY,
485 		    iores->mapped_rid, iores->mapped);
486 		iores->mapped = NULL;
487 		iores->mapped_rid = -1;
488 	}
489 
490 	free(eio, M_BHND);
491 }
492 
493 /**
494  * Initialize an I/O instance that will perform mapping directly from the
495  * given bus space tag and handle.
496  *
497  * @param iobus	The I/O instance to be initialized.
498  * @param addr	The base address mapped by @p bsh.
499  * @param size	The total size mapped by @p bsh.
500  * @param bst	Bus space tag for @p bsh.
501  * @param bsh	Bus space handle mapping the full bus enumeration space.
502  *
503  * @retval 0		success
504  * @retval non-zero	if initializing @p iobus otherwise fails, a regular
505  *			unix error code will be returned.
506  */
507 int
bhnd_erom_iobus_init(struct bhnd_erom_iobus * iobus,bhnd_addr_t addr,bhnd_size_t size,bus_space_tag_t bst,bus_space_handle_t bsh)508 bhnd_erom_iobus_init(struct bhnd_erom_iobus *iobus, bhnd_addr_t addr,
509     bhnd_size_t size, bus_space_tag_t bst, bus_space_handle_t bsh)
510 {
511 	iobus->eio.map = bhnd_erom_iobus_map;
512 	iobus->eio.tell = bhnd_erom_iobus_tell;
513 	iobus->eio.read = bhnd_erom_iobus_read;
514 	iobus->eio.fini = NULL;
515 
516 	iobus->addr = addr;
517 	iobus->size = size;
518 	iobus->bst = bst;
519 	iobus->bsh = bsh;
520 	iobus->mapped = false;
521 
522 	return (0);
523 }
524 
525 static int
bhnd_erom_iobus_map(struct bhnd_erom_io * eio,bhnd_addr_t addr,bhnd_size_t size)526 bhnd_erom_iobus_map(struct bhnd_erom_io *eio, bhnd_addr_t addr,
527     bhnd_size_t size)
528 {
529 	struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;
530 
531 	/* Sanity check the addr/size */
532 	if (size == 0)
533 		return (EINVAL);
534 
535 	/* addr+size must not overflow */
536 	if (BHND_ADDR_MAX - size < addr)
537 		return (EINVAL);
538 
539 	/* addr/size must fit within our bus tag's mapping */
540 	if (addr < iobus->addr || size > iobus->size)
541 		return (ENXIO);
542 
543 	if (iobus->size - (addr - iobus->addr) < size)
544 		return (ENXIO);
545 
546 	/* The new addr offset and size must be representible as a bus_size_t */
547 	if ((addr - iobus->addr) > BUS_SPACE_MAXSIZE)
548 		return (ENXIO);
549 
550 	if (size > BUS_SPACE_MAXSIZE)
551 		return (ENXIO);
552 
553 	iobus->offset = addr - iobus->addr;
554 	iobus->limit = size;
555 	iobus->mapped = true;
556 
557 	return (0);
558 }
559 
560 static int
bhnd_erom_iobus_tell(struct bhnd_erom_io * eio,bhnd_addr_t * addr,bhnd_size_t * size)561 bhnd_erom_iobus_tell(struct bhnd_erom_io *eio, bhnd_addr_t *addr,
562     bhnd_size_t *size)
563 {
564 	struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;
565 
566 	if (!iobus->mapped)
567 		return (ENXIO);
568 
569 	*addr = iobus->addr + iobus->offset;
570 	*size = iobus->limit;
571 
572 	return (0);
573 }
574 
575 static uint32_t
bhnd_erom_iobus_read(struct bhnd_erom_io * eio,bhnd_size_t offset,u_int width)576 bhnd_erom_iobus_read(struct bhnd_erom_io *eio, bhnd_size_t offset, u_int width)
577 {
578 	struct bhnd_erom_iobus *iobus = (struct bhnd_erom_iobus *)eio;
579 
580 	if (!iobus->mapped)
581 		panic("no active mapping");
582 
583 	if (iobus->limit < width || iobus->limit - width < offset)
584 		panic("invalid offset %#jx", offset);
585 
586 	switch (width) {
587 	case 1:
588 		return (bus_space_read_1(iobus->bst, iobus->bsh,
589 		    iobus->offset + offset));
590 	case 2:
591 		return (bus_space_read_2(iobus->bst, iobus->bsh,
592 		    iobus->offset + offset));
593 	case 4:
594 		return (bus_space_read_4(iobus->bst, iobus->bsh,
595 		    iobus->offset + offset));
596 	default:
597 		panic("invalid width %u", width);
598 	}
599 }
600