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 * Find a bus region that maps @p size bytes at @p addr. 568 * 569 * @param br The resource state to search. 570 * @param addr The requested starting address. 571 * @param size The requested size. 572 * 573 * @retval bhndb_region A region that fully contains the requested range. 574 * @retval NULL If no mapping region can be found. 575 */ 576 struct bhndb_region * 577 bhndb_find_resource_region(struct bhndb_resources *br, bhnd_addr_t addr, 578 bhnd_size_t size) 579 { 580 struct bhndb_region *region; 581 582 STAILQ_FOREACH(region, &br->bus_regions, link) { 583 /* Request must fit within the region's mapping */ 584 if (addr < region->addr) 585 continue; 586 587 if (addr + size > region->addr + region->size) 588 continue; 589 590 return (region); 591 } 592 593 /* Not found */ 594 return (NULL); 595 } 596 597 /** 598 * Find the entry matching @p r in @p dwa's references, if any. 599 * 600 * @param dwa The dynamic window allocation to search 601 * @param r The resource to search for in @p dwa. 602 */ 603 static struct bhndb_dw_rentry * 604 bhndb_dw_find_resource_entry(struct bhndb_dw_alloc *dwa, struct resource *r) 605 { 606 struct bhndb_dw_rentry *rentry; 607 608 LIST_FOREACH(rentry, &dwa->refs, dw_link) { 609 struct resource *dw_res = rentry->dw_res; 610 611 /* Match dev/rid/addr/size */ 612 if (rman_get_device(dw_res) != rman_get_device(r) || 613 rman_get_rid(dw_res) != rman_get_rid(r) || 614 rman_get_start(dw_res) != rman_get_start(r) || 615 rman_get_size(dw_res) != rman_get_size(r)) 616 { 617 continue; 618 } 619 620 /* Matching allocation found */ 621 return (rentry); 622 } 623 624 return (NULL); 625 } 626 627 /** 628 * Find the dynamic region allocated for @p r, if any. 629 * 630 * @param br The resource state to search. 631 * @param r The resource to search for. 632 * 633 * @retval bhndb_dw_alloc The allocation record for @p r. 634 * @retval NULL if no dynamic window is allocated for @p r. 635 */ 636 struct bhndb_dw_alloc * 637 bhndb_dw_find_resource(struct bhndb_resources *br, struct resource *r) 638 { 639 struct bhndb_dw_alloc *dwa; 640 641 for (size_t i = 0; i < br->dwa_count; i++) { 642 dwa = &br->dw_alloc[i]; 643 644 /* Skip free dynamic windows */ 645 if (bhndb_dw_is_free(br, dwa)) 646 continue; 647 648 /* Matching allocation found? */ 649 if (bhndb_dw_find_resource_entry(dwa, r) != NULL) 650 return (dwa); 651 } 652 653 return (NULL); 654 } 655 656 /** 657 * Find an existing dynamic window mapping @p size bytes 658 * at @p addr. The window may or may not be free. 659 * 660 * @param br The resource state to search. 661 * @param addr The requested starting address. 662 * @param size The requested size. 663 * 664 * @retval bhndb_dw_alloc A window allocation that fully contains the requested 665 * range. 666 * @retval NULL If no mapping region can be found. 667 */ 668 struct bhndb_dw_alloc * 669 bhndb_dw_find_mapping(struct bhndb_resources *br, bhnd_addr_t addr, 670 bhnd_size_t size) 671 { 672 struct bhndb_dw_alloc *dwr; 673 const struct bhndb_regwin *win; 674 675 /* Search for an existing dynamic mapping of this address range. */ 676 for (size_t i = 0; i < br->dwa_count; i++) { 677 dwr = &br->dw_alloc[i]; 678 win = dwr->win; 679 680 /* Verify the range */ 681 if (addr < dwr->target) 682 continue; 683 684 if (addr + size > dwr->target + win->win_size) 685 continue; 686 687 /* Found a usable mapping */ 688 return (dwr); 689 } 690 691 /* not found */ 692 return (NULL); 693 } 694 695 /** 696 * Retain a reference to @p dwa for use by @p res. 697 * 698 * @param br The resource state owning @p dwa. 699 * @param dwa The allocation record to be retained. 700 * @param res The resource that will own a reference to @p dwa. 701 * 702 * @retval 0 success 703 * @retval ENOMEM Failed to allocate a new reference structure. 704 */ 705 int 706 bhndb_dw_retain(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, 707 struct resource *res) 708 { 709 struct bhndb_dw_rentry *rentry; 710 711 KASSERT(bhndb_dw_find_resource_entry(dwa, res) == NULL, 712 ("double-retain of dynamic window for same resource")); 713 714 /* Insert a reference entry; we use M_NOWAIT to allow use from 715 * within a non-sleepable lock */ 716 rentry = malloc(sizeof(*rentry), M_BHND, M_NOWAIT); 717 if (rentry == NULL) 718 return (ENOMEM); 719 720 rentry->dw_res = res; 721 LIST_INSERT_HEAD(&dwa->refs, rentry, dw_link); 722 723 /* Update the free list */ 724 br->dwa_freelist &= ~(1 << (dwa->rnid)); 725 726 return (0); 727 } 728 729 /** 730 * Release a reference to @p dwa previously retained by @p res. If the 731 * reference count of @p dwa reaches zero, it will be added to the 732 * free list. 733 * 734 * @param br The resource state owning @p dwa. 735 * @param dwa The allocation record to be released. 736 * @param res The resource that currently owns a reference to @p dwa. 737 */ 738 void 739 bhndb_dw_release(struct bhndb_resources *br, struct bhndb_dw_alloc *dwa, 740 struct resource *r) 741 { 742 struct bhndb_dw_rentry *rentry; 743 744 /* Find the rentry */ 745 rentry = bhndb_dw_find_resource_entry(dwa, r); 746 KASSERT(rentry != NULL, ("over release of resource entry")); 747 748 LIST_REMOVE(rentry, dw_link); 749 free(rentry, M_BHND); 750 751 /* If this was the last reference, update the free list */ 752 if (LIST_EMPTY(&dwa->refs)) 753 br->dwa_freelist |= (1 << (dwa->rnid)); 754 } 755 756 /** 757 * Attempt to set (or reset) the target address of @p dwa to map @p size bytes 758 * at @p addr. 759 * 760 * This will apply any necessary window alignment and verify that 761 * the window is capable of mapping the requested range prior to modifying 762 * therecord. 763 * 764 * @param dev The device on which to issue the BHNDB_SET_WINDOW_ADDR() request. 765 * @param br The resource state owning @p dwa. 766 * @param dwa The allocation record to be configured. 767 * @param addr The address to be mapped via @p dwa. 768 * @param size The number of bytes to be mapped at @p addr. 769 * 770 * @retval 0 success 771 * @retval non-zero no usable register window available. 772 */ 773 int 774 bhndb_dw_set_addr(device_t dev, struct bhndb_resources *br, 775 struct bhndb_dw_alloc *dwa, bus_addr_t addr, bus_size_t size) 776 { 777 const struct bhndb_regwin *rw; 778 bus_addr_t offset; 779 int error; 780 781 rw = dwa->win; 782 783 KASSERT(bhndb_dw_is_free(br, dwa), 784 ("attempting to set the target address on an in-use window")); 785 786 /* Page-align the target address */ 787 offset = addr % rw->win_size; 788 dwa->target = addr - offset; 789 790 /* Verify that the window is large enough for the full target */ 791 if (rw->win_size - offset < size) 792 return (ENOMEM); 793 794 /* Update the window target */ 795 error = BHNDB_SET_WINDOW_ADDR(dev, dwa->win, dwa->target); 796 if (error) { 797 dwa->target = 0x0; 798 return (error); 799 } 800 801 return (0); 802 } 803 804 /** 805 * Return the count of @p type register windows in @p table. 806 * 807 * @param table The table to search. 808 * @param type The required window type, or BHNDB_REGWIN_T_INVALID to 809 * count all register window types. 810 */ 811 size_t 812 bhndb_regwin_count(const struct bhndb_regwin *table, 813 bhndb_regwin_type_t type) 814 { 815 const struct bhndb_regwin *rw; 816 size_t count; 817 818 count = 0; 819 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) { 820 if (type == BHNDB_REGWIN_T_INVALID || rw->win_type == type) 821 count++; 822 } 823 824 return (count); 825 } 826 827 /** 828 * Search @p table for the first window with the given @p type. 829 * 830 * @param table The table to search. 831 * @param type The required window type. 832 * @param min_size The minimum window size. 833 * 834 * @retval bhndb_regwin The first matching window. 835 * @retval NULL If no window of the requested type could be found. 836 */ 837 const struct bhndb_regwin * 838 bhndb_regwin_find_type(const struct bhndb_regwin *table, 839 bhndb_regwin_type_t type, bus_size_t min_size) 840 { 841 const struct bhndb_regwin *rw; 842 843 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) 844 { 845 if (rw->win_type == type && rw->win_size >= min_size) 846 return (rw); 847 } 848 849 return (NULL); 850 } 851 852 /** 853 * Search @p windows for the first matching core window. 854 * 855 * @param table The table to search. 856 * @param class The required core class. 857 * @param unit The required core unit, or -1. 858 * @param port_type The required port type. 859 * @param port The required port. 860 * @param region The required region. 861 * 862 * @retval bhndb_regwin The first matching window. 863 * @retval NULL If no matching window was found. 864 */ 865 const struct bhndb_regwin * 866 bhndb_regwin_find_core(const struct bhndb_regwin *table, bhnd_devclass_t class, 867 int unit, bhnd_port_type port_type, u_int port, u_int region) 868 { 869 const struct bhndb_regwin *rw; 870 871 for (rw = table; rw->win_type != BHNDB_REGWIN_T_INVALID; rw++) 872 { 873 if (rw->win_type != BHNDB_REGWIN_T_CORE) 874 continue; 875 876 if (rw->d.core.class != class) 877 continue; 878 879 if (unit != -1 && rw->d.core.unit != unit) 880 continue; 881 882 if (rw->d.core.port_type != port_type) 883 continue; 884 885 if (rw->d.core.port != port) 886 continue; 887 888 if (rw->d.core.region != region) 889 continue; 890 891 return (rw); 892 } 893 894 return (NULL); 895 } 896 897 /** 898 * Search @p windows for the best available window of at least @p min_size. 899 * 900 * Search order: 901 * - BHND_REGWIN_T_CORE 902 * - BHND_REGWIN_T_DYN 903 * 904 * @param table The table to search. 905 * @param class The required core class. 906 * @param unit The required core unit, or -1. 907 * @param port_type The required port type. 908 * @param port The required port. 909 * @param region The required region. 910 * @param min_size The minimum window size. 911 * 912 * @retval bhndb_regwin The first matching window. 913 * @retval NULL If no matching window was found. 914 */ 915 const struct bhndb_regwin * 916 bhndb_regwin_find_best(const struct bhndb_regwin *table, 917 bhnd_devclass_t class, int unit, bhnd_port_type port_type, u_int port, 918 u_int region, bus_size_t min_size) 919 { 920 const struct bhndb_regwin *rw; 921 922 /* Prefer a fixed core mapping */ 923 rw = bhndb_regwin_find_core(table, class, unit, port_type, 924 port, region); 925 if (rw != NULL) 926 return (rw); 927 928 /* Fall back on a generic dynamic window */ 929 return (bhndb_regwin_find_type(table, BHNDB_REGWIN_T_DYN, min_size)); 930 } 931 932 /** 933 * Return true if @p regw defines a static port register window, and 934 * the mapped port is actually defined on @p dev. 935 * 936 * @param regw A register window to match against. 937 * @param dev A bhnd(4) bus device. 938 */ 939 bool 940 bhndb_regwin_matches_device(const struct bhndb_regwin *regw, device_t dev) 941 { 942 /* Only core windows are supported */ 943 if (regw->win_type != BHNDB_REGWIN_T_CORE) 944 return (false); 945 946 /* Device class must match */ 947 if (bhnd_get_class(dev) != regw->d.core.class) 948 return (false); 949 950 /* Device unit must match */ 951 if (bhnd_get_core_unit(dev) != regw->d.core.unit) 952 return (false); 953 954 /* The regwin port/region must be defined. */ 955 if (!bhnd_is_region_valid(dev, regw->d.core.port_type, regw->d.core.port, 956 regw->d.core.region)) 957 { 958 return (false); 959 } 960 961 /* Matches */ 962 return (true); 963 } 964 965 /** 966 * Search for a core resource priority descriptor in @p table that matches 967 * @p device. 968 * 969 * @param table The table to search. 970 * @param device A bhnd(4) bus device. 971 */ 972 const struct bhndb_hw_priority * 973 bhndb_hw_priority_find_device(const struct bhndb_hw_priority *table, 974 device_t device) 975 { 976 const struct bhndb_hw_priority *hp; 977 978 for (hp = table; hp->ports != NULL; hp++) { 979 if (bhnd_device_matches(device, &hp->match)) 980 return (hp); 981 } 982 983 /* not found */ 984 return (NULL); 985 } 986