xref: /freebsd/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 86c9d9918f1db7cdd968b60f8902466887bcd9e9)
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 
36 #include "bhndb_private.h"
37 #include "bhndbvar.h"
38 
39 /**
40  * Attach a BHND bridge device to @p parent.
41  *
42  * @param parent A parent PCI device.
43  * @param[out] bhndb On success, the probed and attached bhndb bridge device.
44  * @param unit The device unit number, or -1 to select the next available unit
45  * number.
46  *
47  * @retval 0 success
48  * @retval non-zero Failed to attach the bhndb device.
49  */
50 int
51 bhndb_attach_bridge(device_t parent, device_t *bhndb, int unit)
52 {
53 	int error;
54 
55 	*bhndb = device_add_child(parent, "bhndb", unit);
56 	if (*bhndb == NULL)
57 		return (ENXIO);
58 
59 	if (!(error = device_probe_and_attach(*bhndb)))
60 		return (0);
61 
62 	if ((device_delete_child(parent, *bhndb)))
63 		device_printf(parent, "failed to detach bhndb child\n");
64 
65 	return (error);
66 }
67 
68 /*
69  * Call BHNDB_SUSPEND_RESOURCE() for all resources in @p rl.
70  */
71 static void
72 bhndb_do_suspend_resources(device_t dev, struct resource_list *rl)
73 {
74 	struct resource_list_entry *rle;
75 
76 	/* Suspend all child resources. */
77 	STAILQ_FOREACH(rle, rl, link) {
78 		/* Skip non-allocated resources */
79 		if (rle->res == NULL)
80 			continue;
81 
82 		BHNDB_SUSPEND_RESOURCE(device_get_parent(dev), dev, rle->type,
83 		    rle->res);
84 	}
85 }
86 
87 /**
88  * Helper function for implementing BUS_RESUME_CHILD() on bridged
89  * bhnd(4) buses.
90  *
91  * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST()
92  * to find the child's resources and call BHNDB_SUSPEND_RESOURCE() for all
93  * child resources, ensuring that the device's allocated bridge resources
94  * will be available to other devices during bus resumption.
95  *
96  * Before suspending any resources, @p child is suspended by
97  * calling bhnd_generic_suspend_child().
98  *
99  * If @p child is not a direct child of @p dev, suspension is delegated to
100  * the @p dev parent.
101  */
102 int
103 bhnd_generic_br_suspend_child(device_t dev, device_t child)
104 {
105 	struct resource_list		*rl;
106 	int				 error;
107 
108 	if (device_get_parent(child) != dev)
109 		BUS_SUSPEND_CHILD(device_get_parent(dev), child);
110 
111 	if (device_is_suspended(child))
112 		return (EBUSY);
113 
114 	/* Suspend the child device */
115 	if ((error = bhnd_generic_suspend_child(dev, child)))
116 		return (error);
117 
118 	/* Fetch the resource list. If none, there's nothing else to do */
119 	rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
120 	if (rl == NULL)
121 		return (0);
122 
123 	/* Suspend all child resources. */
124 	bhndb_do_suspend_resources(dev, rl);
125 
126 	return (0);
127 }
128 
129 /**
130  * Helper function for implementing BUS_RESUME_CHILD() on bridged
131  * bhnd(4) bus devices.
132  *
133  * This implementation of BUS_RESUME_CHILD() uses BUS_GET_RESOURCE_LIST()
134  * to find the child's resources and call BHNDB_RESUME_RESOURCE() for all
135  * child resources, before delegating to bhnd_generic_resume_child().
136  *
137  * If resource resumption fails, @p child will not be resumed.
138  *
139  * If @p child is not a direct child of @p dev, suspension is delegated to
140  * the @p dev parent.
141  */
142 int
143 bhnd_generic_br_resume_child(device_t dev, device_t child)
144 {
145 	struct resource_list		*rl;
146 	struct resource_list_entry	*rle;
147 	int				 error;
148 
149 	if (device_get_parent(child) != dev)
150 		BUS_RESUME_CHILD(device_get_parent(dev), child);
151 
152 	if (!device_is_suspended(child))
153 		return (EBUSY);
154 
155 	/* Fetch the resource list. If none, there's nothing else to do */
156 	rl = BUS_GET_RESOURCE_LIST(device_get_parent(child), child);
157 	if (rl == NULL)
158 		return (bhnd_generic_resume_child(dev, child));
159 
160 	/* Resume all resources */
161 	STAILQ_FOREACH(rle, rl, link) {
162 		/* Skip non-allocated resources */
163 		if (rle->res == NULL)
164 			continue;
165 
166 		error = BHNDB_RESUME_RESOURCE(device_get_parent(dev), dev,
167 		    rle->type, rle->res);
168 		if (error) {
169 			/* Put all resources back into a suspend state */
170 			bhndb_do_suspend_resources(dev, rl);
171 			return (error);
172 		}
173 	}
174 
175 	/* Now that all resources are resumed, resume child */
176 	if ((error = bhnd_generic_resume_child(dev, child))) {
177 		/* Put all resources back into a suspend state */
178 		bhndb_do_suspend_resources(dev, rl);
179 	}
180 
181 	return (error);
182 }
183 
184 /**
185  * Find a SYS_RES_MEMORY resource containing the given address range.
186  *
187  * @param br The bhndb resource state to search.
188  * @param start The start address of the range to search for.
189  * @param count The size of the range to search for.
190  *
191  * @retval resource the host resource containing the requested range.
192  * @retval NULL if no resource containing the requested range can be found.
193  */
194 struct resource *
195 bhndb_find_resource_range(struct bhndb_resources *br, rman_res_t start,
196      rman_res_t count)
197 {
198 	for (u_int i = 0; br->res_spec[i].type != -1; i++) {
199 		struct resource *r = br->res[i];
200 
201 		if (br->res_spec->type != SYS_RES_MEMORY)
202 			continue;
203 
204 		/* Verify range */
205 		if (rman_get_start(r) > start)
206 			continue;
207 
208 		if (rman_get_end(r) < (start + count - 1))
209 			continue;
210 
211 		return (r);
212 	}
213 
214 	return (NULL);
215 }
216 
217 /**
218  * Find the resource containing @p win.
219  *
220  * @param br The bhndb resource state to search.
221  * @param win A register window.
222  *
223  * @retval resource the resource containing @p win.
224  * @retval NULL if no resource containing @p win can be found.
225  */
226 struct resource *
227 bhndb_find_regwin_resource(struct bhndb_resources *br,
228     const struct bhndb_regwin *win)
229 {
230 	const struct resource_spec *rspecs;
231 
232 	rspecs = br->cfg->resource_specs;
233 	for (u_int i = 0; rspecs[i].type != -1; i++) {
234 		if (win->res.type != rspecs[i].type)
235 			continue;
236 
237 		if (win->res.rid != rspecs[i].rid)
238 			continue;
239 
240 		/* Found declared resource */
241 		return (br->res[i]);
242 	}
243 
244 	device_printf(br->dev,
245 	    "missing regwin resource spec (type=%d, rid=%d)\n",
246 	    win->res.type, win->res.rid);
247 
248 	return (NULL);
249 }
250 
251 /**
252  * Allocate and initialize a new resource state structure, allocating
253  * bus resources from @p parent_dev according to @p cfg.
254  *
255  * @param dev The bridge device.
256  * @param parent_dev The parent device from which resources will be allocated.
257  * @param cfg The hardware configuration to be used.
258  */
259 struct bhndb_resources *
260 bhndb_alloc_resources(device_t dev, device_t parent_dev,
261     const struct bhndb_hwcfg *cfg)
262 {
263 	struct bhndb_resources		*r;
264 	const struct bhndb_regwin	*win;
265 	bus_size_t			 last_window_size;
266 	size_t				 res_num;
267 	u_int				 rnid;
268 	int				 error;
269 	bool				 free_parent_res;
270 	bool				 free_ht_mem, free_br_mem;
271 
272 	free_parent_res = false;
273 	free_ht_mem = false;
274 	free_br_mem = false;
275 
276 	r = malloc(sizeof(*r), M_BHND, M_NOWAIT|M_ZERO);
277 	if (r == NULL)
278 		return (NULL);
279 
280 	/* Basic initialization */
281 	r->dev = dev;
282 	r->parent_dev = parent_dev;
283 	r->cfg = cfg;
284 	r->min_prio = BHNDB_PRIORITY_NONE;
285 	STAILQ_INIT(&r->bus_regions);
286 
287 	/* Initialize host address space resource manager. */
288 	r->ht_mem_rman.rm_start = 0;
289 	r->ht_mem_rman.rm_end = ~0;
290 	r->ht_mem_rman.rm_type = RMAN_ARRAY;
291 	r->ht_mem_rman.rm_descr = "BHNDB host memory";
292 	if ((error = rman_init(&r->ht_mem_rman))) {
293 		device_printf(r->dev, "could not initialize ht_mem_rman\n");
294 		goto failed;
295 	}
296 	free_ht_mem = true;
297 
298 
299 	/* Initialize resource manager for the bridged address space. */
300 	r->br_mem_rman.rm_start = 0;
301 	r->br_mem_rman.rm_end = BUS_SPACE_MAXADDR_32BIT;
302 	r->br_mem_rman.rm_type = RMAN_ARRAY;
303 	r->br_mem_rman.rm_descr = "BHNDB bridged memory";
304 
305 	if ((error = rman_init(&r->br_mem_rman))) {
306 		device_printf(r->dev, "could not initialize br_mem_rman\n");
307 		goto failed;
308 	}
309 	free_br_mem = true;
310 
311 	error = rman_manage_region(&r->br_mem_rman, 0, BUS_SPACE_MAXADDR_32BIT);
312 	if (error) {
313 		device_printf(r->dev, "could not configure br_mem_rman\n");
314 		goto failed;
315 	}
316 
317 
318 	/* Determine our bridge resource count from the hardware config. */
319 	res_num = 0;
320 	for (size_t i = 0; cfg->resource_specs[i].type != -1; i++)
321 		res_num++;
322 
323 	/* Allocate space for a non-const copy of our resource_spec
324 	 * table; this will be updated with the RIDs assigned by
325 	 * bus_alloc_resources. */
326 	r->res_spec = malloc(sizeof(r->res_spec[0]) * (res_num + 1), M_BHND,
327 	    M_NOWAIT);
328 	if (r->res_spec == NULL)
329 		goto failed;
330 
331 	/* Initialize and terminate the table */
332 	for (size_t i = 0; i < res_num; i++)
333 		r->res_spec[i] = cfg->resource_specs[i];
334 
335 	r->res_spec[res_num].type = -1;
336 
337 	/* Allocate space for our resource references */
338 	r->res = malloc(sizeof(r->res[0]) * res_num, M_BHND, M_NOWAIT);
339 	if (r->res == NULL)
340 		goto failed;
341 
342 	/* Allocate resources */
343 	error = bus_alloc_resources(r->parent_dev, r->res_spec, r->res);
344 	if (error) {
345 		device_printf(r->dev,
346 		    "could not allocate bridge resources on %s: %d\n",
347 		    device_get_nameunit(r->parent_dev), error);
348 		goto failed;
349 	} else {
350 		free_parent_res = true;
351 	}
352 
353 	/* Add allocated memory resources to our host memory resource manager */
354 	for (u_int i = 0; r->res_spec[i].type != -1; i++) {
355 		struct resource *res;
356 
357 		/* skip non-memory resources */
358 		if (r->res_spec[i].type != SYS_RES_MEMORY)
359 			continue;
360 
361 		/* add host resource to set of managed regions */
362 		res = r->res[i];
363 		error = rman_manage_region(&r->ht_mem_rman, rman_get_start(res),
364 		    rman_get_end(res));
365 		if (error) {
366 			device_printf(r->dev,
367 			    "could not register host memory region with "
368 			    "ht_mem_rman: %d\n", error);
369 			goto failed;
370 		}
371 	}
372 
373 	/* Fetch the dynamic regwin count and verify that it does not exceed
374 	 * what is representable via our freelist bitmask. */
375 	r->dwa_count = bhndb_regwin_count(cfg->register_windows,
376 	    BHNDB_REGWIN_T_DYN);
377 	if (r->dwa_count >= (8 * sizeof(r->dwa_freelist))) {
378 		device_printf(r->dev, "max dynamic regwin count exceeded\n");
379 		goto failed;
380 	}
381 
382 	/* Allocate the dynamic window allocation table. */
383 	r->dw_alloc = malloc(sizeof(r->dw_alloc[0]) * r->dwa_count, M_BHND,
384 	    M_NOWAIT);
385 	if (r->dw_alloc == NULL)
386 		goto failed;
387 
388 	/* Initialize the dynamic window table and freelist. */
389 	r->dwa_freelist = 0;
390 	rnid = 0;
391 	last_window_size = 0;
392 	for (win = cfg->register_windows;
393 	    win->win_type != BHNDB_REGWIN_T_INVALID; win++)
394 	{
395 		struct bhndb_dw_alloc *dwa;
396 
397 		/* Skip non-DYN windows */
398 		if (win->win_type != BHNDB_REGWIN_T_DYN)
399 			continue;
400 
401 		/* Validate the window size */
402 		if (win->win_size == 0) {
403 			device_printf(r->dev, "ignoring zero-length dynamic "
404 			    "register window\n");
405 			continue;
406 		} else if (last_window_size == 0) {
407 			last_window_size = win->win_size;
408 		} else if (last_window_size != win->win_size) {
409 			/*
410 			 * No existing hardware should trigger this.
411 			 *
412 			 * If you run into this in the future, the dynamic
413 			 * window allocator and the resource priority system
414 			 * will need to be extended to support multiple register
415 			 * window allocation pools.
416 			 */
417 			device_printf(r->dev, "devices that vend multiple "
418 			    "dynamic register window sizes are not currently "
419 			    "supported\n");
420 			goto failed;
421 		}
422 
423 		dwa = &r->dw_alloc[rnid];
424 		dwa->win = win;
425 		dwa->parent_res = NULL;
426 		dwa->rnid = rnid;
427 		dwa->target = 0x0;
428 
429 		LIST_INIT(&dwa->refs);
430 
431 		/* Find and validate corresponding resource. */
432 		dwa->parent_res = bhndb_find_regwin_resource(r, win);
433 		if (dwa->parent_res == NULL)
434 			goto failed;
435 
436 		if (rman_get_size(dwa->parent_res) < win->win_offset +
437 		    win->win_size)
438 		{
439 			device_printf(r->dev, "resource %d too small for "
440 			    "register window with offset %llx and size %llx\n",
441 			    rman_get_rid(dwa->parent_res),
442 			    (unsigned long long) win->win_offset,
443 			    (unsigned long long) win->win_size);
444 
445 			error = EINVAL;
446 			goto failed;
447 		}
448 
449 		/* Add to freelist */
450 		r->dwa_freelist |= (1 << rnid);
451 
452 		rnid++;
453 	}
454 
455 	return (r);
456 
457 failed:
458 	if (free_parent_res)
459 		bus_release_resources(r->parent_dev, r->res_spec, r->res);
460 
461 	if (free_ht_mem)
462 		rman_fini(&r->ht_mem_rman);
463 
464 	if (free_br_mem)
465 		rman_fini(&r->br_mem_rman);
466 
467 	if (r->res != NULL)
468 		free(r->res, M_BHND);
469 
470 	if (r->res_spec != NULL)
471 		free(r->res_spec, M_BHND);
472 
473 	if (r->dw_alloc != NULL)
474 		free(r->dw_alloc, M_BHND);
475 
476 	free (r, M_BHND);
477 
478 	return (NULL);
479 }
480 
481 /**
482  * Deallocate the given bridge resource structure and any associated resources.
483  *
484  * @param br Resource state to be deallocated.
485  */
486 void
487 bhndb_free_resources(struct bhndb_resources *br)
488 {
489 	struct bhndb_region	*region, *r_next;
490 	struct bhndb_dw_alloc	*dwa;
491 	struct bhndb_dw_rentry	*dwr, *dwr_next;
492 
493 	/* No window regions may still be held */
494 	if (__builtin_popcount(br->dwa_freelist) != br->dwa_count) {
495 		device_printf(br->dev, "leaked %llu dynamic register regions\n",
496 		    (unsigned long long) br->dwa_count - br->dwa_freelist);
497 	}
498 
499 	/* Release resources allocated through our parent. */
500 	bus_release_resources(br->parent_dev, br->res_spec, 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 backing resource state structures */
523 	free(br->res, M_BHND);
524 	free(br->res_spec, M_BHND);
525 	free(br->dw_alloc, M_BHND);
526 }
527 
528 /**
529  * Add a bus region entry to @p r for the given base @p addr and @p size.
530  *
531  * @param br The resource state to which the bus region entry will be added.
532  * @param addr The base address of this region.
533  * @param size The size of this region.
534  * @param priority The resource priority to be assigned to allocations
535  * made within this bus region.
536  * @param static_regwin If available, a static register window mapping this
537  * bus region entry. If not available, NULL.
538  *
539  * @retval 0 success
540  * @retval non-zero if adding the bus region fails.
541  */
542 int
543 bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
544     bhnd_size_t size, bhndb_priority_t priority,
545     const struct bhndb_regwin *static_regwin)
546 {
547 	struct bhndb_region	*reg;
548 
549 	/* Insert in the bus resource list */
550 	reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT);
551 	if (reg == NULL)
552 		return (ENOMEM);
553 
554 	*reg = (struct bhndb_region) {
555 		.addr = addr,
556 		.size = size,
557 		.priority = priority,
558 		.static_regwin = static_regwin
559 	};
560 
561 	STAILQ_INSERT_HEAD(&br->bus_regions, reg, link);
562 
563 	return (0);
564 }
565 
566 
567 /**
568  * Find the maximum start and end limits of the register window mapping
569  * resource @p r.
570  *
571  * If the memory range is not mapped by an existing dynamic or static register
572  * window, ENOENT will be returned.
573  *
574  * @param br The resource state to search.
575  * @param r The resource to search for in @p br.
576  * @param addr The requested starting address.
577  * @param size The requested size.
578  *
579  * @retval bhndb_region A region that fully contains the requested range.
580  * @retval NULL If no mapping region can be found.
581  */
582 int
583 bhndb_find_resource_limits(struct bhndb_resources *br, struct resource *r,
584     rman_res_t *start, rman_res_t *end)
585 {
586 	struct bhndb_dw_alloc	*dynamic;
587 	struct bhndb_region	*sregion;
588 
589 	/* Check for an enclosing dynamic register window */
590 	if ((dynamic = bhndb_dw_find_resource(br, r))) {
591 		*start = dynamic->target;
592 		*end = dynamic->target + dynamic->win->win_size - 1;
593 		return (0);
594 	}
595 
596 	/* Check for a static region */
597 	sregion = bhndb_find_resource_region(br, rman_get_start(r),
598 	    rman_get_size(r));
599 	if (sregion != NULL && sregion->static_regwin != NULL) {
600 		*start = sregion->addr;
601 		*end = sregion->addr + sregion->size - 1;
602 
603 		return (0);
604 	}
605 
606 	/* Not found */
607 	return (ENOENT);
608 }
609 
610 /**
611  * Find the bus region that maps @p size bytes at @p addr.
612  *
613  * @param br The resource state to search.
614  * @param addr The requested starting address.
615  * @param size The requested size.
616  *
617  * @retval bhndb_region A region that fully contains the requested range.
618  * @retval NULL If no mapping region can be found.
619  */
620 struct bhndb_region *
621 bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
622     bhnd_size_t size)
623 {
624 	struct bhndb_region *region;
625 
626 	STAILQ_FOREACH(region, &br->bus_regions, link) {
627 		/* Request must fit within the region's mapping  */
628 		if (addr < region->addr)
629 			continue;
630 
631 		if (addr + size > region->addr + region->size)
632 			continue;
633 
634 		return (region);
635 	}
636 
637 	/* Not found */
638 	return (NULL);
639 }
640 
641 /**
642  * Find the entry matching @p r in @p dwa's references, if any.
643  *
644  * @param dwa The dynamic window allocation to search
645  * @param r The resource to search for in @p dwa.
646  */
647 static struct bhndb_dw_rentry *
648 bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r)
649 {
650 	struct bhndb_dw_rentry	*rentry;
651 
652 	LIST_FOREACH(rentry, &dwa->refs, dw_link) {
653 		struct resource *dw_res = rentry->dw_res;
654 
655 		/* Match dev/rid/addr/size */
656 		if (rman_get_device(dw_res)	!= rman_get_device(r) ||
657 			rman_get_rid(dw_res)	!= rman_get_rid(r) ||
658 			rman_get_start(dw_res)	!= rman_get_start(r) ||
659 			rman_get_size(dw_res)	!= rman_get_size(r))
660 		{
661 			continue;
662 		}
663 
664 		/* Matching allocation found */
665 		return (rentry);
666 	}
667 
668 	return (NULL);
669 }
670 
671 /**
672  * Find the dynamic region allocated for @p r, if any.
673  *
674  * @param br The resource state to search.
675  * @param r The resource to search for.
676  *
677  * @retval bhndb_dw_alloc The allocation record for @p r.
678  * @retval NULL if no dynamic window is allocated for @p r.
679  */
680 struct bhndb_dw_alloc *
681 bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r)
682 {
683 	struct bhndb_dw_alloc	*dwa;
684 
685 	for (size_t i = 0; i < br->dwa_count; i++) {
686 		dwa = &br->dw_alloc[i];
687 
688 		/* Skip free dynamic windows */
689 		if (bhndb_dw_is_free(br, dwa))
690 			continue;
691 
692 		/* Matching allocation found? */
693 		if (bhndb_dw_find_resource_entry(dwa, r) != NULL)
694 			return (dwa);
695 	}
696 
697 	return (NULL);
698 }
699 
700 /**
701  * Find an existing dynamic window mapping @p size bytes
702  * at @p addr. The window may or may not be free.
703  *
704  * @param br The resource state to search.
705  * @param addr The requested starting address.
706  * @param size The requested size.
707  *
708  * @retval bhndb_dw_alloc A window allocation that fully contains the requested
709  * range.
710  * @retval NULL If no mapping region can be found.
711  */
712 struct bhndb_dw_alloc *
713 bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr,
714     bhnd_size_t size)
715 {
716 	struct bhndb_dw_alloc		*dwr;
717 	const struct bhndb_regwin	*win;
718 
719 	/* Search for an existing dynamic mapping of this address range. */
720 	for (size_t i = 0; i < br->dwa_count; i++) {
721 		dwr = &br->dw_alloc[i];
722 		win = dwr->win;
723 
724 		/* Verify the range */
725 		if (addr < dwr->target)
726 			continue;
727 
728 		if (addr + size > dwr->target + win->win_size)
729 			continue;
730 
731 		/* Found a usable mapping */
732 		return (dwr);
733 	}
734 
735 	/* not found */
736 	return (NULL);
737 }
738 
739 /**
740  * Retain a reference to @p dwa for use by @p res.
741  *
742  * @param br The resource state owning @p dwa.
743  * @param dwa The allocation record to be retained.
744  * @param res The resource that will own a reference to @p dwa.
745  *
746  * @retval 0 success
747  * @retval ENOMEM Failed to allocate a new reference structure.
748  */
749 int
750 bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
751     struct resource *res)
752 {
753 	struct bhndb_dw_rentry *rentry;
754 
755 	KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL,
756 	    ("double-retain of dynamic window for same resource"));
757 
758 	/* Insert a reference entry; we use M_NOWAIT to allow use from
759 	 * within a non-sleepable lock */
760 	rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT);
761 	if (rentry == NULL)
762 		return (ENOMEM);
763 
764 	rentry->dw_res = res;
765 	LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link);
766 
767 	/* Update the free list */
768 	br->dwa_freelist &= ~(1 << (dwa->rnid));
769 
770 	return (0);
771 }
772 
773 /**
774  * Release a reference to @p dwa previously retained by @p res. If the
775  * reference count of @p dwa reaches zero, it will be added to the
776  * free list.
777  *
778  * @param br The resource state owning @p dwa.
779  * @param dwa The allocation record to be released.
780  * @param res The resource that currently owns a reference to @p dwa.
781  */
782 void
783 bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
784     struct resource *r)
785 {
786 	struct bhndb_dw_rentry	*rentry;
787 
788 	/* Find the rentry */
789 	rentry = bhndb_dw_find_resource_entry(dwa, r);
790 	KASSERT(rentry != NULL, ("over release of resource entry"));
791 
792 	LIST_REMOVE(rentry, dw_link);
793 	free(rentry, M_BHND);
794 
795 	/* If this was the last reference, update the free list */
796 	if (LIST_EMPTY(&dwa->refs))
797 		br->dwa_freelist |= (1 << (dwa->rnid));
798 }
799 
800 /**
801  * Attempt to set (or reset) the target address of @p dwa to map @p size bytes
802  * at @p addr.
803  *
804  * This will apply any necessary window alignment and verify that
805  * the window is capable of mapping the requested range prior to modifying
806  * therecord.
807  *
808  * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request.
809  * @param br The resource state owning @p dwa.
810  * @param dwa The allocation record to be configured.
811  * @param addr The address to be mapped via @p dwa.
812  * @param size The number of bytes to be mapped at @p addr.
813  *
814  * @retval 0 success
815  * @retval non-zero no usable register window available.
816  */
817 int
818 bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
819     struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size)
820 {
821 	const struct bhndb_regwin	*rw;
822 	bus_addr_t			 offset;
823 	int				 error;
824 
825 	rw = dwa->win;
826 
827 	KASSERT(bhndb_dw_is_free(br, dwa),
828 	    ("attempting to set the target address on an in-use window"));
829 
830 	/* Page-align the target address */
831 	offset = addr % rw->win_size;
832 	dwa->target = addr - offset;
833 
834 	/* Verify that the window is large enough for the full target */
835 	if (rw->win_size - offset < size)
836 		return (ENOMEM);
837 
838 	/* Update the window target */
839 	error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target);
840 	if (error) {
841 		dwa->target = 0x0;
842 		return (error);
843 	}
844 
845 	return (0);
846 }
847 
848 /**
849  * Return the count of @p type register windows in @p table.
850  *
851  * @param table The table to search.
852  * @param type The required window type, or BHNDB_REGWIN_T_INVALID to
853  * count all register window types.
854  */
855 size_t
856 bhndb_regwin_count(const struct bhndb_regwin *table,
857     bhndb_regwin_type_t type)
858 {
859 	const struct bhndb_regwin	*rw;
860 	size_t				 count;
861 
862 	count = 0;
863 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) {
864 		if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type)
865 			count++;
866 	}
867 
868 	return (count);
869 }
870 
871 /**
872  * Search @p table for the first window with the given @p type.
873  *
874  * @param table The table to search.
875  * @param type The required window type.
876  * @param min_size The minimum window size.
877  *
878  * @retval bhndb_regwin The first matching window.
879  * @retval NULL If no window of the requested type could be found.
880  */
881 const struct bhndb_regwin *
882 bhndb_regwin_find_type(const struct bhndb_regwin *table,
883     bhndb_regwin_type_t type, bus_size_t min_size)
884 {
885 	const struct bhndb_regwin *rw;
886 
887 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
888 	{
889 		if (rw->win_type == type && rw->win_size >= min_size)
890 			return (rw);
891 	}
892 
893 	return (NULL);
894 }
895 
896 /**
897  * Search @p windows for the first matching core window.
898  *
899  * @param table The table to search.
900  * @param class The required core class.
901  * @param unit The required core unit, or -1.
902  * @param port_type The required port type.
903  * @param port The required port.
904  * @param region The required region.
905  *
906  * @retval bhndb_regwin The first matching window.
907  * @retval NULL If no matching window was found.
908  */
909 const struct bhndb_regwin *
910 bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
911     int unit, bhnd_port_type port_type, u_int port, u_int region)
912 {
913 	const struct bhndb_regwin *rw;
914 
915 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
916 	{
917 		if (rw->win_type != BHNDB_REGWIN_T_CORE)
918 			continue;
919 
920 		if (rw->d.core.class != class)
921 			continue;
922 
923 		if (unit != -1 && rw->d.core.unit != unit)
924 			continue;
925 
926 		if (rw->d.core.port_type != port_type)
927 			continue;
928 
929 		if (rw->d.core.port != port)
930 			continue;
931 
932 		if (rw->d.core.region != region)
933 			continue;
934 
935 		return (rw);
936 	}
937 
938 	return (NULL);
939 }
940 
941 /**
942  * Search @p windows for the best available window of at least @p min_size.
943  *
944  * Search order:
945  * - BHND_REGWIN_T_CORE
946  * - BHND_REGWIN_T_DYN
947  *
948  * @param table The table to search.
949  * @param class The required core class.
950  * @param unit The required core unit, or -1.
951  * @param port_type The required port type.
952  * @param port The required port.
953  * @param region The required region.
954  * @param min_size The minimum window size.
955  *
956  * @retval bhndb_regwin The first matching window.
957  * @retval NULL If no matching window was found.
958  */
959 const struct bhndb_regwin *
960 bhndb_regwin_find_best(const struct bhndb_regwin *table,
961     bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port,
962     u_int region, bus_size_t min_size)
963 {
964 	const struct bhndb_regwin *rw;
965 
966 	/* Prefer a fixed core mapping */
967 	rw = bhndb_regwin_find_core(table, class, unit, port_type,
968 	    port, region);
969 	if (rw != NULL)
970 		return (rw);
971 
972 	/* Fall back on a generic dynamic window */
973 	return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size));
974 }
975 
976 /**
977  * Return true if @p regw defines a static port register window, and
978  * the mapped port is actually defined on @p dev.
979  *
980  * @param regw A register window to match against.
981  * @param dev A bhnd(4) bus device.
982  */
983 bool
984 bhndb_regwin_matches_device(const struct bhndb_regwin *regw, device_t dev)
985 {
986 	/* Only core windows are supported */
987 	if (regw->win_type != BHNDB_REGWIN_T_CORE)
988 		return (false);
989 
990 	/* Device class must match */
991 	if (bhnd_get_class(dev) != regw->d.core.class)
992 		return (false);
993 
994 	/* Device unit must match */
995 	if (bhnd_get_core_unit(dev) != regw->d.core.unit)
996 		return (false);
997 
998 	/* The regwin port/region must be defined. */
999 	if (!bhnd_is_region_valid(dev, regw->d.core.port_type, regw->d.core.port,
1000 	    regw->d.core.region))
1001 	{
1002 		return (false);
1003 	}
1004 
1005 	/* Matches */
1006 	return (true);
1007 }
1008 
1009 /**
1010  * Search for a core resource priority descriptor in @p table that matches
1011  * @p device.
1012  *
1013  * @param table The table to search.
1014  * @param device A bhnd(4) bus device.
1015  */
1016 const struct bhndb_hw_priority *
1017 bhndb_hw_priority_find_device(const struct bhndb_hw_priority *table,
1018     device_t device)
1019 {
1020 	const struct bhndb_hw_priority	*hp;
1021 	struct bhnd_core_info		 ci;
1022 
1023 	ci = bhnd_get_core_info(device);
1024 
1025 	for (hp = table; hp->ports != NULL; hp++) {
1026 		if (bhnd_core_matches(&ci, &hp->match))
1027 			return (hp);
1028 	}
1029 
1030 	/* not found */
1031 	return (NULL);
1032 }
1033