1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2009-2010 The FreeBSD Foundation 5 * 6 * This software was developed by Semihalf under sponsorship from 7 * the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 28 * SUCH DAMAGE. 29 */ 30 31 #include <sys/cdefs.h> 32 __FBSDID("$FreeBSD$"); 33 34 #include <sys/param.h> 35 #include <sys/kernel.h> 36 #include <sys/malloc.h> 37 #include <sys/systm.h> 38 39 #include <contrib/libfdt/libfdt.h> 40 41 #include <machine/stdarg.h> 42 43 #include <dev/fdt/fdt_common.h> 44 #include <dev/ofw/ofwvar.h> 45 #include <dev/ofw/openfirm.h> 46 #include <dev/ofw/ofw_bus_subr.h> 47 48 #include "ofw_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 #if defined(__arm__) 58 #if defined(SOC_MV_ARMADAXP) || defined(SOC_MV_ARMADA38X) || \ 59 defined(SOC_MV_DISCOVERY) || defined(SOC_MV_DOVE) || \ 60 defined(SOC_MV_FREY) || defined(SOC_MV_KIRKWOOD) || \ 61 defined(SOC_MV_LOKIPLUS) || defined(SOC_MV_ORION) 62 #define FDT_MARVELL 63 #endif 64 #endif 65 66 static int ofw_fdt_init(ofw_t, void *); 67 static phandle_t ofw_fdt_peer(ofw_t, phandle_t); 68 static phandle_t ofw_fdt_child(ofw_t, phandle_t); 69 static phandle_t ofw_fdt_parent(ofw_t, phandle_t); 70 static phandle_t ofw_fdt_instance_to_package(ofw_t, ihandle_t); 71 static ssize_t ofw_fdt_getproplen(ofw_t, phandle_t, const char *); 72 static ssize_t ofw_fdt_getprop(ofw_t, phandle_t, const char *, void *, size_t); 73 static int ofw_fdt_nextprop(ofw_t, phandle_t, const char *, char *, size_t); 74 static int ofw_fdt_setprop(ofw_t, phandle_t, const char *, const void *, 75 size_t); 76 static ssize_t ofw_fdt_canon(ofw_t, const char *, char *, size_t); 77 static phandle_t ofw_fdt_finddevice(ofw_t, const char *); 78 static ssize_t ofw_fdt_instance_to_path(ofw_t, ihandle_t, char *, size_t); 79 static ssize_t ofw_fdt_package_to_path(ofw_t, phandle_t, char *, size_t); 80 static int ofw_fdt_interpret(ofw_t, const char *, int, cell_t *); 81 82 static ofw_method_t ofw_fdt_methods[] = { 83 OFWMETHOD(ofw_init, ofw_fdt_init), 84 OFWMETHOD(ofw_peer, ofw_fdt_peer), 85 OFWMETHOD(ofw_child, ofw_fdt_child), 86 OFWMETHOD(ofw_parent, ofw_fdt_parent), 87 OFWMETHOD(ofw_instance_to_package, ofw_fdt_instance_to_package), 88 OFWMETHOD(ofw_getproplen, ofw_fdt_getproplen), 89 OFWMETHOD(ofw_getprop, ofw_fdt_getprop), 90 OFWMETHOD(ofw_nextprop, ofw_fdt_nextprop), 91 OFWMETHOD(ofw_setprop, ofw_fdt_setprop), 92 OFWMETHOD(ofw_canon, ofw_fdt_canon), 93 OFWMETHOD(ofw_finddevice, ofw_fdt_finddevice), 94 OFWMETHOD(ofw_instance_to_path, ofw_fdt_instance_to_path), 95 OFWMETHOD(ofw_package_to_path, ofw_fdt_package_to_path), 96 OFWMETHOD(ofw_interpret, ofw_fdt_interpret), 97 { 0, 0 } 98 }; 99 100 static ofw_def_t ofw_fdt = { 101 OFW_FDT, 102 ofw_fdt_methods, 103 0 104 }; 105 OFW_DEF(ofw_fdt); 106 107 #define FDT_FBSDVER_LEN 16 108 #define FDT_MODEL_LEN 80 109 #define FDT_COMPAT_LEN 255 110 111 static void *fdtp = NULL; 112 static char fdt_model[FDT_MODEL_LEN]; 113 static char fdt_compatible[FDT_COMPAT_LEN]; 114 static char fdt_fbsd_version[FDT_FBSDVER_LEN]; 115 116 static int 117 sysctl_handle_dtb(SYSCTL_HANDLER_ARGS) 118 { 119 120 return (sysctl_handle_opaque(oidp, fdtp, fdt_totalsize(fdtp), req)); 121 } 122 123 static void 124 sysctl_register_fdt_oid(void *arg) 125 { 126 127 /* If there is no FDT registered, skip adding the sysctl */ 128 if (fdtp == NULL) 129 return; 130 131 SYSCTL_ADD_PROC(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt), OID_AUTO, "dtb", 132 CTLTYPE_OPAQUE | CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, 0, 133 sysctl_handle_dtb, "", "Device Tree Blob"); 134 if (fdt_model[0] != '\0') 135 SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt), 136 OID_AUTO, "model", CTLFLAG_RD, fdt_model, 137 FDT_MODEL_LEN, "System board model"); 138 if (fdt_compatible[0] != '\0') 139 SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt), 140 OID_AUTO, "compatible", CTLFLAG_RD, fdt_compatible, 141 FDT_COMPAT_LEN, "Compatible platforms"); 142 if (fdt_fbsd_version[0] != '\0') 143 SYSCTL_ADD_STRING(NULL, SYSCTL_STATIC_CHILDREN(_hw_fdt), 144 OID_AUTO, "freebsd-version", CTLFLAG_RD, fdt_fbsd_version, 145 FDT_FBSDVER_LEN, "FreeBSD DTS branding version"); 146 } 147 SYSINIT(dtb_oid, SI_SUB_KMEM, SI_ORDER_ANY, sysctl_register_fdt_oid, NULL); 148 149 static int 150 ofw_fdt_init(ofw_t ofw, void *data) 151 { 152 phandle_t root; 153 ssize_t len; 154 int err; 155 int i; 156 157 /* Check FDT blob integrity */ 158 if ((err = fdt_check_header(data)) != 0) 159 return (err); 160 161 fdtp = data; 162 root = ofw_fdt_finddevice(NULL, "/"); 163 len = ofw_fdt_getproplen(NULL, root, "model"); 164 bzero(fdt_model, FDT_MODEL_LEN); 165 ofw_fdt_getprop(NULL, root, "model", fdt_model, FDT_MODEL_LEN); 166 len = ofw_fdt_getproplen(NULL, root, "compatible"); 167 bzero(fdt_compatible, FDT_COMPAT_LEN); 168 ofw_fdt_getprop(NULL, root, "compatible", fdt_compatible, FDT_COMPAT_LEN); 169 /* Replace the middle '\0' with ' ' */ 170 for (i = 0; i < len - 1; i++) 171 if (fdt_compatible[i] == '\0') 172 fdt_compatible[i] = ' '; 173 if ((len = ofw_fdt_getproplen(NULL, root, "freebsd,dts-version")) > 0) { 174 bzero(fdt_fbsd_version, FDT_FBSDVER_LEN); 175 ofw_fdt_getprop(NULL, root, "freebsd,dts-version", 176 fdt_fbsd_version, FDT_FBSDVER_LEN); 177 } 178 return (0); 179 } 180 181 /* 182 * Device tree functions. 183 * 184 * We use the offset from fdtp to the node as the 'phandle' in OF interface. 185 * 186 * phandle is a u32 value, therefore we cannot use the pointer to node as 187 * phandle in 64 bit. We also do not use the usual fdt offset as phandle, 188 * as it can be 0, and the OF interface has special meaning for phandle 0. 189 */ 190 191 static phandle_t 192 fdt_offset_phandle(int offset) 193 { 194 if (offset < 0) 195 return (0); 196 return ((phandle_t)offset + fdt_off_dt_struct(fdtp)); 197 } 198 199 static int 200 fdt_phandle_offset(phandle_t p) 201 { 202 int pint = (int)p; 203 int dtoff = fdt_off_dt_struct(fdtp); 204 205 if (pint < dtoff) 206 return (-1); 207 return (pint - dtoff); 208 } 209 210 /* Return the next sibling of this node or 0. */ 211 static phandle_t 212 ofw_fdt_peer(ofw_t ofw, phandle_t node) 213 { 214 int offset; 215 216 if (node == 0) { 217 /* Find root node */ 218 offset = fdt_path_offset(fdtp, "/"); 219 220 return (fdt_offset_phandle(offset)); 221 } 222 223 offset = fdt_phandle_offset(node); 224 if (offset < 0) 225 return (0); 226 offset = fdt_next_subnode(fdtp, offset); 227 return (fdt_offset_phandle(offset)); 228 } 229 230 /* Return the first child of this node or 0. */ 231 static phandle_t 232 ofw_fdt_child(ofw_t ofw, phandle_t node) 233 { 234 int offset; 235 236 offset = fdt_phandle_offset(node); 237 if (offset < 0) 238 return (0); 239 offset = fdt_first_subnode(fdtp, offset); 240 return (fdt_offset_phandle(offset)); 241 } 242 243 /* Return the parent of this node or 0. */ 244 static phandle_t 245 ofw_fdt_parent(ofw_t ofw, phandle_t node) 246 { 247 int offset, paroffset; 248 249 offset = fdt_phandle_offset(node); 250 if (offset < 0) 251 return (0); 252 253 paroffset = fdt_parent_offset(fdtp, offset); 254 return (fdt_offset_phandle(paroffset)); 255 } 256 257 /* Return the package handle that corresponds to an instance handle. */ 258 static phandle_t 259 ofw_fdt_instance_to_package(ofw_t ofw, ihandle_t instance) 260 { 261 262 /* Where real OF uses ihandles in the tree, FDT uses xref phandles */ 263 return (OF_node_from_xref(instance)); 264 } 265 266 /* Get the length of a property of a package. */ 267 static ssize_t 268 ofw_fdt_getproplen(ofw_t ofw, phandle_t package, const char *propname) 269 { 270 const void *prop; 271 int offset, len; 272 273 offset = fdt_phandle_offset(package); 274 if (offset < 0) 275 return (-1); 276 277 len = -1; 278 prop = fdt_getprop(fdtp, offset, propname, &len); 279 280 if (prop == NULL && strcmp(propname, "name") == 0) { 281 /* Emulate the 'name' property */ 282 fdt_get_name(fdtp, offset, &len); 283 return (len + 1); 284 } 285 286 if (prop == NULL && offset == fdt_path_offset(fdtp, "/chosen")) { 287 if (strcmp(propname, "fdtbootcpu") == 0) 288 return (sizeof(cell_t)); 289 if (strcmp(propname, "fdtmemreserv") == 0) 290 return (sizeof(uint64_t)*2*fdt_num_mem_rsv(fdtp)); 291 } 292 293 if (prop == NULL) 294 return (-1); 295 296 return (len); 297 } 298 299 /* Get the value of a property of a package. */ 300 static ssize_t 301 ofw_fdt_getprop(ofw_t ofw, phandle_t package, const char *propname, void *buf, 302 size_t buflen) 303 { 304 const void *prop; 305 const char *name; 306 int len, offset; 307 uint32_t cpuid; 308 309 offset = fdt_phandle_offset(package); 310 if (offset < 0) 311 return (-1); 312 313 prop = fdt_getprop(fdtp, offset, propname, &len); 314 315 if (prop == NULL && strcmp(propname, "name") == 0) { 316 /* Emulate the 'name' property */ 317 name = fdt_get_name(fdtp, offset, &len); 318 strncpy(buf, name, buflen); 319 return (len + 1); 320 } 321 322 if (prop == NULL && offset == fdt_path_offset(fdtp, "/chosen")) { 323 if (strcmp(propname, "fdtbootcpu") == 0) { 324 cpuid = cpu_to_fdt32(fdt_boot_cpuid_phys(fdtp)); 325 len = sizeof(cpuid); 326 prop = &cpuid; 327 } 328 if (strcmp(propname, "fdtmemreserv") == 0) { 329 prop = (char *)fdtp + fdt_off_mem_rsvmap(fdtp); 330 len = sizeof(uint64_t)*2*fdt_num_mem_rsv(fdtp); 331 } 332 } 333 334 if (prop == NULL) 335 return (-1); 336 337 bcopy(prop, buf, min(len, buflen)); 338 339 return (len); 340 } 341 342 /* 343 * Get the next property of a package. Return values: 344 * -1: package or previous property does not exist 345 * 0: no more properties 346 * 1: success 347 */ 348 static int 349 ofw_fdt_nextprop(ofw_t ofw, phandle_t package, const char *previous, char *buf, 350 size_t size) 351 { 352 const void *prop; 353 const char *name; 354 int offset; 355 356 offset = fdt_phandle_offset(package); 357 if (offset < 0) 358 return (-1); 359 360 if (previous == NULL) 361 /* Find the first prop in the node */ 362 offset = fdt_first_property_offset(fdtp, offset); 363 else { 364 fdt_for_each_property_offset(offset, fdtp, offset) { 365 prop = fdt_getprop_by_offset(fdtp, offset, &name, NULL); 366 if (prop == NULL) 367 return (-1); /* Internal error */ 368 /* Skip until we find 'previous', then bail out */ 369 if (strcmp(name, previous) != 0) 370 continue; 371 offset = fdt_next_property_offset(fdtp, offset); 372 break; 373 } 374 } 375 376 if (offset < 0) 377 return (0); /* No properties */ 378 379 prop = fdt_getprop_by_offset(fdtp, offset, &name, &offset); 380 if (prop == NULL) 381 return (-1); /* Internal error */ 382 383 strncpy(buf, name, size); 384 385 return (1); 386 } 387 388 /* Set the value of a property of a package. */ 389 static int 390 ofw_fdt_setprop(ofw_t ofw, phandle_t package, const char *propname, 391 const void *buf, size_t len) 392 { 393 int offset; 394 395 offset = fdt_phandle_offset(package); 396 if (offset < 0) 397 return (-1); 398 399 if (fdt_setprop_inplace(fdtp, offset, propname, buf, len) != 0) 400 /* Try to add property, when setting value inplace failed */ 401 return (fdt_setprop(fdtp, offset, propname, buf, len)); 402 403 return (0); 404 } 405 406 /* Convert a device specifier to a fully qualified pathname. */ 407 static ssize_t 408 ofw_fdt_canon(ofw_t ofw, const char *device, char *buf, size_t len) 409 { 410 411 return (-1); 412 } 413 414 /* Return a package handle for the specified device. */ 415 static phandle_t 416 ofw_fdt_finddevice(ofw_t ofw, const char *device) 417 { 418 int offset; 419 420 offset = fdt_path_offset(fdtp, device); 421 if (offset < 0) 422 return (-1); 423 return (fdt_offset_phandle(offset)); 424 } 425 426 /* Return the fully qualified pathname corresponding to an instance. */ 427 static ssize_t 428 ofw_fdt_instance_to_path(ofw_t ofw, ihandle_t instance, char *buf, size_t len) 429 { 430 phandle_t phandle; 431 432 phandle = OF_instance_to_package(instance); 433 if (phandle == -1) 434 return (-1); 435 436 return (OF_package_to_path(phandle, buf, len)); 437 } 438 439 /* Return the fully qualified pathname corresponding to a package. */ 440 static ssize_t 441 ofw_fdt_package_to_path(ofw_t ofw, phandle_t package, char *buf, size_t len) 442 { 443 444 return (-1); 445 } 446 447 #if defined(FDT_MARVELL) 448 static int 449 ofw_fdt_fixup(ofw_t ofw) 450 { 451 char model[FDT_MODEL_LEN]; 452 phandle_t root; 453 ssize_t len; 454 int i; 455 456 if ((root = ofw_fdt_finddevice(ofw, "/")) == -1) 457 return (ENODEV); 458 459 if ((len = ofw_fdt_getproplen(ofw, root, "model")) <= 0) 460 return (0); 461 462 bzero(model, FDT_MODEL_LEN); 463 if (ofw_fdt_getprop(ofw, root, "model", model, FDT_MODEL_LEN) <= 0) 464 return (0); 465 466 /* 467 * Search fixup table and call handler if appropriate. 468 */ 469 for (i = 0; fdt_fixup_table[i].model != NULL; i++) { 470 if (strncmp(model, fdt_fixup_table[i].model, 471 FDT_MODEL_LEN) != 0) 472 /* 473 * Sometimes it's convenient to provide one 474 * fixup entry that refers to many boards. 475 * To handle this case, simply check if model 476 * is compatible parameter 477 */ 478 if(!ofw_bus_node_is_compatible(root, 479 fdt_fixup_table[i].model)) 480 continue; 481 482 if (fdt_fixup_table[i].handler != NULL) 483 (*fdt_fixup_table[i].handler)(root); 484 } 485 486 return (0); 487 } 488 #endif 489 490 static int 491 ofw_fdt_interpret(ofw_t ofw, const char *cmd, int nret, cell_t *retvals) 492 { 493 #if defined(FDT_MARVELL) 494 int rv; 495 496 /* 497 * Note: FDT does not have the possibility to 'interpret' commands, 498 * but we abuse the interface a bit to use it for doing non-standard 499 * operations on the device tree blob. 500 * 501 * Currently the only supported 'command' is to trigger performing 502 * fixups. 503 */ 504 if (strncmp("perform-fixup", cmd, 13) != 0) 505 return (0); 506 507 rv = ofw_fdt_fixup(ofw); 508 if (nret > 0) 509 retvals[0] = rv; 510 511 return (rv); 512 #else 513 return (0); 514 #endif 515 } 516