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
bhndb_attach_bridge(device_t parent,device_t * bhndb,int unit)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
bhndb_do_suspend_resources(device_t dev,struct resource_list * rl)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
bhnd_generic_br_suspend_child(device_t dev,device_t child)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
bhnd_generic_br_resume_child(device_t dev,device_t child)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 *
bhndb_find_resource_range(struct bhndb_resources * br,rman_res_t start,rman_res_t count)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 *
bhndb_find_regwin_resource(struct bhndb_resources * br,const struct bhndb_regwin * win)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 *
bhndb_alloc_resources(device_t dev,device_t parent_dev,const struct bhndb_hwcfg * cfg)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
bhndb_free_resources(struct bhndb_resources * br)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
bhndb_add_resource_region(struct bhndb_resources * br,bhnd_addr_t addr,bhnd_size_t size,bhndb_priority_t priority,const struct bhndb_regwin * static_regwin)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
bhndb_find_resource_limits(struct bhndb_resources * br,struct resource * r,rman_res_t * start,rman_res_t * end)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 *
bhndb_find_resource_region(struct bhndb_resources * br,bhnd_addr_t addr,bhnd_size_t size)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 *
bhndb_dw_find_resource_entry(struct bhndb_dw_alloc * dwa,struct resource * r)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 *
bhndb_dw_find_resource(struct bhndb_resources * br,struct resource * r)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 *
bhndb_dw_find_mapping(struct bhndb_resources * br,bhnd_addr_t addr,bhnd_size_t size)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
bhndb_dw_retain(struct bhndb_resources * br,struct bhndb_dw_alloc * dwa,struct resource * res)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
bhndb_dw_release(struct bhndb_resources * br,struct bhndb_dw_alloc * dwa,struct resource * r)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
bhndb_dw_set_addr(device_t dev,struct bhndb_resources * br,struct bhndb_dw_alloc * dwa,bus_addr_t addr,bus_size_t size)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
bhndb_regwin_count(const struct bhndb_regwin * table,bhndb_regwin_type_t type)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 *
bhndb_regwin_find_type(const struct bhndb_regwin * table,bhndb_regwin_type_t type,bus_size_t min_size)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 *
bhndb_regwin_find_core(const struct bhndb_regwin * table,bhnd_devclass_t class,int unit,bhnd_port_type port_type,u_int port,u_int region)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 *
bhndb_regwin_find_best(const struct bhndb_regwin * table,bhnd_devclass_t class,int unit,bhnd_port_type port_type,u_int port,u_int region,bus_size_t min_size)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
bhndb_regwin_matches_device(const struct bhndb_regwin * regw,device_t dev)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 *
bhndb_hw_priority_find_device(const struct bhndb_hw_priority * table,device_t device)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