xref: /freebsd/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 38069501)
1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landon@landonf.org>
3  * Copyright (c) 2017 The FreeBSD Foundation
4  * All rights reserved.
5  *
6  * Portions of this software were developed by Landon Fuller
7  * under sponsorship from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer,
14  *    without modification.
15  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
16  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
17  *    redistribution must be conditioned upon including a substantially
18  *    similar Disclaimer requirement for further binary redistribution.
19  *
20  * NO WARRANTY
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
24  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
25  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
26  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31  * THE POSSIBILITY OF SUCH DAMAGES.
32  */
33 
34 #include <sys/cdefs.h>
35 __FBSDID("$FreeBSD$");
36 
37 #include <sys/param.h>
38 #include <sys/kernel.h>
39 #include <sys/limits.h>
40 
41 #include "bhndb_private.h"
42 #include "bhndbvar.h"
43 
44 /**
45  * Attach a BHND bridge device to @p parent.
46  *
47  * @param parent A parent PCI device.
48  * @param[out] bhndb On success, the probed and attached bhndb bridge device.
49  * @param unit The device unit number, or -1 to select the next available unit
50  * number.
51  *
52  * @retval 0 success
53  * @retval non-zero Failed to attach the bhndb device.
54  */
55 int
56 bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit)
57 {
58 	int error;
59 
60 	*bhndb = device_add_child(parent, "bhndb", unit);
61 	if (*bhndb == NULL)
62 		return (ENXIO);
63 
64 	if (!(error = device_probe_and_attach(*bhndb)))
65 		return (0);
66 
67 	if ((device_delete_child(parent, *bhndb)))
68 		device_printf(parent, "failed to detach bhndb child\n");
69 
70 	return (error);
71 }
72 
73 /*
74  * Call BHNDB_SUSPEND_RESOURCE() for all resources in @p rl.
75  */
76 static void
77 bhndb_do_suspend_resources(device_t dev, struct resource_list *rl)
78 {
79 	struct resource_list_entry *rle;
80 
81 	/* Suspend all child resources. */
82 	STAILQ_FOREACH(rle, rl, link) {
83 		/* Skip non-allocated resources */
84 		if (rle->res == NULL)
85 			continue;
86 
87 		BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, rle->type,
88 		    rle->res);
89 	}
90 }
91 
92 /**
93  * Helper function for implementing BUS_RESUME_CHILD() on bridged
94  * bhnd(4) buses.
95  *
96  * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST()
97  * to find the child's resources and call BHNDB_SUSPEND_RESOURCE() for all
98  * child resources, ensuring that the device's allocated bridge resources
99  * will be available to other devices during bus resumption.
100  *
101  * Before suspending any resources, @p child is suspended by
102  * calling bhnd_generic_suspend_child().
103  *
104  * If @p child is not a direct child of @p dev, suspension is delegated to
105  * the @p dev parent.
106  */
107 int
108 bhnd_generic_br_suspend_child(device_t dev, device_t child)
109 {
110 	struct resource_list		*rl;
111 	int				 error;
112 
113 	if (device_get_parent(child) != dev)
114 		BUS_SUSPEND_CHILD(device_get_parent(dev), child);
115 
116 	if (device_is_suspended(child))
117 		return (EBUSY);
118 
119 	/* Suspend the child device */
120 	if ((error = bhnd_generic_suspend_child(dev, child)))
121 		return (error);
122 
123 	/* Fetch the resource list. If none, there's nothing else to do */
124 	rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
125 	if (rl == NULL)
126 		return (0);
127 
128 	/* Suspend all child resources. */
129 	bhndb_do_suspend_resources(dev, rl);
130 
131 	return (0);
132 }
133 
134 /**
135  * Helper function for implementing BUS_RESUME_CHILD() on bridged
136  * bhnd(4) bus devices.
137  *
138  * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST()
139  * to find the child's resources and call BHNDB_RESUME_RESOURCE() for all
140  * child resources, before delegating to bhnd_generic_resume_child().
141  *
142  * If resource resumption fails, @p child will not be resumed.
143  *
144  * If @p child is not a direct child of @p dev, suspension is delegated to
145  * the @p dev parent.
146  */
147 int
148 bhnd_generic_br_resume_child(device_t dev, device_t child)
149 {
150 	struct resource_list		*rl;
151 	struct resource_list_entry	*rle;
152 	int				 error;
153 
154 	if (device_get_parent(child) != dev)
155 		BUS_RESUME_CHILD(device_get_parent(dev), child);
156 
157 	if (!device_is_suspended(child))
158 		return (EBUSY);
159 
160 	/* Fetch the resource list. If none, there's nothing else to do */
161 	rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
162 	if (rl == NULL)
163 		return (bhnd_generic_resume_child(dev, child));
164 
165 	/* Resume all resources */
166 	STAILQ_FOREACH(rle, rl, link) {
167 		/* Skip non-allocated resources */
168 		if (rle->res == NULL)
169 			continue;
170 
171 		error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev,
172 		    rle->type, rle->res);
173 		if (error) {
174 			/* Put all resources back into a suspend state */
175 			bhndb_do_suspend_resources(dev, rl);
176 			return (error);
177 		}
178 	}
179 
180 	/* Now that all resources are resumed, resume child */
181 	if ((error = bhnd_generic_resume_child(dev, child))) {
182 		/* Put all resources back into a suspend state */
183 		bhndb_do_suspend_resources(dev, rl);
184 	}
185 
186 	return (error);
187 }
188 
189 /**
190  * Find a host resource of @p type that maps the given range.
191  *
192  * @param hr The resource state to search.
193  * @param type The resource type to search for (see SYS_RES_*).
194  * @param start The start address of the range to search for.
195  * @param count The size of the range to search for.
196  *
197  * @retval resource the host resource containing the requested range.
198  * @retval NULL if no resource containing the requested range can be found.
199  */
200 struct resource *
201 bhndb_host_resource_for_range(struct bhndb_host_resources *hr, int type,
202     rman_res_t start, rman_res_t count)
203 {
204 	for (u_int i = 0; hr->resource_specs[i].type != -1; i++) {
205 		struct resource *r = hr->resources[i];
206 
207 		if (hr->resource_specs[i].type != type)
208 			continue;
209 
210 		/* Verify range */
211 		if (rman_get_start(r) > start)
212 			continue;
213 
214 		if (rman_get_end(r) < (start + count - 1))
215 			continue;
216 
217 		return (r);
218 	}
219 
220 	return (NULL);
221 }
222 
223 /**
224  * Find a host resource of that matches the given register window definition.
225  *
226  * @param hr The resource state to search.
227  * @param win A register window definition.
228  *
229  * @retval resource the host resource corresponding to @p win.
230  * @retval NULL if no resource corresponding to @p win can be found.
231  */
232 struct resource *
233 bhndb_host_resource_for_regwin(struct bhndb_host_resources *hr,
234     const struct bhndb_regwin *win)
235 {
236 	const struct resource_spec *rspecs;
237 
238 	rspecs = hr->resource_specs;
239 	for (u_int i = 0; rspecs[i].type != -1; i++) {
240 		if (win->res.type != rspecs[i].type)
241 			continue;
242 
243 		if (win->res.rid != rspecs[i].rid)
244 			continue;
245 
246 		/* Found declared resource */
247 		return (hr->resources[i]);
248 	}
249 
250 	device_printf(hr->owner, "missing regwin resource spec "
251 	    "(type=%d, rid=%d)\n", win->res.type, win->res.rid);
252 
253 	return (NULL);
254 }
255 
256 /**
257  * Allocate and initialize a new resource state structure.
258  *
259  * @param dev The bridge device.
260  * @param parent_dev The parent device from which host resources should be
261  * allocated.
262  * @param cfg The hardware configuration to be used.
263  */
264 struct bhndb_resources *
265 bhndb_alloc_resources(device_t dev, device_t parent_dev,
266     const struct bhndb_hwcfg *cfg)
267 {
268 	struct bhndb_resources		*r;
269 	const struct bhndb_regwin	*win;
270 	bus_size_t			 last_window_size;
271 	int				 rnid;
272 	int				 error;
273 	bool				 free_ht_mem, free_br_mem;
274 
275 	free_ht_mem = false;
276 	free_br_mem = false;
277 
278 	r = malloc(sizeof(*r), M_BHND, M_NOWAIT|M_ZERO);
279 	if (r == NULL)
280 		return (NULL);
281 
282 	/* Basic initialization */
283 	r->dev = dev;
284 	r->cfg = cfg;
285 	r->res = NULL;
286 	r->min_prio = BHNDB_PRIORITY_NONE;
287 	STAILQ_INIT(&r->bus_regions);
288 
289 	/* Initialize host address space resource manager. */
290 	r->ht_mem_rman.rm_start = 0;
291 	r->ht_mem_rman.rm_end = ~0;
292 	r->ht_mem_rman.rm_type = RMAN_ARRAY;
293 	r->ht_mem_rman.rm_descr = "BHNDB host memory";
294 	if ((error = rman_init(&r->ht_mem_rman))) {
295 		device_printf(r->dev, "could not initialize ht_mem_rman\n");
296 		goto failed;
297 	}
298 	free_ht_mem = true;
299 
300 
301 	/* Initialize resource manager for the bridged address space. */
302 	r->br_mem_rman.rm_start = 0;
303 	r->br_mem_rman.rm_end = BUS_SPACE_MAXADDR_32BIT;
304 	r->br_mem_rman.rm_type = RMAN_ARRAY;
305 	r->br_mem_rman.rm_descr = "BHNDB bridged memory";
306 
307 	if ((error = rman_init(&r->br_mem_rman))) {
308 		device_printf(r->dev, "could not initialize br_mem_rman\n");
309 		goto failed;
310 	}
311 	free_br_mem = true;
312 
313 	error = rman_manage_region(&r->br_mem_rman, 0, BUS_SPACE_MAXADDR_32BIT);
314 	if (error) {
315 		device_printf(r->dev, "could not configure br_mem_rman\n");
316 		goto failed;
317 	}
318 
319 	/* Fetch the dynamic regwin count and verify that it does not exceed
320 	 * what is representable via our freelist bitstring. */
321 	r->dwa_count = bhndb_regwin_count(cfg->register_windows,
322 	    BHNDB_REGWIN_T_DYN);
323 	if (r->dwa_count >= INT_MAX) {
324 		device_printf(r->dev, "max dynamic regwin count exceeded\n");
325 		goto failed;
326 	}
327 
328 	/* Allocate the dynamic window allocation table. */
329 	r->dw_alloc = malloc(sizeof(r->dw_alloc[0]) * r->dwa_count, M_BHND,
330 	    M_NOWAIT);
331 	if (r->dw_alloc == NULL)
332 		goto failed;
333 
334 	/* Allocate the dynamic window allocation freelist */
335 	r->dwa_freelist = bit_alloc(r->dwa_count, M_BHND, M_NOWAIT);
336 	if (r->dwa_freelist == NULL)
337 		goto failed;
338 
339 	/* Initialize the dynamic window table */
340 	rnid = 0;
341 	last_window_size = 0;
342 	for (win = cfg->register_windows;
343 	    win->win_type != BHNDB_REGWIN_T_INVALID; win++)
344 	{
345 		struct bhndb_dw_alloc *dwa;
346 
347 		/* Skip non-DYN windows */
348 		if (win->win_type != BHNDB_REGWIN_T_DYN)
349 			continue;
350 
351 		/* Validate the window size */
352 		if (win->win_size == 0) {
353 			device_printf(r->dev, "ignoring zero-length dynamic "
354 			    "register window\n");
355 			continue;
356 		} else if (last_window_size == 0) {
357 			last_window_size = win->win_size;
358 		} else if (last_window_size != win->win_size) {
359 			/*
360 			 * No existing hardware should trigger this.
361 			 *
362 			 * If you run into this in the future, the dynamic
363 			 * window allocator and the resource priority system
364 			 * will need to be extended to support multiple register
365 			 * window allocation pools.
366 			 */
367 			device_printf(r->dev, "devices that vend multiple "
368 			    "dynamic register window sizes are not currently "
369 			    "supported\n");
370 			goto failed;
371 		}
372 
373 		dwa = &r->dw_alloc[rnid];
374 		dwa->win = win;
375 		dwa->parent_res = NULL;
376 		dwa->rnid = rnid;
377 		dwa->target = 0x0;
378 
379 		LIST_INIT(&dwa->refs);
380 		rnid++;
381 	}
382 
383 	/* Allocate host resources */
384 	error = bhndb_alloc_host_resources(parent_dev, r->cfg, &r->res);
385 	if (error) {
386 		device_printf(r->dev,
387 		    "could not allocate host resources on %s: %d\n",
388 		    device_get_nameunit(parent_dev), error);
389 		goto failed;
390 	}
391 
392 	/* Populate (and validate) parent resource references for all
393 	 * dynamic windows */
394 	for (size_t i = 0; i < r->dwa_count; i++) {
395 		struct bhndb_dw_alloc		*dwa;
396 		const struct bhndb_regwin	*win;
397 
398 		dwa = &r->dw_alloc[i];
399 		win = dwa->win;
400 
401 		/* Find and validate corresponding resource. */
402 		dwa->parent_res = bhndb_host_resource_for_regwin(r->res, win);
403 		if (dwa->parent_res == NULL) {
404 			device_printf(r->dev, "no host resource found for %u "
405 			    "register window with offset %#jx and "
406 			    "size %#jx\n",
407 			    win->win_type,
408 			    (uintmax_t)win->win_offset,
409 			    (uintmax_t)win->win_size);
410 
411 			error = ENXIO;
412 			goto failed;
413 		}
414 
415 		if (rman_get_size(dwa->parent_res) < win->win_offset +
416 		    win->win_size)
417 		{
418 			device_printf(r->dev, "resource %d too small for "
419 			    "register window with offset %llx and size %llx\n",
420 			    rman_get_rid(dwa->parent_res),
421 			    (unsigned long long) win->win_offset,
422 			    (unsigned long long) win->win_size);
423 
424 			error = EINVAL;
425 			goto failed;
426 		}
427 	}
428 
429 	/* Add allocated memory resources to our host memory resource manager */
430 	for (u_int i = 0; r->res->resource_specs[i].type != -1; i++) {
431 		struct resource *res;
432 
433 		/* skip non-memory resources */
434 		if (r->res->resource_specs[i].type != SYS_RES_MEMORY)
435 			continue;
436 
437 		/* add host resource to set of managed regions */
438 		res = r->res->resources[i];
439 		error = rman_manage_region(&r->ht_mem_rman,
440 		    rman_get_start(res), rman_get_end(res));
441 		if (error) {
442 			device_printf(r->dev,
443 			    "could not register host memory region with "
444 			    "ht_mem_rman: %d\n", error);
445 			goto failed;
446 		}
447 	}
448 
449 	return (r);
450 
451 failed:
452 	if (free_ht_mem)
453 		rman_fini(&r->ht_mem_rman);
454 
455 	if (free_br_mem)
456 		rman_fini(&r->br_mem_rman);
457 
458 	if (r->dw_alloc != NULL)
459 		free(r->dw_alloc, M_BHND);
460 
461 	if (r->dwa_freelist != NULL)
462 		free(r->dwa_freelist, M_BHND);
463 
464 	if (r->res != NULL)
465 		bhndb_release_host_resources(r->res);
466 
467 	free(r, M_BHND);
468 
469 	return (NULL);
470 }
471 
472 /**
473  * Deallocate the given bridge resource structure and any associated resources.
474  *
475  * @param br Resource state to be deallocated.
476  */
477 void
478 bhndb_free_resources(struct bhndb_resources *br)
479 {
480 	struct bhndb_region	*region, *r_next;
481 	struct bhndb_dw_alloc	*dwa;
482 	struct bhndb_dw_rentry	*dwr, *dwr_next;
483 
484 	/* No window regions may still be held */
485 	if (!bhndb_dw_all_free(br)) {
486 		for (int i = 0; i < br->dwa_count; i++) {
487 			dwa = &br->dw_alloc[i];
488 
489 			/* Skip free dynamic windows */
490 			if (bhndb_dw_is_free(br, dwa))
491 				continue;
492 
493 			device_printf(br->dev,
494 			    "leaked dynamic register window %d\n", dwa->rnid);
495 		}
496 	}
497 
498 	/* Release host resources allocated through our parent. */
499 	if (br->res != NULL)
500 		bhndb_release_host_resources(br->res);
501 
502 	/* Clean up resource reservations */
503 	for (size_t i = 0; i < br->dwa_count; i++) {
504 		dwa = &br->dw_alloc[i];
505 
506 		LIST_FOREACH_SAFE(dwr, &dwa->refs, dw_link, dwr_next) {
507 			LIST_REMOVE(dwr, dw_link);
508 			free(dwr, M_BHND);
509 		}
510 	}
511 
512 	/* Release bus regions */
513 	STAILQ_FOREACH_SAFE(region, &br->bus_regions, link, r_next) {
514 		STAILQ_REMOVE(&br->bus_regions, region, bhndb_region, link);
515 		free(region, M_BHND);
516 	}
517 
518 	/* Release our resource managers */
519 	rman_fini(&br->ht_mem_rman);
520 	rman_fini(&br->br_mem_rman);
521 
522 	free(br->dw_alloc, M_BHND);
523 	free(br->dwa_freelist, M_BHND);
524 }
525 
526 /**
527  * Allocate host bus resources defined by @p hwcfg.
528  *
529  * On success, the caller assumes ownership of the allocated host resources,
530  * which must be freed via bhndb_release_host_resources().
531  *
532  * @param	dev		The device to be used when allocating resources
533  *				(e.g. via bus_alloc_resources()).
534  * @param	hwcfg		The hardware configuration defining the host
535  *				resources to be allocated
536  * @param[out]	resources	On success, the allocated host resources.
537  */
538 int
539 bhndb_alloc_host_resources(device_t dev, const struct bhndb_hwcfg *hwcfg,
540     struct bhndb_host_resources **resources)
541 {
542 	struct bhndb_host_resources	*hr;
543 	size_t				 nres;
544 	int				 error;
545 
546 	hr = malloc(sizeof(*hr), M_BHND, M_WAITOK);
547 	hr->owner = dev;
548 	hr->cfg = hwcfg;
549 	hr->resource_specs = NULL;
550 	hr->resources = NULL;
551 
552 	/* Determine our bridge resource count from the hardware config. */
553 	nres = 0;
554 	for (size_t i = 0; hwcfg->resource_specs[i].type != -1; i++)
555 		nres++;
556 
557 	/* Allocate space for a non-const copy of our resource_spec
558 	 * table; this will be updated with the RIDs assigned by
559 	 * bus_alloc_resources. */
560 	hr->resource_specs = malloc(sizeof(hr->resource_specs[0]) * (nres + 1),
561 	    M_BHND, M_WAITOK);
562 
563 	/* Initialize and terminate the table */
564 	for (size_t i = 0; i < nres; i++)
565 		hr->resource_specs[i] = hwcfg->resource_specs[i];
566 
567 	hr->resource_specs[nres].type = -1;
568 
569 	/* Allocate space for our resource references */
570 	hr->resources = malloc(sizeof(hr->resources[0]) * nres, M_BHND,
571 	    M_WAITOK);
572 
573 	/* Allocate host resources */
574 	error = bus_alloc_resources(hr->owner, hr->resource_specs,
575 	    hr->resources);
576 	if (error) {
577 		device_printf(dev, "could not allocate bridge resources via "
578 		    "%s: %d\n", device_get_nameunit(dev), error);
579 		goto failed;
580 	}
581 
582 	*resources = hr;
583 	return (0);
584 
585 failed:
586 	if (hr->resource_specs != NULL)
587 		free(hr->resource_specs, M_BHND);
588 
589 	if (hr->resources != NULL)
590 		free(hr->resources, M_BHND);
591 
592 	free(hr, M_BHND);
593 
594 	return (error);
595 }
596 
597 /**
598  * Deallocate a set of bridge host resources.
599  *
600  * @param hr The resources to be freed.
601  */
602 void
603 bhndb_release_host_resources(struct bhndb_host_resources *hr)
604 {
605 	bus_release_resources(hr->owner, hr->resource_specs, hr->resources);
606 
607 	free(hr->resources, M_BHND);
608 	free(hr->resource_specs, M_BHND);
609 	free(hr, M_BHND);
610 }
611 
612 
613 /**
614  * Search @p cores for the core serving as the bhnd host bridge.
615  *
616  * This function uses a heuristic valid on all known PCI/PCIe/PCMCIA-bridged
617  * bhnd(4) devices to determine the hostb core:
618  *
619  * - The core must have a Broadcom vendor ID.
620  * - The core devclass must match the bridge type.
621  * - The core must be the first device on the bus with the bridged device
622  *   class.
623  *
624  * @param	cores		The core table to search.
625  * @param	ncores		The number of cores in @p cores.
626  * @param	bridge_devclass	The expected device class of the bridge core.
627  * @param[out]	core		If found, the matching host bridge core info.
628  *
629  * @retval 0		success
630  * @retval ENOENT	not found
631  */
632 int
633 bhndb_find_hostb_core(struct bhnd_core_info *cores, u_int ncores,
634     bhnd_devclass_t bridge_devclass, struct bhnd_core_info *core)
635 {
636 	struct bhnd_core_match	 md;
637 	struct bhnd_core_info	*match;
638 	u_int			 match_core_idx;
639 
640 	/* Set up a match descriptor for the required device class. */
641 	md = (struct bhnd_core_match) {
642 		BHND_MATCH_CORE_CLASS(bridge_devclass),
643 		BHND_MATCH_CORE_UNIT(0)
644 	};
645 
646 	/* Find the matching core with the lowest core index */
647 	match = NULL;
648 	match_core_idx = UINT_MAX;
649 
650 	for (u_int i = 0; i < ncores; i++) {
651 		if (!bhnd_core_matches(&cores[i], &md))
652 			continue;
653 
654 		/* Lower core indices take precedence */
655 		if (match != NULL && match_core_idx < match->core_idx)
656 			continue;
657 
658 		match = &cores[i];
659 		match_core_idx = match->core_idx;
660 	}
661 
662 	if (match == NULL)
663 		return (ENOENT);
664 
665 	*core = *match;
666 	return (0);
667 }
668 
669 /**
670  * Add a bus region entry to @p r for the given base @p addr and @p size.
671  *
672  * @param br The resource state to which the bus region entry will be added.
673  * @param addr The base address of this region.
674  * @param size The size of this region.
675  * @param priority The resource priority to be assigned to allocations
676  * made within this bus region.
677  * @param static_regwin If available, a static register window mapping this
678  * bus region entry. If not available, NULL.
679  *
680  * @retval 0 success
681  * @retval non-zero if adding the bus region fails.
682  */
683 int
684 bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
685     bhnd_size_t size, bhndb_priority_t priority,
686     const struct bhndb_regwin *static_regwin)
687 {
688 	struct bhndb_region	*reg;
689 
690 	/* Insert in the bus resource list */
691 	reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT);
692 	if (reg == NULL)
693 		return (ENOMEM);
694 
695 	*reg = (struct bhndb_region) {
696 		.addr = addr,
697 		.size = size,
698 		.priority = priority,
699 		.static_regwin = static_regwin
700 	};
701 
702 	STAILQ_INSERT_HEAD(&br->bus_regions, reg, link);
703 
704 	return (0);
705 }
706 
707 
708 /**
709  * Find the maximum start and end limits of the register window mapping
710  * resource @p r.
711  *
712  * If the memory range is not mapped by an existing dynamic or static register
713  * window, ENOENT will be returned.
714  *
715  * @param br The resource state to search.
716  * @param r The resource to search for in @p br.
717  * @param addr The requested starting address.
718  * @param size The requested size.
719  *
720  * @retval bhndb_region A region that fully contains the requested range.
721  * @retval NULL If no mapping region can be found.
722  */
723 int
724 bhndb_find_resource_limits(struct bhndb_resources *br, struct resource *r,
725     rman_res_t *start, rman_res_t *end)
726 {
727 	struct bhndb_dw_alloc	*dynamic;
728 	struct bhndb_region	*sregion;
729 
730 	/* Check for an enclosing dynamic register window */
731 	if ((dynamic = bhndb_dw_find_resource(br, r))) {
732 		*start = dynamic->target;
733 		*end = dynamic->target + dynamic->win->win_size - 1;
734 		return (0);
735 	}
736 
737 	/* Check for a static region */
738 	sregion = bhndb_find_resource_region(br, rman_get_start(r),
739 	    rman_get_size(r));
740 	if (sregion != NULL && sregion->static_regwin != NULL) {
741 		*start = sregion->addr;
742 		*end = sregion->addr + sregion->size - 1;
743 
744 		return (0);
745 	}
746 
747 	/* Not found */
748 	return (ENOENT);
749 }
750 
751 /**
752  * Find the bus region that maps @p size bytes at @p addr.
753  *
754  * @param br The resource state to search.
755  * @param addr The requested starting address.
756  * @param size The requested size.
757  *
758  * @retval bhndb_region A region that fully contains the requested range.
759  * @retval NULL If no mapping region can be found.
760  */
761 struct bhndb_region *
762 bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
763     bhnd_size_t size)
764 {
765 	struct bhndb_region *region;
766 
767 	STAILQ_FOREACH(region, &br->bus_regions, link) {
768 		/* Request must fit within the region's mapping  */
769 		if (addr < region->addr)
770 			continue;
771 
772 		if (addr + size > region->addr + region->size)
773 			continue;
774 
775 		return (region);
776 	}
777 
778 	/* Not found */
779 	return (NULL);
780 }
781 
782 /**
783  * Find the entry matching @p r in @p dwa's references, if any.
784  *
785  * @param dwa The dynamic window allocation to search
786  * @param r The resource to search for in @p dwa.
787  */
788 static struct bhndb_dw_rentry *
789 bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r)
790 {
791 	struct bhndb_dw_rentry	*rentry;
792 
793 	LIST_FOREACH(rentry, &dwa->refs, dw_link) {
794 		struct resource *dw_res = rentry->dw_res;
795 
796 		/* Match dev/rid/addr/size */
797 		if (rman_get_device(dw_res)	!= rman_get_device(r) ||
798 			rman_get_rid(dw_res)	!= rman_get_rid(r) ||
799 			rman_get_start(dw_res)	!= rman_get_start(r) ||
800 			rman_get_size(dw_res)	!= rman_get_size(r))
801 		{
802 			continue;
803 		}
804 
805 		/* Matching allocation found */
806 		return (rentry);
807 	}
808 
809 	return (NULL);
810 }
811 
812 /**
813  * Find the dynamic region allocated for @p r, if any.
814  *
815  * @param br The resource state to search.
816  * @param r The resource to search for.
817  *
818  * @retval bhndb_dw_alloc The allocation record for @p r.
819  * @retval NULL if no dynamic window is allocated for @p r.
820  */
821 struct bhndb_dw_alloc *
822 bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r)
823 {
824 	struct bhndb_dw_alloc	*dwa;
825 
826 	for (size_t i = 0; i < br->dwa_count; i++) {
827 		dwa = &br->dw_alloc[i];
828 
829 		/* Skip free dynamic windows */
830 		if (bhndb_dw_is_free(br, dwa))
831 			continue;
832 
833 		/* Matching allocation found? */
834 		if (bhndb_dw_find_resource_entry(dwa, r) != NULL)
835 			return (dwa);
836 	}
837 
838 	return (NULL);
839 }
840 
841 /**
842  * Find an existing dynamic window mapping @p size bytes
843  * at @p addr. The window may or may not be free.
844  *
845  * @param br The resource state to search.
846  * @param addr The requested starting address.
847  * @param size The requested size.
848  *
849  * @retval bhndb_dw_alloc A window allocation that fully contains the requested
850  * range.
851  * @retval NULL If no mapping region can be found.
852  */
853 struct bhndb_dw_alloc *
854 bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr,
855     bhnd_size_t size)
856 {
857 	struct bhndb_dw_alloc		*dwr;
858 	const struct bhndb_regwin	*win;
859 
860 	/* Search for an existing dynamic mapping of this address range. */
861 	for (size_t i = 0; i < br->dwa_count; i++) {
862 		dwr = &br->dw_alloc[i];
863 		win = dwr->win;
864 
865 		/* Verify the range */
866 		if (addr < dwr->target)
867 			continue;
868 
869 		if (addr + size > dwr->target + win->win_size)
870 			continue;
871 
872 		/* Found a usable mapping */
873 		return (dwr);
874 	}
875 
876 	/* not found */
877 	return (NULL);
878 }
879 
880 /**
881  * Retain a reference to @p dwa for use by @p res.
882  *
883  * @param br The resource state owning @p dwa.
884  * @param dwa The allocation record to be retained.
885  * @param res The resource that will own a reference to @p dwa.
886  *
887  * @retval 0 success
888  * @retval ENOMEM Failed to allocate a new reference structure.
889  */
890 int
891 bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
892     struct resource *res)
893 {
894 	struct bhndb_dw_rentry *rentry;
895 
896 	KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL,
897 	    ("double-retain of dynamic window for same resource"));
898 
899 	/* Insert a reference entry; we use M_NOWAIT to allow use from
900 	 * within a non-sleepable lock */
901 	rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT);
902 	if (rentry == NULL)
903 		return (ENOMEM);
904 
905 	rentry->dw_res = res;
906 	LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link);
907 
908 	/* Update the free list */
909 	bit_set(br->dwa_freelist, dwa->rnid);
910 
911 	return (0);
912 }
913 
914 /**
915  * Release a reference to @p dwa previously retained by @p res. If the
916  * reference count of @p dwa reaches zero, it will be added to the
917  * free list.
918  *
919  * @param br The resource state owning @p dwa.
920  * @param dwa The allocation record to be released.
921  * @param res The resource that currently owns a reference to @p dwa.
922  */
923 void
924 bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
925     struct resource *r)
926 {
927 	struct bhndb_dw_rentry	*rentry;
928 
929 	/* Find the rentry */
930 	rentry = bhndb_dw_find_resource_entry(dwa, r);
931 	KASSERT(rentry != NULL, ("over release of resource entry"));
932 
933 	LIST_REMOVE(rentry, dw_link);
934 	free(rentry, M_BHND);
935 
936 	/* If this was the last reference, update the free list */
937 	if (LIST_EMPTY(&dwa->refs))
938 		bit_clear(br->dwa_freelist, dwa->rnid);
939 }
940 
941 /**
942  * Attempt to set (or reset) the target address of @p dwa to map @p size bytes
943  * at @p addr.
944  *
945  * This will apply any necessary window alignment and verify that
946  * the window is capable of mapping the requested range prior to modifying
947  * therecord.
948  *
949  * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request.
950  * @param br The resource state owning @p dwa.
951  * @param dwa The allocation record to be configured.
952  * @param addr The address to be mapped via @p dwa.
953  * @param size The number of bytes to be mapped at @p addr.
954  *
955  * @retval 0 success
956  * @retval non-zero no usable register window available.
957  */
958 int
959 bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
960     struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size)
961 {
962 	const struct bhndb_regwin	*rw;
963 	bus_addr_t			 offset;
964 	int				 error;
965 
966 	rw = dwa->win;
967 
968 	KASSERT(bhndb_dw_is_free(br, dwa),
969 	    ("attempting to set the target address on an in-use window"));
970 
971 	/* Page-align the target address */
972 	offset = addr % rw->win_size;
973 	dwa->target = addr - offset;
974 
975 	/* Verify that the window is large enough for the full target */
976 	if (rw->win_size - offset < size)
977 		return (ENOMEM);
978 
979 	/* Update the window target */
980 	error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target);
981 	if (error) {
982 		dwa->target = 0x0;
983 		return (error);
984 	}
985 
986 	return (0);
987 }
988 
989 /**
990  * Return the count of @p type register windows in @p table.
991  *
992  * @param table The table to search.
993  * @param type The required window type, or BHNDB_REGWIN_T_INVALID to
994  * count all register window types.
995  */
996 size_t
997 bhndb_regwin_count(const struct bhndb_regwin *table,
998     bhndb_regwin_type_t type)
999 {
1000 	const struct bhndb_regwin	*rw;
1001 	size_t				 count;
1002 
1003 	count = 0;
1004 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) {
1005 		if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type)
1006 			count++;
1007 	}
1008 
1009 	return (count);
1010 }
1011 
1012 /**
1013  * Search @p table for the first window with the given @p type.
1014  *
1015  * @param table The table to search.
1016  * @param type The required window type.
1017  * @param min_size The minimum window size.
1018  *
1019  * @retval bhndb_regwin The first matching window.
1020  * @retval NULL If no window of the requested type could be found.
1021  */
1022 const struct bhndb_regwin *
1023 bhndb_regwin_find_type(const struct bhndb_regwin *table,
1024     bhndb_regwin_type_t type, bus_size_t min_size)
1025 {
1026 	const struct bhndb_regwin *rw;
1027 
1028 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
1029 	{
1030 		if (rw->win_type == type && rw->win_size >= min_size)
1031 			return (rw);
1032 	}
1033 
1034 	return (NULL);
1035 }
1036 
1037 /**
1038  * Search @p windows for the first matching core window.
1039  *
1040  * @param table The table to search.
1041  * @param class The required core class.
1042  * @param unit The required core unit, or -1.
1043  * @param port_type The required port type.
1044  * @param port The required port.
1045  * @param region The required region.
1046  *
1047  * @retval bhndb_regwin The first matching window.
1048  * @retval NULL If no matching window was found.
1049  */
1050 const struct bhndb_regwin *
1051 bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
1052     int unit, bhnd_port_type port_type, u_int port, u_int region)
1053 {
1054 	const struct bhndb_regwin *rw;
1055 
1056 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
1057 	{
1058 		if (rw->win_type != BHNDB_REGWIN_T_CORE)
1059 			continue;
1060 
1061 		if (rw->d.core.class != class)
1062 			continue;
1063 
1064 		if (unit != -1 && rw->d.core.unit != unit)
1065 			continue;
1066 
1067 		if (rw->d.core.port_type != port_type)
1068 			continue;
1069 
1070 		if (rw->d.core.port != port)
1071 			continue;
1072 
1073 		if (rw->d.core.region != region)
1074 			continue;
1075 
1076 		return (rw);
1077 	}
1078 
1079 	return (NULL);
1080 }
1081 
1082 /**
1083  * Search @p windows for the best available window of at least @p min_size.
1084  *
1085  * Search order:
1086  * - BHND_REGWIN_T_CORE
1087  * - BHND_REGWIN_T_DYN
1088  *
1089  * @param table The table to search.
1090  * @param class The required core class.
1091  * @param unit The required core unit, or -1.
1092  * @param port_type The required port type.
1093  * @param port The required port.
1094  * @param region The required region.
1095  * @param min_size The minimum window size.
1096  *
1097  * @retval bhndb_regwin The first matching window.
1098  * @retval NULL If no matching window was found.
1099  */
1100 const struct bhndb_regwin *
1101 bhndb_regwin_find_best(const struct bhndb_regwin *table,
1102     bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port,
1103     u_int region, bus_size_t min_size)
1104 {
1105 	const struct bhndb_regwin *rw;
1106 
1107 	/* Prefer a fixed core mapping */
1108 	rw = bhndb_regwin_find_core(table, class, unit, port_type,
1109 	    port, region);
1110 	if (rw != NULL)
1111 		return (rw);
1112 
1113 	/* Fall back on a generic dynamic window */
1114 	return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size));
1115 }
1116 
1117 /**
1118  * Return true if @p regw defines a BHNDB_REGWIN_T_CORE register window
1119  * that matches against @p core.
1120  *
1121  * @param regw A register window to match against.
1122  * @param core The bhnd(4) core info to match against @p regw.
1123  */
1124 bool
1125 bhndb_regwin_match_core(const struct bhndb_regwin *regw,
1126     struct bhnd_core_info *core)
1127 {
1128 	/* Only core windows are supported */
1129 	if (regw->win_type != BHNDB_REGWIN_T_CORE)
1130 		return (false);
1131 
1132 	/* Device class must match */
1133 	if (bhnd_core_class(core) != regw->d.core.class)
1134 		return (false);
1135 
1136 	/* Device unit must match */
1137 	if (core->unit != regw->d.core.unit)
1138 		return (false);
1139 
1140 	/* Matches */
1141 	return (true);
1142 }
1143 
1144 /**
1145  * Search for a core resource priority descriptor in @p table that matches
1146  * @p core.
1147  *
1148  * @param table The table to search.
1149  * @param core The core to match against @p table.
1150  */
1151 const struct bhndb_hw_priority *
1152 bhndb_hw_priority_find_core(const struct bhndb_hw_priority *table,
1153     struct bhnd_core_info *core)
1154 {
1155 	const struct bhndb_hw_priority	*hp;
1156 
1157 	for (hp = table; hp->ports != NULL; hp++) {
1158 		if (bhnd_core_matches(core, &hp->match))
1159 			return (hp);
1160 	}
1161 
1162 	/* not found */
1163 	return (NULL);
1164 }
1165