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