bhndb_subr.c revision 300251
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: head/sys/dev/bhnd/bhndb/bhndb_subr.c 300251 2016-05-20 00:49:10Z adrian $");
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 */
50int
51bhndb_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 */
71static void
72bhndb_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 */
102int
103bhnd_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 */
142int
143bhnd_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 */
194struct resource *
195bhndb_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 */
226struct resource *
227bhndb_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 */
259struct bhndb_resources *
260bhndb_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
457failed:
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 */
486void
487bhndb_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 */
542int
543bhndb_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 */
582int
583bhndb_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 */
620struct bhndb_region *
621bhndb_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 */
647static struct bhndb_dw_rentry *
648bhndb_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 */
680struct bhndb_dw_alloc *
681bhndb_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 */
712struct bhndb_dw_alloc *
713bhndb_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 */
749int
750bhndb_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 */
782void
783bhndb_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 */
817int
818bhndb_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 */
855size_t
856bhndb_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 */
881const struct bhndb_regwin *
882bhndb_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 */
909const struct bhndb_regwin *
910bhndb_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 */
959const struct bhndb_regwin *
960bhndb_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 */
983bool
984bhndb_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 */
1016const struct bhndb_hw_priority *
1017bhndb_hw_priority_find_device(const struct bhndb_hw_priority *table,
1018    device_t device)
1019{
1020	const struct bhndb_hw_priority *hp;
1021
1022	for (hp = table; hp->ports != NULL; hp++) {
1023		if (bhnd_device_matches(device, &hp->match))
1024			return (hp);
1025	}
1026
1027	/* not found */
1028	return (NULL);
1029}
1030