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, devclass_get_name(bhndb_devclass), 56 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 u_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 bitmask. */ 376 r->dwa_count = bhndb_regwin_count(cfg->register_windows, 377 BHNDB_REGWIN_T_DYN); 378 if (r->dwa_count >= (8 * sizeof(r->dwa_freelist))) { 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 /* Initialize the dynamic window table and freelist. */ 390 r->dwa_freelist = 0; 391 rnid = 0; 392 last_window_size = 0; 393 for (win = cfg->register_windows; 394 win->win_type != BHNDB_REGWIN_T_INVALID; win++) 395 { 396 struct bhndb_dw_alloc *dwa; 397 398 /* Skip non-DYN windows */ 399 if (win->win_type != BHNDB_REGWIN_T_DYN) 400 continue; 401 402 /* Validate the window size */ 403 if (win->win_size == 0) { 404 device_printf(r->dev, "ignoring zero-length dynamic " 405 "register window\n"); 406 continue; 407 } else if (last_window_size == 0) { 408 last_window_size = win->win_size; 409 } else if (last_window_size != win->win_size) { 410 /* 411 * No existing hardware should trigger this. 412 * 413 * If you run into this in the future, the dynamic 414 * window allocator and the resource priority system 415 * will need to be extended to support multiple register 416 * window allocation pools. 417 */ 418 device_printf(r->dev, "devices that vend multiple " 419 "dynamic register window sizes are not currently " 420 "supported\n"); 421 goto failed; 422 } 423 424 dwa = &r->dw_alloc[rnid]; 425 dwa->win = win; 426 dwa->parent_res = NULL; 427 dwa->rnid = rnid; 428 dwa->target = 0x0; 429 430 LIST_INIT(&dwa->refs); 431 432 /* Find and validate corresponding resource. */ 433 dwa->parent_res = bhndb_find_regwin_resource(r, win); 434 if (dwa->parent_res == NULL) 435 goto failed; 436 437 if (rman_get_size(dwa->parent_res) < win->win_offset + 438 win->win_size) 439 { 440 device_printf(r->dev, "resource %d too small for " 441 "register window with offset %llx and size %llx\n", 442 rman_get_rid(dwa->parent_res), 443 (unsigned long long) win->win_offset, 444 (unsigned long long) win->win_size); 445 446 error = EINVAL; 447 goto failed; 448 } 449 450 /* Add to freelist */ 451 r->dwa_freelist |= (1 << rnid); 452 453 rnid++; 454 } 455 456 return (r); 457 458 failed: 459 if (free_parent_res) 460 bus_release_resources(r->parent_dev, r->res_spec, r->res); 461 462 if (free_ht_mem) 463 rman_fini(&r->ht_mem_rman); 464 465 if (free_br_mem) 466 rman_fini(&r->br_mem_rman); 467 468 if (r->res != NULL) 469 free(r->res, M_BHND); 470 471 if (r->res_spec != NULL) 472 free(r->res_spec, M_BHND); 473 474 if (r->dw_alloc != NULL) 475 free(r->dw_alloc, M_BHND); 476 477 free (r, M_BHND); 478 479 return (NULL); 480 } 481 482 /** 483 * Deallocate the given bridge resource structure and any associated resources. 484 * 485 * @param br Resource state to be deallocated. 486 */ 487 void 488 bhndb_free_resources(struct bhndb_resources *br) 489 { 490 struct bhndb_region *region, *r_next; 491 struct bhndb_dw_alloc *dwa; 492 struct bhndb_dw_rentry *dwr, *dwr_next; 493 494 /* No window regions may still be held */ 495 if (__builtin_popcount(br->dwa_freelist) != br->dwa_count) { 496 device_printf(br->dev, "leaked %llu dynamic register regions\n", 497 (unsigned long long) br->dwa_count - br->dwa_freelist); 498 } 499 500 /* Release resources allocated through our parent. */ 501 bus_release_resources(br->parent_dev, br->res_spec, br->res); 502 503 /* Clean up resource reservations */ 504 for (size_t i = 0; i < br->dwa_count; i++) { 505 dwa = &br->dw_alloc[i]; 506 507 LIST_FOREACH_SAFE(dwr, &dwa->refs, dw_link, dwr_next) { 508 LIST_REMOVE(dwr, dw_link); 509 free(dwr, M_BHND); 510 } 511 } 512 513 /* Release bus regions */ 514 STAILQ_FOREACH_SAFE(region, &br->bus_regions, link, r_next) { 515 STAILQ_REMOVE(&br->bus_regions, region, bhndb_region, link); 516 free(region, M_BHND); 517 } 518 519 /* Release our resource managers */ 520 rman_fini(&br->ht_mem_rman); 521 rman_fini(&br->br_mem_rman); 522 523 /* Free backing resource state structures */ 524 free(br->res, M_BHND); 525 free(br->res_spec, M_BHND); 526 free(br->dw_alloc, M_BHND); 527 } 528 529 /** 530 * Add a bus region entry to @p r for the given base @p addr and @p size. 531 * 532 * @param br The resource state to which the bus region entry will be added. 533 * @param addr The base address of this region. 534 * @param size The size of this region. 535 * @param priority The resource priority to be assigned to allocations 536 * made within this bus region. 537 * @param static_regwin If available, a static register window mapping this 538 * bus region entry. If not available, NULL. 539 * 540 * @retval 0 success 541 * @retval non-zero if adding the bus region fails. 542 */ 543 int 544 bhndb_add_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, 545 bhnd_size_t size, bhndb_priority_t priority, 546 const struct bhndb_regwin *static_regwin) 547 { 548 struct bhndb_region *reg; 549 550 /* Insert in the bus resource list */ 551 reg = malloc(sizeof(*reg), M_BHND, M_NOWAIT); 552 if (reg == NULL) 553 return (ENOMEM); 554 555 *reg = (struct bhndb_region) { 556 .addr = addr, 557 .size = size, 558 .priority = priority, 559 .static_regwin = static_regwin 560 }; 561 562 STAILQ_INSERT_HEAD(&br->bus_regions, reg, link); 563 564 return (0); 565 } 566 567 /** 568 * Find a bus region that maps @p size bytes at @p addr. 569 * 570 * @param br The resource state to search. 571 * @param addr The requested starting address. 572 * @param size The requested size. 573 * 574 * @retval bhndb_region A region that fully contains the requested range. 575 * @retval NULL If no mapping region can be found. 576 */ 577 struct bhndb_region * 578 bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, 579 bhnd_size_t size) 580 { 581 struct bhndb_region *region; 582 583 STAILQ_FOREACH(region, &br->bus_regions, link) { 584 /* Request must fit within the region's mapping */ 585 if (addr < region->addr) 586 continue; 587 588 if (addr + size > region->addr + region->size) 589 continue; 590 591 return (region); 592 } 593 594 /* Not found */ 595 return (NULL); 596 } 597 598 /** 599 * Find the entry matching @p r in @p dwa's references, if any. 600 * 601 * @param dwa The dynamic window allocation to search 602 * @param r The resource to search for in @p dwa. 603 */ 604 static struct bhndb_dw_rentry * 605 bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r) 606 { 607 struct bhndb_dw_rentry *rentry; 608 609 LIST_FOREACH(rentry, &dwa->refs, dw_link) { 610 struct resource *dw_res = rentry->dw_res; 611 612 /* Match dev/rid/addr/size */ 613 if (rman_get_device(dw_res) != rman_get_device(r) || 614 rman_get_rid(dw_res) != rman_get_rid(r) || 615 rman_get_start(dw_res) != rman_get_start(r) || 616 rman_get_size(dw_res) != rman_get_size(r)) 617 { 618 continue; 619 } 620 621 /* Matching allocation found */ 622 return (rentry); 623 } 624 625 return (NULL); 626 } 627 628 /** 629 * Find the dynamic region allocated for @p r, if any. 630 * 631 * @param br The resource state to search. 632 * @param r The resource to search for. 633 * 634 * @retval bhndb_dw_alloc The allocation record for @p r. 635 * @retval NULL if no dynamic window is allocated for @p r. 636 */ 637 struct bhndb_dw_alloc * 638 bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r) 639 { 640 struct bhndb_dw_alloc *dwa; 641 642 for (size_t i = 0; i < br->dwa_count; i++) { 643 dwa = &br->dw_alloc[i]; 644 645 /* Skip free dynamic windows */ 646 if (bhndb_dw_is_free(br, dwa)) 647 continue; 648 649 /* Matching allocation found? */ 650 if (bhndb_dw_find_resource_entry(dwa, r) != NULL) 651 return (dwa); 652 } 653 654 return (NULL); 655 } 656 657 /** 658 * Find an existing dynamic window mapping @p size bytes 659 * at @p addr. The window may or may not be free. 660 * 661 * @param br The resource state to search. 662 * @param addr The requested starting address. 663 * @param size The requested size. 664 * 665 * @retval bhndb_dw_alloc A window allocation that fully contains the requested 666 * range. 667 * @retval NULL If no mapping region can be found. 668 */ 669 struct bhndb_dw_alloc * 670 bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr, 671 bhnd_size_t size) 672 { 673 struct bhndb_dw_alloc *dwr; 674 const struct bhndb_regwin *win; 675 676 /* Search for an existing dynamic mapping of this address range. */ 677 for (size_t i = 0; i < br->dwa_count; i++) { 678 dwr = &br->dw_alloc[i]; 679 win = dwr->win; 680 681 /* Verify the range */ 682 if (addr < dwr->target) 683 continue; 684 685 if (addr + size > dwr->target + win->win_size) 686 continue; 687 688 /* Found a usable mapping */ 689 return (dwr); 690 } 691 692 /* not found */ 693 return (NULL); 694 } 695 696 /** 697 * Retain a reference to @p dwa for use by @p res. 698 * 699 * @param br The resource state owning @p dwa. 700 * @param dwa The allocation record to be retained. 701 * @param res The resource that will own a reference to @p dwa. 702 * 703 * @retval 0 success 704 * @retval ENOMEM Failed to allocate a new reference structure. 705 */ 706 int 707 bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, 708 struct resource *res) 709 { 710 struct bhndb_dw_rentry *rentry; 711 712 KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL, 713 ("double-retain of dynamic window for same resource")); 714 715 /* Insert a reference entry; we use M_NOWAIT to allow use from 716 * within a non-sleepable lock */ 717 rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT); 718 if (rentry == NULL) 719 return (ENOMEM); 720 721 rentry->dw_res = res; 722 LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link); 723 724 /* Update the free list */ 725 br->dwa_freelist &= ~(1 << (dwa->rnid)); 726 727 return (0); 728 } 729 730 /** 731 * Release a reference to @p dwa previously retained by @p res. If the 732 * reference count of @p dwa reaches zero, it will be added to the 733 * free list. 734 * 735 * @param br The resource state owning @p dwa. 736 * @param dwa The allocation record to be released. 737 * @param res The resource that currently owns a reference to @p dwa. 738 */ 739 void 740 bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, 741 struct resource *r) 742 { 743 struct bhndb_dw_rentry *rentry; 744 745 /* Find the rentry */ 746 rentry = bhndb_dw_find_resource_entry(dwa, r); 747 KASSERT(rentry != NULL, ("over release of resource entry")); 748 749 LIST_REMOVE(rentry, dw_link); 750 free(rentry, M_BHND); 751 752 /* If this was the last reference, update the free list */ 753 if (LIST_EMPTY(&dwa->refs)) 754 br->dwa_freelist |= (1 << (dwa->rnid)); 755 } 756 757 /** 758 * Attempt to set (or reset) the target address of @p dwa to map @p size bytes 759 * at @p addr. 760 * 761 * This will apply any necessary window alignment and verify that 762 * the window is capable of mapping the requested range prior to modifying 763 * therecord. 764 * 765 * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request. 766 * @param br The resource state owning @p dwa. 767 * @param dwa The allocation record to be configured. 768 * @param addr The address to be mapped via @p dwa. 769 * @param size The number of bytes to be mapped at @p addr. 770 * 771 * @retval 0 success 772 * @retval non-zero no usable register window available. 773 */ 774 int 775 bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br, 776 struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size) 777 { 778 const struct bhndb_regwin *rw; 779 bus_addr_t offset; 780 int error; 781 782 rw = dwa->win; 783 784 KASSERT(bhndb_dw_is_free(br, dwa), 785 ("attempting to set the target address on an in-use window")); 786 787 /* Page-align the target address */ 788 offset = addr % rw->win_size; 789 dwa->target = addr - offset; 790 791 /* Verify that the window is large enough for the full target */ 792 if (rw->win_size - offset < size) 793 return (ENOMEM); 794 795 /* Update the window target */ 796 error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); 797 if (error) { 798 dwa->target = 0x0; 799 return (error); 800 } 801 802 return (0); 803 } 804 805 /** 806 * Return the count of @p type register windows in @p table. 807 * 808 * @param table The table to search. 809 * @param type The required window type, or BHNDB_REGWIN_T_INVALID to 810 * count all register window types. 811 */ 812 size_t 813 bhndb_regwin_count(const struct bhndb_regwin *table, 814 bhndb_regwin_type_t type) 815 { 816 const struct bhndb_regwin *rw; 817 size_t count; 818 819 count = 0; 820 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { 821 if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type) 822 count++; 823 } 824 825 return (count); 826 } 827 828 /** 829 * Search @p table for the first window with the given @p type. 830 * 831 * @param table The table to search. 832 * @param type The required window type. 833 * @param min_size The minimum window size. 834 * 835 * @retval bhndb_regwin The first matching window. 836 * @retval NULL If no window of the requested type could be found. 837 */ 838 const struct bhndb_regwin * 839 bhndb_regwin_find_type(const struct bhndb_regwin *table, 840 bhndb_regwin_type_t type, bus_size_t min_size) 841 { 842 const struct bhndb_regwin *rw; 843 844 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) 845 { 846 if (rw->win_type == type && rw->win_size >= min_size) 847 return (rw); 848 } 849 850 return (NULL); 851 } 852 853 /** 854 * Search @p windows for the first matching core window. 855 * 856 * @param table The table to search. 857 * @param class The required core class. 858 * @param unit The required core unit, or -1. 859 * @param port_type The required port type. 860 * @param port The required port. 861 * @param region The required region. 862 * 863 * @retval bhndb_regwin The first matching window. 864 * @retval NULL If no matching window was found. 865 */ 866 const struct bhndb_regwin * 867 bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class, 868 int unit, bhnd_port_type port_type, u_int port, u_int region) 869 { 870 const struct bhndb_regwin *rw; 871 872 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) 873 { 874 if (rw->win_type != BHNDB_REGWIN_T_CORE) 875 continue; 876 877 if (rw->core.class != class) 878 continue; 879 880 if (unit != -1 && rw->core.unit != unit) 881 continue; 882 883 if (rw->core.port_type != port_type) 884 continue; 885 886 if (rw->core.port != port) 887 continue; 888 889 if (rw->core.region != region) 890 continue; 891 892 return (rw); 893 } 894 895 return (NULL); 896 } 897 898 /** 899 * Search @p windows for the best available window of at least @p min_size. 900 * 901 * Search order: 902 * - BHND_REGWIN_T_CORE 903 * - BHND_REGWIN_T_DYN 904 * 905 * @param table The table to search. 906 * @param class The required core class. 907 * @param unit The required core unit, or -1. 908 * @param port_type The required port type. 909 * @param port The required port. 910 * @param region The required region. 911 * @param min_size The minimum window size. 912 * 913 * @retval bhndb_regwin The first matching window. 914 * @retval NULL If no matching window was found. 915 */ 916 const struct bhndb_regwin * 917 bhndb_regwin_find_best(const struct bhndb_regwin *table, 918 bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, 919 u_int region, bus_size_t min_size) 920 { 921 const struct bhndb_regwin *rw; 922 923 /* Prefer a fixed core mapping */ 924 rw = bhndb_regwin_find_core(table, class, unit, port_type, 925 port, region); 926 if (rw != NULL) 927 return (rw); 928 929 /* Fall back on a generic dynamic window */ 930 return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size)); 931 } 932 933 /** 934 * Return true if @p regw defines a static port register window, and 935 * the mapped port is actually defined on @p dev. 936 * 937 * @param regw A register window to match against. 938 * @param dev A bhnd(4) bus device. 939 */ 940 bool 941 bhndb_regwin_matches_device(const struct bhndb_regwin *regw, device_t dev) 942 { 943 /* Only core windows are supported */ 944 if (regw->win_type != BHNDB_REGWIN_T_CORE) 945 return (false); 946 947 /* Device class must match */ 948 if (bhnd_get_class(dev) != regw->core.class) 949 return (false); 950 951 /* Device unit must match */ 952 if (bhnd_get_core_unit(dev) != regw->core.unit) 953 return (false); 954 955 /* The regwin port/region must be defined. */ 956 if (!bhnd_is_region_valid(dev, regw->core.port_type, regw->core.port, 957 regw->core.region)) 958 { 959 return (false); 960 } 961 962 /* Matches */ 963 return (true); 964 } 965 966 /** 967 * Search for a core resource priority descriptor in @p table that matches 968 * @p device. 969 * 970 * @param table The table to search. 971 * @param device A bhnd(4) bus device. 972 */ 973 const struct bhndb_hw_priority * 974 bhndb_hw_priority_find_device(const struct bhndb_hw_priority *table, 975 device_t device) 976 { 977 const struct bhndb_hw_priority *hp; 978 979 for (hp = table; hp->ports != NULL; hp++) { 980 if (bhnd_device_matches(device, &hp->match)) 981 return (hp); 982 } 983 984 /* not found */ 985 return (NULL); 986 } 987