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