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 | CTLFLAG_MPSAFE, 0, 67 "Flattened Device Tree"); 68 69 vm_paddr_t fdt_immr_pa; 70 vm_offset_t fdt_immr_va; 71 vm_offset_t fdt_immr_size; 72 73 struct fdt_ic_list fdt_ic_list_head = SLIST_HEAD_INITIALIZER(fdt_ic_list_head); 74 75 static int 76 fdt_get_range_by_busaddr(phandle_t node, u_long addr, u_long *base, 77 u_long *size) 78 { 79 pcell_t ranges[32], *rangesptr; 80 pcell_t addr_cells, size_cells, par_addr_cells; 81 u_long bus_addr, par_bus_addr, pbase, psize; 82 int err, i, len, tuple_size, tuples; 83 84 if (node == 0) { 85 *base = 0; 86 *size = ULONG_MAX; 87 return (0); 88 } 89 90 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0) 91 return (ENXIO); 92 /* 93 * Process 'ranges' property. 94 */ 95 par_addr_cells = fdt_parent_addr_cells(node); 96 if (par_addr_cells > 2) { 97 return (ERANGE); 98 } 99 100 len = OF_getproplen(node, "ranges"); 101 if (len < 0) 102 return (-1); 103 if (len > sizeof(ranges)) 104 return (ENOMEM); 105 if (len == 0) { 106 return (fdt_get_range_by_busaddr(OF_parent(node), addr, 107 base, size)); 108 } 109 110 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0) 111 return (EINVAL); 112 113 tuple_size = addr_cells + par_addr_cells + size_cells; 114 tuples = len / (tuple_size * sizeof(cell_t)); 115 116 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2) 117 return (ERANGE); 118 119 *base = 0; 120 *size = 0; 121 122 for (i = 0; i < tuples; i++) { 123 rangesptr = &ranges[i * tuple_size]; 124 125 bus_addr = fdt_data_get((void *)rangesptr, addr_cells); 126 if (bus_addr != addr) 127 continue; 128 rangesptr += addr_cells; 129 130 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells); 131 rangesptr += par_addr_cells; 132 133 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr, 134 &pbase, &psize); 135 if (err > 0) 136 return (err); 137 if (err == 0) 138 *base = pbase; 139 else 140 *base = par_bus_addr; 141 142 *size = fdt_data_get((void *)rangesptr, size_cells); 143 144 return (0); 145 } 146 147 return (EINVAL); 148 } 149 150 int 151 fdt_get_range(phandle_t node, int range_id, u_long *base, u_long *size) 152 { 153 pcell_t ranges[FDT_RANGES_SIZE], *rangesptr; 154 pcell_t addr_cells, size_cells, par_addr_cells; 155 u_long par_bus_addr, pbase, psize; 156 int err, len; 157 158 if ((fdt_addrsize_cells(node, &addr_cells, &size_cells)) != 0) 159 return (ENXIO); 160 /* 161 * Process 'ranges' property. 162 */ 163 par_addr_cells = fdt_parent_addr_cells(node); 164 if (par_addr_cells > 2) 165 return (ERANGE); 166 167 len = OF_getproplen(node, "ranges"); 168 if (len > sizeof(ranges)) 169 return (ENOMEM); 170 if (len == 0) { 171 *base = 0; 172 *size = ULONG_MAX; 173 return (0); 174 } 175 176 if (!(range_id < len)) 177 return (ERANGE); 178 179 if (OF_getprop(node, "ranges", ranges, sizeof(ranges)) <= 0) 180 return (EINVAL); 181 182 if (par_addr_cells > 2 || addr_cells > 2 || size_cells > 2) 183 return (ERANGE); 184 185 *base = 0; 186 *size = 0; 187 rangesptr = &ranges[range_id]; 188 189 *base = fdt_data_get((void *)rangesptr, addr_cells); 190 rangesptr += addr_cells; 191 192 par_bus_addr = fdt_data_get((void *)rangesptr, par_addr_cells); 193 rangesptr += par_addr_cells; 194 195 err = fdt_get_range_by_busaddr(OF_parent(node), par_bus_addr, 196 &pbase, &psize); 197 if (err == 0) 198 *base += pbase; 199 else 200 *base += par_bus_addr; 201 202 *size = fdt_data_get((void *)rangesptr, size_cells); 203 return (0); 204 } 205 206 int 207 fdt_immr_addr(vm_offset_t immr_va) 208 { 209 phandle_t node; 210 u_long base, size; 211 int r; 212 213 /* 214 * Try to access the SOC node directly i.e. through /aliases/. 215 */ 216 if ((node = OF_finddevice("soc")) != -1) 217 if (ofw_bus_node_is_compatible(node, "simple-bus")) 218 goto moveon; 219 /* 220 * Find the node the long way. 221 */ 222 if ((node = OF_finddevice("/")) == -1) 223 return (ENXIO); 224 225 if ((node = fdt_find_compatible(node, "simple-bus", 0)) == 0) 226 return (ENXIO); 227 228 moveon: 229 if ((r = fdt_get_range(node, 0, &base, &size)) == 0) { 230 fdt_immr_pa = base; 231 fdt_immr_va = immr_va; 232 fdt_immr_size = size; 233 } 234 235 return (r); 236 } 237 238 int 239 fdt_is_compatible_strict(phandle_t node, const char *compatible) 240 { 241 char compat[FDT_COMPAT_LEN]; 242 243 if (OF_getproplen(node, "compatible") <= 0) 244 return (0); 245 246 if (OF_getprop(node, "compatible", compat, FDT_COMPAT_LEN) < 0) 247 return (0); 248 249 if (strncasecmp(compat, compatible, FDT_COMPAT_LEN) == 0) 250 /* This fits. */ 251 return (1); 252 253 return (0); 254 } 255 256 phandle_t 257 fdt_find_compatible(phandle_t start, const char *compat, int strict) 258 { 259 phandle_t child; 260 261 /* 262 * Traverse all children of 'start' node, and find first with 263 * matching 'compatible' property. 264 */ 265 for (child = OF_child(start); child != 0; child = OF_peer(child)) 266 if (ofw_bus_node_is_compatible(child, compat)) { 267 if (strict) 268 if (!fdt_is_compatible_strict(child, compat)) 269 continue; 270 return (child); 271 } 272 return (0); 273 } 274 275 phandle_t 276 fdt_depth_search_compatible(phandle_t start, const char *compat, int strict) 277 { 278 phandle_t child, node; 279 280 /* 281 * Depth-search all descendants of 'start' node, and find first with 282 * matching 'compatible' property. 283 */ 284 for (node = OF_child(start); node != 0; node = OF_peer(node)) { 285 if (ofw_bus_node_is_compatible(node, compat) && 286 (strict == 0 || fdt_is_compatible_strict(node, compat))) { 287 return (node); 288 } 289 child = fdt_depth_search_compatible(node, compat, strict); 290 if (child != 0) 291 return (child); 292 } 293 return (0); 294 } 295 296 int 297 fdt_parent_addr_cells(phandle_t node) 298 { 299 pcell_t addr_cells; 300 301 /* Find out #address-cells of the superior bus. */ 302 if (OF_searchprop(OF_parent(node), "#address-cells", &addr_cells, 303 sizeof(addr_cells)) <= 0) 304 return (2); 305 306 return ((int)fdt32_to_cpu(addr_cells)); 307 } 308 309 u_long 310 fdt_data_get(void *data, int cells) 311 { 312 313 if (cells == 1) 314 return (fdt32_to_cpu(*((uint32_t *)data))); 315 316 return (fdt64_to_cpu(*((uint64_t *)data))); 317 } 318 319 int 320 fdt_addrsize_cells(phandle_t node, int *addr_cells, int *size_cells) 321 { 322 pcell_t cell; 323 int cell_size; 324 325 /* 326 * Retrieve #{address,size}-cells. 327 */ 328 cell_size = sizeof(cell); 329 if (OF_getencprop(node, "#address-cells", &cell, cell_size) < cell_size) 330 cell = 2; 331 *addr_cells = (int)cell; 332 333 if (OF_getencprop(node, "#size-cells", &cell, cell_size) < cell_size) 334 cell = 1; 335 *size_cells = (int)cell; 336 337 if (*addr_cells > 3 || *size_cells > 2) 338 return (ERANGE); 339 return (0); 340 } 341 342 int 343 fdt_data_to_res(pcell_t *data, int addr_cells, int size_cells, u_long *start, 344 u_long *count) 345 { 346 347 /* Address portion. */ 348 if (addr_cells > 2) 349 return (ERANGE); 350 351 *start = fdt_data_get((void *)data, addr_cells); 352 data += addr_cells; 353 354 /* Size portion. */ 355 if (size_cells > 2) 356 return (ERANGE); 357 358 *count = fdt_data_get((void *)data, size_cells); 359 return (0); 360 } 361 362 int 363 fdt_regsize(phandle_t node, u_long *base, u_long *size) 364 { 365 pcell_t reg[4]; 366 int addr_cells, len, size_cells; 367 368 if (fdt_addrsize_cells(OF_parent(node), &addr_cells, &size_cells)) 369 return (ENXIO); 370 371 if ((sizeof(pcell_t) * (addr_cells + size_cells)) > sizeof(reg)) 372 return (ENOMEM); 373 374 len = OF_getprop(node, "reg", ®, sizeof(reg)); 375 if (len <= 0) 376 return (EINVAL); 377 378 *base = fdt_data_get(®[0], addr_cells); 379 *size = fdt_data_get(®[addr_cells], size_cells); 380 return (0); 381 } 382 383 int 384 fdt_get_phyaddr(phandle_t node, device_t dev, int *phy_addr, void **phy_sc) 385 { 386 phandle_t phy_node; 387 pcell_t phy_handle, phy_reg; 388 uint32_t i; 389 device_t parent, child; 390 391 if (OF_getencprop(node, "phy-handle", (void *)&phy_handle, 392 sizeof(phy_handle)) <= 0) 393 return (ENXIO); 394 395 phy_node = OF_node_from_xref(phy_handle); 396 397 if (OF_getencprop(phy_node, "reg", (void *)&phy_reg, 398 sizeof(phy_reg)) <= 0) 399 return (ENXIO); 400 401 *phy_addr = phy_reg; 402 403 if (phy_sc == NULL) 404 return (0); 405 406 /* 407 * Search for softc used to communicate with phy. 408 */ 409 410 /* 411 * Step 1: Search for ancestor of the phy-node with a "phy-handle" 412 * property set. 413 */ 414 phy_node = OF_parent(phy_node); 415 while (phy_node != 0) { 416 if (OF_getprop(phy_node, "phy-handle", (void *)&phy_handle, 417 sizeof(phy_handle)) > 0) 418 break; 419 phy_node = OF_parent(phy_node); 420 } 421 if (phy_node == 0) 422 return (ENXIO); 423 424 /* 425 * Step 2: For each device with the same parent and name as ours 426 * compare its node with the one found in step 1, ancestor of phy 427 * node (stored in phy_node). 428 */ 429 parent = device_get_parent(dev); 430 i = 0; 431 child = device_find_child(parent, device_get_name(dev), i); 432 while (child != NULL) { 433 if (ofw_bus_get_node(child) == phy_node) 434 break; 435 i++; 436 child = device_find_child(parent, device_get_name(dev), i); 437 } 438 if (child == NULL) 439 return (ENXIO); 440 441 /* 442 * Use softc of the device found. 443 */ 444 *phy_sc = (void *)device_get_softc(child); 445 446 return (0); 447 } 448 449 int 450 fdt_get_reserved_regions(struct mem_region *mr, int *mrcnt) 451 { 452 pcell_t reserve[FDT_REG_CELLS * FDT_MEM_REGIONS]; 453 pcell_t *reservep; 454 phandle_t memory, root; 455 int addr_cells, size_cells; 456 int i, res_len, rv, tuple_size, tuples; 457 458 root = OF_finddevice("/"); 459 memory = OF_finddevice("/memory"); 460 if (memory == -1) { 461 rv = ENXIO; 462 goto out; 463 } 464 465 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells, 466 &size_cells)) != 0) 467 goto out; 468 469 if (addr_cells > 2) { 470 rv = ERANGE; 471 goto out; 472 } 473 474 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells); 475 476 res_len = OF_getproplen(root, "memreserve"); 477 if (res_len <= 0 || res_len > sizeof(reserve)) { 478 rv = ERANGE; 479 goto out; 480 } 481 482 if (OF_getprop(root, "memreserve", reserve, res_len) <= 0) { 483 rv = ENXIO; 484 goto out; 485 } 486 487 tuples = res_len / tuple_size; 488 reservep = (pcell_t *)&reserve; 489 for (i = 0; i < tuples; i++) { 490 491 rv = fdt_data_to_res(reservep, addr_cells, size_cells, 492 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size); 493 494 if (rv != 0) 495 goto out; 496 497 reservep += addr_cells + size_cells; 498 } 499 500 *mrcnt = i; 501 rv = 0; 502 out: 503 return (rv); 504 } 505 506 int 507 fdt_get_reserved_mem(struct mem_region *reserved, int *mreserved) 508 { 509 pcell_t reg[FDT_REG_CELLS]; 510 phandle_t child, root; 511 int addr_cells, size_cells; 512 int i, rv; 513 514 root = OF_finddevice("/reserved-memory"); 515 if (root == -1) { 516 return (ENXIO); 517 } 518 519 if ((rv = fdt_addrsize_cells(root, &addr_cells, &size_cells)) != 0) 520 return (rv); 521 522 if (addr_cells + size_cells > FDT_REG_CELLS) 523 panic("Too many address and size cells %d %d", addr_cells, 524 size_cells); 525 526 i = 0; 527 for (child = OF_child(root); child != 0; child = OF_peer(child)) { 528 if (!OF_hasprop(child, "no-map")) 529 continue; 530 531 rv = OF_getprop(child, "reg", reg, sizeof(reg)); 532 if (rv <= 0) 533 /* XXX: Does a no-map of a dynamic range make sense? */ 534 continue; 535 536 fdt_data_to_res(reg, addr_cells, size_cells, 537 (u_long *)&reserved[i].mr_start, 538 (u_long *)&reserved[i].mr_size); 539 i++; 540 } 541 542 *mreserved = i; 543 544 return (0); 545 } 546 547 int 548 fdt_get_mem_regions(struct mem_region *mr, int *mrcnt, uint64_t *memsize) 549 { 550 pcell_t reg[FDT_REG_CELLS * FDT_MEM_REGIONS]; 551 pcell_t *regp; 552 phandle_t memory; 553 uint64_t memory_size; 554 int addr_cells, size_cells; 555 int i, reg_len, rv, tuple_size, tuples; 556 557 memory = OF_finddevice("/memory"); 558 if (memory == -1) { 559 rv = ENXIO; 560 goto out; 561 } 562 563 if ((rv = fdt_addrsize_cells(OF_parent(memory), &addr_cells, 564 &size_cells)) != 0) 565 goto out; 566 567 if (addr_cells > 2) { 568 rv = ERANGE; 569 goto out; 570 } 571 572 tuple_size = sizeof(pcell_t) * (addr_cells + size_cells); 573 reg_len = OF_getproplen(memory, "reg"); 574 if (reg_len <= 0 || reg_len > sizeof(reg)) { 575 rv = ERANGE; 576 goto out; 577 } 578 579 if (OF_getprop(memory, "reg", reg, reg_len) <= 0) { 580 rv = ENXIO; 581 goto out; 582 } 583 584 memory_size = 0; 585 tuples = reg_len / tuple_size; 586 regp = (pcell_t *)® 587 for (i = 0; i < tuples; i++) { 588 589 rv = fdt_data_to_res(regp, addr_cells, size_cells, 590 (u_long *)&mr[i].mr_start, (u_long *)&mr[i].mr_size); 591 592 if (rv != 0) 593 goto out; 594 595 regp += addr_cells + size_cells; 596 memory_size += mr[i].mr_size; 597 } 598 599 if (memory_size == 0) { 600 rv = ERANGE; 601 goto out; 602 } 603 604 *mrcnt = i; 605 if (memsize != NULL) 606 *memsize = memory_size; 607 rv = 0; 608 out: 609 return (rv); 610 } 611 612 int 613 fdt_get_chosen_bootargs(char *bootargs, size_t max_size) 614 { 615 phandle_t chosen; 616 617 chosen = OF_finddevice("/chosen"); 618 if (chosen == -1) 619 return (ENXIO); 620 if (OF_getprop(chosen, "bootargs", bootargs, max_size) == -1) 621 return (ENXIO); 622 return (0); 623 } 624