xref: /freebsd/sys/dev/bhnd/bhndb/bhndb_subr.c (revision 18849b5da0c5eaa88500b457be05b038813b51b1)
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  * Find a bus region that maps @p size bytes at @p addr.
568  *
569  * @param br The resource state to search.
570  * @param addr The requested starting address.
571  * @param size The requested size.
572  *
573  * @retval bhndb_region A region that fully contains the requested range.
574  * @retval NULL If no mapping region can be found.
575  */
576 struct bhndb_region *
577 bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr,
578     bhnd_size_t size)
579 {
580 	struct bhndb_region *region;
581 
582 	STAILQ_FOREACH(region, &br->bus_regions, link) {
583 		/* Request must fit within the region's mapping  */
584 		if (addr < region->addr)
585 			continue;
586 
587 		if (addr + size > region->addr + region->size)
588 			continue;
589 
590 		return (region);
591 	}
592 
593 	/* Not found */
594 	return (NULL);
595 }
596 
597 /**
598  * Find the entry matching @p r in @p dwa's references, if any.
599  *
600  * @param dwa The dynamic window allocation to search
601  * @param r The resource to search for in @p dwa.
602  */
603 static struct bhndb_dw_rentry *
604 bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r)
605 {
606 	struct bhndb_dw_rentry	*rentry;
607 
608 	LIST_FOREACH(rentry, &dwa->refs, dw_link) {
609 		struct resource *dw_res = rentry->dw_res;
610 
611 		/* Match dev/rid/addr/size */
612 		if (rman_get_device(dw_res)	!= rman_get_device(r) ||
613 			rman_get_rid(dw_res)	!= rman_get_rid(r) ||
614 			rman_get_start(dw_res)	!= rman_get_start(r) ||
615 			rman_get_size(dw_res)	!= rman_get_size(r))
616 		{
617 			continue;
618 		}
619 
620 		/* Matching allocation found */
621 		return (rentry);
622 	}
623 
624 	return (NULL);
625 }
626 
627 /**
628  * Find the dynamic region allocated for @p r, if any.
629  *
630  * @param br The resource state to search.
631  * @param r The resource to search for.
632  *
633  * @retval bhndb_dw_alloc The allocation record for @p r.
634  * @retval NULL if no dynamic window is allocated for @p r.
635  */
636 struct bhndb_dw_alloc *
637 bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r)
638 {
639 	struct bhndb_dw_alloc	*dwa;
640 
641 	for (size_t i = 0; i < br->dwa_count; i++) {
642 		dwa = &br->dw_alloc[i];
643 
644 		/* Skip free dynamic windows */
645 		if (bhndb_dw_is_free(br, dwa))
646 			continue;
647 
648 		/* Matching allocation found? */
649 		if (bhndb_dw_find_resource_entry(dwa, r) != NULL)
650 			return (dwa);
651 	}
652 
653 	return (NULL);
654 }
655 
656 /**
657  * Find an existing dynamic window mapping @p size bytes
658  * at @p addr. The window may or may not be free.
659  *
660  * @param br The resource state to search.
661  * @param addr The requested starting address.
662  * @param size The requested size.
663  *
664  * @retval bhndb_dw_alloc A window allocation that fully contains the requested
665  * range.
666  * @retval NULL If no mapping region can be found.
667  */
668 struct bhndb_dw_alloc *
669 bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr,
670     bhnd_size_t size)
671 {
672 	struct bhndb_dw_alloc		*dwr;
673 	const struct bhndb_regwin	*win;
674 
675 	/* Search for an existing dynamic mapping of this address range. */
676 	for (size_t i = 0; i < br->dwa_count; i++) {
677 		dwr = &br->dw_alloc[i];
678 		win = dwr->win;
679 
680 		/* Verify the range */
681 		if (addr < dwr->target)
682 			continue;
683 
684 		if (addr + size > dwr->target + win->win_size)
685 			continue;
686 
687 		/* Found a usable mapping */
688 		return (dwr);
689 	}
690 
691 	/* not found */
692 	return (NULL);
693 }
694 
695 /**
696  * Retain a reference to @p dwa for use by @p res.
697  *
698  * @param br The resource state owning @p dwa.
699  * @param dwa The allocation record to be retained.
700  * @param res The resource that will own a reference to @p dwa.
701  *
702  * @retval 0 success
703  * @retval ENOMEM Failed to allocate a new reference structure.
704  */
705 int
706 bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
707     struct resource *res)
708 {
709 	struct bhndb_dw_rentry *rentry;
710 
711 	KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL,
712 	    ("double-retain of dynamic window for same resource"));
713 
714 	/* Insert a reference entry; we use M_NOWAIT to allow use from
715 	 * within a non-sleepable lock */
716 	rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT);
717 	if (rentry == NULL)
718 		return (ENOMEM);
719 
720 	rentry->dw_res = res;
721 	LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link);
722 
723 	/* Update the free list */
724 	br->dwa_freelist &= ~(1 << (dwa->rnid));
725 
726 	return (0);
727 }
728 
729 /**
730  * Release a reference to @p dwa previously retained by @p res. If the
731  * reference count of @p dwa reaches zero, it will be added to the
732  * free list.
733  *
734  * @param br The resource state owning @p dwa.
735  * @param dwa The allocation record to be released.
736  * @param res The resource that currently owns a reference to @p dwa.
737  */
738 void
739 bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa,
740     struct resource *r)
741 {
742 	struct bhndb_dw_rentry	*rentry;
743 
744 	/* Find the rentry */
745 	rentry = bhndb_dw_find_resource_entry(dwa, r);
746 	KASSERT(rentry != NULL, ("over release of resource entry"));
747 
748 	LIST_REMOVE(rentry, dw_link);
749 	free(rentry, M_BHND);
750 
751 	/* If this was the last reference, update the free list */
752 	if (LIST_EMPTY(&dwa->refs))
753 		br->dwa_freelist |= (1 << (dwa->rnid));
754 }
755 
756 /**
757  * Attempt to set (or reset) the target address of @p dwa to map @p size bytes
758  * at @p addr.
759  *
760  * This will apply any necessary window alignment and verify that
761  * the window is capable of mapping the requested range prior to modifying
762  * therecord.
763  *
764  * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request.
765  * @param br The resource state owning @p dwa.
766  * @param dwa The allocation record to be configured.
767  * @param addr The address to be mapped via @p dwa.
768  * @param size The number of bytes to be mapped at @p addr.
769  *
770  * @retval 0 success
771  * @retval non-zero no usable register window available.
772  */
773 int
774 bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br,
775     struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size)
776 {
777 	const struct bhndb_regwin	*rw;
778 	bus_addr_t			 offset;
779 	int				 error;
780 
781 	rw = dwa->win;
782 
783 	KASSERT(bhndb_dw_is_free(br, dwa),
784 	    ("attempting to set the target address on an in-use window"));
785 
786 	/* Page-align the target address */
787 	offset = addr % rw->win_size;
788 	dwa->target = addr - offset;
789 
790 	/* Verify that the window is large enough for the full target */
791 	if (rw->win_size - offset < size)
792 		return (ENOMEM);
793 
794 	/* Update the window target */
795 	error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target);
796 	if (error) {
797 		dwa->target = 0x0;
798 		return (error);
799 	}
800 
801 	return (0);
802 }
803 
804 /**
805  * Return the count of @p type register windows in @p table.
806  *
807  * @param table The table to search.
808  * @param type The required window type, or BHNDB_REGWIN_T_INVALID to
809  * count all register window types.
810  */
811 size_t
812 bhndb_regwin_count(const struct bhndb_regwin *table,
813     bhndb_regwin_type_t type)
814 {
815 	const struct bhndb_regwin	*rw;
816 	size_t				 count;
817 
818 	count = 0;
819 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) {
820 		if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type)
821 			count++;
822 	}
823 
824 	return (count);
825 }
826 
827 /**
828  * Search @p table for the first window with the given @p type.
829  *
830  * @param table The table to search.
831  * @param type The required window type.
832  * @param min_size The minimum window size.
833  *
834  * @retval bhndb_regwin The first matching window.
835  * @retval NULL If no window of the requested type could be found.
836  */
837 const struct bhndb_regwin *
838 bhndb_regwin_find_type(const struct bhndb_regwin *table,
839     bhndb_regwin_type_t type, bus_size_t min_size)
840 {
841 	const struct bhndb_regwin *rw;
842 
843 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
844 	{
845 		if (rw->win_type == type && rw->win_size >= min_size)
846 			return (rw);
847 	}
848 
849 	return (NULL);
850 }
851 
852 /**
853  * Search @p windows for the first matching core window.
854  *
855  * @param table The table to search.
856  * @param class The required core class.
857  * @param unit The required core unit, or -1.
858  * @param port_type The required port type.
859  * @param port The required port.
860  * @param region The required region.
861  *
862  * @retval bhndb_regwin The first matching window.
863  * @retval NULL If no matching window was found.
864  */
865 const struct bhndb_regwin *
866 bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class,
867     int unit, bhnd_port_type port_type, u_int port, u_int region)
868 {
869 	const struct bhndb_regwin *rw;
870 
871 	for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++)
872 	{
873 		if (rw->win_type != BHNDB_REGWIN_T_CORE)
874 			continue;
875 
876 		if (rw->d.core.class != class)
877 			continue;
878 
879 		if (unit != -1 && rw->d.core.unit != unit)
880 			continue;
881 
882 		if (rw->d.core.port_type != port_type)
883 			continue;
884 
885 		if (rw->d.core.port != port)
886 			continue;
887 
888 		if (rw->d.core.region != region)
889 			continue;
890 
891 		return (rw);
892 	}
893 
894 	return (NULL);
895 }
896 
897 /**
898  * Search @p windows for the best available window of at least @p min_size.
899  *
900  * Search order:
901  * - BHND_REGWIN_T_CORE
902  * - BHND_REGWIN_T_DYN
903  *
904  * @param table The table to search.
905  * @param class The required core class.
906  * @param unit The required core unit, or -1.
907  * @param port_type The required port type.
908  * @param port The required port.
909  * @param region The required region.
910  * @param min_size The minimum window size.
911  *
912  * @retval bhndb_regwin The first matching window.
913  * @retval NULL If no matching window was found.
914  */
915 const struct bhndb_regwin *
916 bhndb_regwin_find_best(const struct bhndb_regwin *table,
917     bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port,
918     u_int region, bus_size_t min_size)
919 {
920 	const struct bhndb_regwin *rw;
921 
922 	/* Prefer a fixed core mapping */
923 	rw = bhndb_regwin_find_core(table, class, unit, port_type,
924 	    port, region);
925 	if (rw != NULL)
926 		return (rw);
927 
928 	/* Fall back on a generic dynamic window */
929 	return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size));
930 }
931 
932 /**
933  * Return true if @p regw defines a static port register window, and
934  * the mapped port is actually defined on @p dev.
935  *
936  * @param regw A register window to match against.
937  * @param dev A bhnd(4) bus device.
938  */
939 bool
940 bhndb_regwin_matches_device(const struct bhndb_regwin *regw, device_t dev)
941 {
942 	/* Only core windows are supported */
943 	if (regw->win_type != BHNDB_REGWIN_T_CORE)
944 		return (false);
945 
946 	/* Device class must match */
947 	if (bhnd_get_class(dev) != regw->d.core.class)
948 		return (false);
949 
950 	/* Device unit must match */
951 	if (bhnd_get_core_unit(dev) != regw->d.core.unit)
952 		return (false);
953 
954 	/* The regwin port/region must be defined. */
955 	if (!bhnd_is_region_valid(dev, regw->d.core.port_type, regw->d.core.port,
956 	    regw->d.core.region))
957 	{
958 		return (false);
959 	}
960 
961 	/* Matches */
962 	return (true);
963 }
964 
965 /**
966  * Search for a core resource priority descriptor in @p table that matches
967  * @p device.
968  *
969  * @param table The table to search.
970  * @param device A bhnd(4) bus device.
971  */
972 const struct bhndb_hw_priority *
973 bhndb_hw_priority_find_device(const struct bhndb_hw_priority *table,
974     device_t device)
975 {
976 	const struct bhndb_hw_priority *hp;
977 
978 	for (hp = table; hp->ports != NULL; hp++) {
979 		if (bhnd_device_matches(device, &hp->match))
980 			return (hp);
981 	}
982 
983 	/* not found */
984 	return (NULL);
985 }
986