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