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