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