1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2009-2014 The FreeBSD Foundation 5 * All rights reserved. 6 * 7 * This software was developed by Andrew Turner under sponsorship from 8 * the FreeBSD Foundation. 9 * This software was developed by Semihalf under sponsorship from 10 * the FreeBSD Foundation. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 */ 33 34 #include <sys/cdefs.h> 35 __FBSDID("$FreeBSD$"); 36 37 #include <sys/param.h> 38 #include <sys/systm.h> 39 #include <sys/kernel.h> 40 #include <sys/module.h> 41 #include <sys/bus.h> 42 #include <sys/limits.h> 43 #include <sys/sysctl.h> 44 45 #include <machine/resource.h> 46 47 #include <dev/fdt/fdt_common.h> 48 #include <dev/ofw/ofw_bus.h> 49 #include <dev/ofw/ofw_bus_subr.h> 50 #include <dev/ofw/openfirm.h> 51 52 #include "ofw_bus_if.h" 53 54 #ifdef DEBUG 55 #define debugf(fmt, args...) do { printf("%s(): ", __func__); \ 56 printf(fmt,##args); } while (0) 57 #else 58 #define debugf(fmt, args...) 59 #endif 60 61 #define FDT_COMPAT_LEN 255 62 63 #define FDT_REG_CELLS 4 64 #define FDT_RANGES_SIZE 48 65 66 SYSCTL_NODE(_hw, OID_AUTO, fdt, CTLFLAG_RD, 0, "Flattened Device Tree"); 67 68 vm_paddr_t fdt_immr_pa; 69 vm_offset_t fdt_immr_va; 70 vm_offset_t fdt_immr_size; 71 72 struct fdt_ic_list fdt_ic_list_head = SLIST_HEAD_INITIALIZER(fdt_ic_list_head); 73 74 static int fdt_is_compatible(phandle_t, const char *); 75 76 static int 77 fdt_get_range_by_busaddr(phandle_t node, u_long addr, u_long *base, 78 u_long *size) 79 { 80 pcell_t ranges[32], *rangesptr; 81 pcell_t addr_cells, size_cells, par_addr_cells; 82 u_long bus_addr, par_bus_addr, pbase, psize; 83 int err, i, len, tuple_size, tuples; 84 85 if (node == 0) { 86 *base = 0; 87 *size = ULONG_MAX; 88 return (0); 89 } 90 91 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0) 92 return (ENXIO); 93 /* 94 * Process 'ranges' property. 95 */ 96 par_addr_cells = fdt_parent_addr_cells(node); 97 if (par_addr_cells > 2) { 98 return (ERANGE); 99 } 100 101 len = OF_getproplen(node, "ranges"); 102 if (len < 0) 103 return (-1); 104 if (len > sizeof(ranges)) 105 return (ENOMEM); 106 if (len == 0) { 107 return (fdt_get_range_by_busaddr(OF_parent(node), addr, 108 base, size)); 109 } 110 111 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0) 112 return (EINVAL); 113 114 tuple_size = addr_cells + par_addr_cells + size_cells; 115 tuples = len / (tuple_size * sizeof(cell_t)); 116 117 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2) 118 return (ERANGE); 119 120 *base = 0; 121 *size = 0; 122 123 for (i = 0; i < tuples; i++) { 124 rangesptr = &ranges[i * tuple_size]; 125 126 bus_addr = fdt_data_get((void *)rangesptr, addr_cells); 127 if (bus_addr != addr) 128 continue; 129 rangesptr += addr_cells; 130 131 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells); 132 rangesptr += par_addr_cells; 133 134 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr, 135 &pbase, &psize); 136 if (err > 0) 137 return (err); 138 if (err == 0) 139 *base = pbase; 140 else 141 *base = par_bus_addr; 142 143 *size = fdt_data_get((void *)rangesptr, size_cells); 144 145 return (0); 146 } 147 148 return (EINVAL); 149 } 150 151 int 152 fdt_get_range(phandle_t node, int range_id, u_long *base, u_long *size) 153 { 154 pcell_t ranges[FDT_RANGES_SIZE], *rangesptr; 155 pcell_t addr_cells, size_cells, par_addr_cells; 156 u_long par_bus_addr, pbase, psize; 157 int err, len; 158 159 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0) 160 return (ENXIO); 161 /* 162 * Process 'ranges' property. 163 */ 164 par_addr_cells = fdt_parent_addr_cells(node); 165 if (par_addr_cells > 2) 166 return (ERANGE); 167 168 len = OF_getproplen(node, "ranges"); 169 if (len > sizeof(ranges)) 170 return (ENOMEM); 171 if (len == 0) { 172 *base = 0; 173 *size = ULONG_MAX; 174 return (0); 175 } 176 177 if (!(range_id < len)) 178 return (ERANGE); 179 180 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0) 181 return (EINVAL); 182 183 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2) 184 return (ERANGE); 185 186 *base = 0; 187 *size = 0; 188 rangesptr = &ranges[range_id]; 189 190 *base = fdt_data_get((void *)rangesptr, addr_cells); 191 rangesptr += addr_cells; 192 193 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells); 194 rangesptr += par_addr_cells; 195 196 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr, 197 &pbase, &psize); 198 if (err == 0) 199 *base += pbase; 200 else 201 *base += par_bus_addr; 202 203 *size = fdt_data_get((void *)rangesptr, size_cells); 204 return (0); 205 } 206 207 int 208 fdt_immr_addr(vm_offset_t immr_va) 209 { 210 phandle_t node; 211 u_long base, size; 212 int r; 213 214 /* 215 * Try to access the SOC node directly i.e. through /aliases/. 216 */ 217 if ((node = OF_finddevice("soc")) != -1) 218 if (fdt_is_compatible(node, "simple-bus")) 219 goto moveon; 220 /* 221 * Find the node the long way. 222 */ 223 if ((node = OF_finddevice("/")) == -1) 224 return (ENXIO); 225 226 if ((node = fdt_find_compatible(node, "simple-bus", 0)) == 0) 227 return (ENXIO); 228 229 moveon: 230 if ((r = fdt_get_range(node, 0, &base, &size)) == 0) { 231 fdt_immr_pa = base; 232 fdt_immr_va = immr_va; 233 fdt_immr_size = size; 234 } 235 236 return (r); 237 } 238 239 /* 240 * This routine is an early-usage version of the ofw_bus_is_compatible() when 241 * the ofw_bus I/F is not available (like early console routines and similar). 242 * Note the buffer has to be on the stack since malloc() is usually not 243 * available in such cases either. 244 */ 245 static int 246 fdt_is_compatible(phandle_t node, const char *compatstr) 247 { 248 char buf[FDT_COMPAT_LEN]; 249 char *compat; 250 int len, onelen, l, rv; 251 252 if ((len = OF_getproplen(node, "compatible")) <= 0) 253 return (0); 254 255 compat = (char *)&buf; 256 bzero(compat, FDT_COMPAT_LEN); 257 258 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0) 259 return (0); 260 261 onelen = strlen(compatstr); 262 rv = 0; 263 while (len > 0) { 264 if (strncasecmp(compat, compatstr, onelen) == 0) { 265 /* Found it. */ 266 rv = 1; 267 break; 268 } 269 /* Slide to the next sub-string. */ 270 l = strlen(compat) + 1; 271 compat += l; 272 len -= l; 273 } 274 275 return (rv); 276 } 277 278 int 279 fdt_is_compatible_strict(phandle_t node, const char *compatible) 280 { 281 char compat[FDT_COMPAT_LEN]; 282 283 if (OF_getproplen(node, "compatible") <= 0) 284 return (0); 285 286 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0) 287 return (0); 288 289 if (strncasecmp(compat, compatible, FDT_COMPAT_LEN) == 0) 290 /* This fits. */ 291 return (1); 292 293 return (0); 294 } 295 296 phandle_t 297 fdt_find_compatible(phandle_t start, const char *compat, int strict) 298 { 299 phandle_t child; 300 301 /* 302 * Traverse all children of 'start' node, and find first with 303 * matching 'compatible' property. 304 */ 305 for (child = OF_child(start); child != 0; child = OF_peer(child)) 306 if (fdt_is_compatible(child, compat)) { 307 if (strict) 308 if (!fdt_is_compatible_strict(child, compat)) 309 continue; 310 return (child); 311 } 312 return (0); 313 } 314 315 phandle_t 316 fdt_depth_search_compatible(phandle_t start, const char *compat, int strict) 317 { 318 phandle_t child, node; 319 320 /* 321 * Depth-search all descendants of 'start' node, and find first with 322 * matching 'compatible' property. 323 */ 324 for (node = OF_child(start); node != 0; node = OF_peer(node)) { 325 if (fdt_is_compatible(node, compat) && 326 (strict == 0 || fdt_is_compatible_strict(node, compat))) { 327 return (node); 328 } 329 child = fdt_depth_search_compatible(node, compat, strict); 330 if (child != 0) 331 return (child); 332 } 333 return (0); 334 } 335 336 int 337 fdt_parent_addr_cells(phandle_t node) 338 { 339 pcell_t addr_cells; 340 341 /* Find out #address-cells of the superior bus. */ 342 if (OF_searchprop(OF_parent(node), "#address-cells", &addr_cells, 343 sizeof(addr_cells)) <= 0) 344 return (2); 345 346 return ((int)fdt32_to_cpu(addr_cells)); 347 } 348 349 u_long 350 fdt_data_get(void *data, int cells) 351 { 352 353 if (cells == 1) 354 return (fdt32_to_cpu(*((uint32_t *)data))); 355 356 return (fdt64_to_cpu(*((uint64_t *)data))); 357 } 358 359 int 360 fdt_addrsize_cells(phandle_t node, int *addr_cells, int *size_cells) 361 { 362 pcell_t cell; 363 int cell_size; 364 365 /* 366 * Retrieve #{address,size}-cells. 367 */ 368 cell_size = sizeof(cell); 369 if (OF_getencprop(node, "#address-cells", &cell, cell_size) < cell_size) 370 cell = 2; 371 *addr_cells = (int)cell; 372 373 if (OF_getencprop(node, "#size-cells", &cell, cell_size) < cell_size) 374 cell = 1; 375 *size_cells = (int)cell; 376 377 if (*addr_cells > 3 || *size_cells > 2) 378 return (ERANGE); 379 return (0); 380 } 381 382 int 383 fdt_data_to_res(pcell_t *data, int addr_cells, int size_cells, u_long *start, 384 u_long *count) 385 { 386 387 /* Address portion. */ 388 if (addr_cells > 2) 389 return (ERANGE); 390 391 *start = fdt_data_get((void *)data, addr_cells); 392 data += addr_cells; 393 394 /* Size portion. */ 395 if (size_cells > 2) 396 return (ERANGE); 397 398 *count = fdt_data_get((void *)data, size_cells); 399 return (0); 400 } 401 402 int 403 fdt_regsize(phandle_t node, u_long *base, u_long *size) 404 { 405 pcell_t reg[4]; 406 int addr_cells, len, size_cells; 407 408 if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells)) 409 return (ENXIO); 410 411 if ((sizeof(pcell_t) * (addr_cells + size_cells)) > sizeof(reg)) 412 return (ENOMEM); 413 414 len = OF_getprop(node, "reg", ®, sizeof(reg)); 415 if (len <= 0) 416 return (EINVAL); 417 418 *base = fdt_data_get(®[0], addr_cells); 419 *size = fdt_data_get(®[addr_cells], size_cells); 420 return (0); 421 } 422 423 int 424 fdt_get_phyaddr(phandle_t node, device_t dev, int *phy_addr, void **phy_sc) 425 { 426 phandle_t phy_node; 427 pcell_t phy_handle, phy_reg; 428 uint32_t i; 429 device_t parent, child; 430 431 if (OF_getencprop(node, "phy-handle", (void *)&phy_handle, 432 sizeof(phy_handle)) <= 0) 433 return (ENXIO); 434 435 phy_node = OF_node_from_xref(phy_handle); 436 437 if (OF_getencprop(phy_node, "reg", (void *)&phy_reg, 438 sizeof(phy_reg)) <= 0) 439 return (ENXIO); 440 441 *phy_addr = phy_reg; 442 443 /* 444 * Search for softc used to communicate with phy. 445 */ 446 447 /* 448 * Step 1: Search for ancestor of the phy-node with a "phy-handle" 449 * property set. 450 */ 451 phy_node = OF_parent(phy_node); 452 while (phy_node != 0) { 453 if (OF_getprop(phy_node, "phy-handle", (void *)&phy_handle, 454 sizeof(phy_handle)) > 0) 455 break; 456 phy_node = OF_parent(phy_node); 457 } 458 if (phy_node == 0) 459 return (ENXIO); 460 461 /* 462 * Step 2: For each device with the same parent and name as ours 463 * compare its node with the one found in step 1, ancestor of phy 464 * node (stored in phy_node). 465 */ 466 parent = device_get_parent(dev); 467 i = 0; 468 child = device_find_child(parent, device_get_name(dev), i); 469 while (child != NULL) { 470 if (ofw_bus_get_node(child) == phy_node) 471 break; 472 i++; 473 child = device_find_child(parent, device_get_name(dev), i); 474 } 475 if (child == NULL) 476 return (ENXIO); 477 478 /* 479 * Use softc of the device found. 480 */ 481 *phy_sc = (void *)device_get_softc(child); 482 483 return (0); 484 } 485 486 int 487 fdt_get_reserved_regions(struct mem_region *mr, int *mrcnt) 488 { 489 pcell_t reserve[FDT_REG_CELLS * FDT_MEM_REGIONS]; 490 pcell_t *reservep; 491 phandle_t memory, root; 492 int addr_cells, size_cells; 493 int i, res_len, rv, tuple_size, tuples; 494 495 root = OF_finddevice("/"); 496 memory = OF_finddevice("/memory"); 497 if (memory == -1) { 498 rv = ENXIO; 499 goto out; 500 } 501 502 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells, 503 &size_cells)) != 0) 504 goto out; 505 506 if (addr_cells > 2) { 507 rv = ERANGE; 508 goto out; 509 } 510 511 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells); 512 513 res_len = OF_getproplen(root, "memreserve"); 514 if (res_len <= 0 || res_len > sizeof(reserve)) { 515 rv = ERANGE; 516 goto out; 517 } 518 519 if (OF_getprop(root, "memreserve", reserve, res_len) <= 0) { 520 rv = ENXIO; 521 goto out; 522 } 523 524 tuples = res_len / tuple_size; 525 reservep = (pcell_t *)&reserve; 526 for (i = 0; i < tuples; i++) { 527 528 rv = fdt_data_to_res(reservep, addr_cells, size_cells, 529 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size); 530 531 if (rv != 0) 532 goto out; 533 534 reservep += addr_cells + size_cells; 535 } 536 537 *mrcnt = i; 538 rv = 0; 539 out: 540 return (rv); 541 } 542 543 int 544 fdt_get_mem_regions(struct mem_region *mr, int *mrcnt, uint64_t *memsize) 545 { 546 pcell_t reg[FDT_REG_CELLS * FDT_MEM_REGIONS]; 547 pcell_t *regp; 548 phandle_t memory; 549 uint64_t memory_size; 550 int addr_cells, size_cells; 551 int i, reg_len, rv, tuple_size, tuples; 552 553 memory = OF_finddevice("/memory"); 554 if (memory == -1) { 555 rv = ENXIO; 556 goto out; 557 } 558 559 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells, 560 &size_cells)) != 0) 561 goto out; 562 563 if (addr_cells > 2) { 564 rv = ERANGE; 565 goto out; 566 } 567 568 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells); 569 reg_len = OF_getproplen(memory, "reg"); 570 if (reg_len <= 0 || reg_len > sizeof(reg)) { 571 rv = ERANGE; 572 goto out; 573 } 574 575 if (OF_getprop(memory, "reg", reg, reg_len) <= 0) { 576 rv = ENXIO; 577 goto out; 578 } 579 580 memory_size = 0; 581 tuples = reg_len / tuple_size; 582 regp = (pcell_t *)® 583 for (i = 0; i < tuples; i++) { 584 585 rv = fdt_data_to_res(regp, addr_cells, size_cells, 586 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size); 587 588 if (rv != 0) 589 goto out; 590 591 regp += addr_cells + size_cells; 592 memory_size += mr[i].mr_size; 593 } 594 595 if (memory_size == 0) { 596 rv = ERANGE; 597 goto out; 598 } 599 600 *mrcnt = i; 601 if (memsize != NULL) 602 *memsize = memory_size; 603 rv = 0; 604 out: 605 return (rv); 606 } 607 608 int 609 fdt_get_chosen_bootargs(char *bootargs, size_t max_size) 610 { 611 phandle_t chosen; 612 613 chosen = OF_finddevice("/chosen"); 614 if (chosen == -1) 615 return (ENXIO); 616 if (OF_getprop(chosen, "bootargs", bootargs, max_size) == -1) 617 return (ENXIO); 618 return (0); 619 } 620