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