You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fdt.c 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  1. /*
  2. * Copyright (C) 2019 Michael Brown <mbrown@fensystems.co.uk>.
  3. *
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License as
  6. * published by the Free Software Foundation; either version 2 of the
  7. * License, or (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  17. * 02110-1301, USA.
  18. *
  19. * You can also choose to distribute this program under the terms of
  20. * the Unmodified Binary Distribution Licence (as given in the file
  21. * COPYING.UBDL), provided that you have satisfied its requirements.
  22. */
  23. FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  24. #include <string.h>
  25. #include <errno.h>
  26. #include <assert.h>
  27. #include <byteswap.h>
  28. #include <ipxe/netdevice.h>
  29. #include <ipxe/fdt.h>
  30. /** @file
  31. *
  32. * Flattened Device Tree
  33. *
  34. */
  35. /** The system flattened device tree (if present) */
  36. static struct fdt fdt;
  37. /** A position within a device tree */
  38. struct fdt_cursor {
  39. /** Offset within structure block */
  40. unsigned int offset;
  41. /** Tree depth */
  42. int depth;
  43. };
  44. /** A lexical descriptor */
  45. struct fdt_descriptor {
  46. /** Node or property name (if applicable) */
  47. const char *name;
  48. /** Property data (if applicable) */
  49. const void *data;
  50. /** Length of property data (if applicable) */
  51. size_t len;
  52. };
  53. /**
  54. * Check if device tree exists
  55. *
  56. * @v has_fdt Device tree exists
  57. */
  58. static inline __attribute__ (( always_inline )) int fdt_exists ( void ) {
  59. return ( fdt.hdr != NULL );
  60. }
  61. /**
  62. * Traverse device tree
  63. *
  64. * @v pos Position within device tree
  65. * @v desc Lexical descriptor to fill in
  66. * @ret rc Return status code
  67. */
  68. static int fdt_traverse ( struct fdt_cursor *pos,
  69. struct fdt_descriptor *desc ) {
  70. const fdt_token_t *token;
  71. const void *data;
  72. const struct fdt_prop *prop;
  73. unsigned int name_off;
  74. size_t remaining;
  75. size_t len;
  76. /* Sanity checks */
  77. assert ( pos->offset < fdt.len );
  78. assert ( ( pos->offset & ( FDT_STRUCTURE_ALIGN - 1 ) ) == 0 );
  79. /* Clear descriptor */
  80. memset ( desc, 0, sizeof ( *desc ) );
  81. /* Locate token and calculate remaining space */
  82. token = ( fdt.raw + fdt.structure + pos->offset );
  83. remaining = ( fdt.len - pos->offset );
  84. if ( remaining < sizeof ( *token ) ) {
  85. DBGC ( &fdt, "FDT truncated tree at +%#04x\n", pos->offset );
  86. return -EINVAL;
  87. }
  88. remaining -= sizeof ( *token );
  89. data = ( ( ( const void * ) token ) + sizeof ( *token ) );
  90. len = 0;
  91. /* Handle token */
  92. switch ( *token ) {
  93. case cpu_to_be32 ( FDT_BEGIN_NODE ):
  94. /* Start of node */
  95. desc->name = data;
  96. len = ( strnlen ( desc->name, remaining ) + 1 /* NUL */ );
  97. if ( remaining < len ) {
  98. DBGC ( &fdt, "FDT unterminated node name at +%#04x\n",
  99. pos->offset );
  100. return -EINVAL;
  101. }
  102. pos->depth++;
  103. break;
  104. case cpu_to_be32 ( FDT_END_NODE ):
  105. /* End of node */
  106. if ( pos->depth < 0 ) {
  107. DBGC ( &fdt, "FDT spurious node end at +%#04x\n",
  108. pos->offset );
  109. return -EINVAL;
  110. }
  111. pos->depth--;
  112. if ( pos->depth < 0 ) {
  113. /* End of (sub)tree */
  114. return -ENOENT;
  115. }
  116. break;
  117. case cpu_to_be32 ( FDT_PROP ):
  118. /* Property */
  119. prop = data;
  120. if ( remaining < sizeof ( *prop ) ) {
  121. DBGC ( &fdt, "FDT truncated property at +%#04x\n",
  122. pos->offset );
  123. return -EINVAL;
  124. }
  125. desc->data = ( ( ( const void * ) prop ) + sizeof ( *prop ) );
  126. desc->len = be32_to_cpu ( prop->len );
  127. len = ( sizeof ( *prop ) + desc->len );
  128. if ( remaining < len ) {
  129. DBGC ( &fdt, "FDT overlength property at +%#04x\n",
  130. pos->offset );
  131. return -EINVAL;
  132. }
  133. name_off = be32_to_cpu ( prop->name_off );
  134. if ( name_off > fdt.strings_len ) {
  135. DBGC ( &fdt, "FDT property name outside strings "
  136. "block at +%#04x\n", pos->offset );
  137. return -EINVAL;
  138. }
  139. desc->name = ( fdt.raw + fdt.strings + name_off );
  140. break;
  141. case cpu_to_be32 ( FDT_NOP ):
  142. /* Do nothing */
  143. break;
  144. default:
  145. /* Unrecognised or unexpected token */
  146. DBGC ( &fdt, "FDT unexpected token %#08x at +%#04x\n",
  147. be32_to_cpu ( *token ), pos->offset );
  148. return -EINVAL;
  149. }
  150. /* Update cursor */
  151. len = ( ( len + FDT_STRUCTURE_ALIGN - 1 ) &
  152. ~( FDT_STRUCTURE_ALIGN - 1 ) );
  153. pos->offset += ( sizeof ( *token ) + len );
  154. /* Sanity checks */
  155. assert ( pos->offset <= fdt.len );
  156. return 0;
  157. }
  158. /**
  159. * Find child node
  160. *
  161. * @v offset Starting node offset
  162. * @v name Node name
  163. * @v child Child node offset to fill in
  164. * @ret rc Return status code
  165. */
  166. static int fdt_child ( unsigned int offset, const char *name,
  167. unsigned int *child ) {
  168. struct fdt_cursor pos;
  169. struct fdt_descriptor desc;
  170. unsigned int orig_offset;
  171. int rc;
  172. /* Record original offset (for debugging) */
  173. orig_offset = offset;
  174. /* Initialise cursor */
  175. pos.offset = offset;
  176. pos.depth = -1;
  177. /* Find child node */
  178. while ( 1 ) {
  179. /* Record current offset */
  180. *child = pos.offset;
  181. /* Traverse tree */
  182. if ( ( rc = fdt_traverse ( &pos, &desc ) ) != 0 ) {
  183. DBGC ( &fdt, "FDT +%#04x has no child node \"%s\": "
  184. "%s\n", orig_offset, name, strerror ( rc ) );
  185. return rc;
  186. }
  187. /* Check for matching immediate child node */
  188. if ( ( pos.depth == 1 ) && desc.name && ( ! desc.data ) ) {
  189. DBGC2 ( &fdt, "FDT +%#04x has child node \"%s\"\n",
  190. orig_offset, desc.name );
  191. if ( strcmp ( name, desc.name ) == 0 ) {
  192. DBGC2 ( &fdt, "FDT +%#04x found child node "
  193. "\"%s\" at +%#04x\n", orig_offset,
  194. desc.name, *child );
  195. return 0;
  196. }
  197. }
  198. }
  199. }
  200. /**
  201. * Find node by path
  202. *
  203. * @v path Node path
  204. * @v offset Offset to fill in
  205. * @ret rc Return status code
  206. */
  207. int fdt_path ( const char *path, unsigned int *offset ) {
  208. char *tmp = ( ( char * ) path );
  209. char *del;
  210. int rc;
  211. /* Initialise offset */
  212. *offset = 0;
  213. /* Traverse tree one path segment at a time */
  214. while ( *tmp ) {
  215. /* Skip any leading '/' */
  216. while ( *tmp == '/' )
  217. tmp++;
  218. /* Find next '/' delimiter and convert to NUL */
  219. del = strchr ( tmp, '/' );
  220. if ( del )
  221. *del = '\0';
  222. /* Find child and restore delimiter */
  223. rc = fdt_child ( *offset, tmp, offset );
  224. if ( del )
  225. *del = '/';
  226. if ( rc != 0 )
  227. return rc;
  228. /* Move to next path component, if any */
  229. while ( *tmp && ( *tmp != '/' ) )
  230. tmp++;
  231. }
  232. DBGC2 ( &fdt, "FDT found path \"%s\" at +%#04x\n", path, *offset );
  233. return 0;
  234. }
  235. /**
  236. * Find node by alias
  237. *
  238. * @v name Alias name
  239. * @v offset Offset to fill in
  240. * @ret rc Return status code
  241. */
  242. int fdt_alias ( const char *name, unsigned int *offset ) {
  243. const char *alias;
  244. int rc;
  245. /* Locate "/aliases" node */
  246. if ( ( rc = fdt_child ( 0, "aliases", offset ) ) != 0 )
  247. return rc;
  248. /* Locate alias property */
  249. if ( ( alias = fdt_string ( *offset, name ) ) == NULL )
  250. return -ENOENT;
  251. DBGC ( &fdt, "FDT alias \"%s\" is \"%s\"\n", name, alias );
  252. /* Locate aliased node */
  253. if ( ( rc = fdt_path ( alias, offset ) ) != 0 )
  254. return rc;
  255. return 0;
  256. }
  257. /**
  258. * Find property
  259. *
  260. * @v offset Starting node offset
  261. * @v name Property name
  262. * @v desc Lexical descriptor to fill in
  263. * @ret rc Return status code
  264. */
  265. static int fdt_property ( unsigned int offset, const char *name,
  266. struct fdt_descriptor *desc ) {
  267. struct fdt_cursor pos;
  268. int rc;
  269. /* Initialise cursor */
  270. pos.offset = offset;
  271. pos.depth = -1;
  272. /* Find property */
  273. while ( 1 ) {
  274. /* Traverse tree */
  275. if ( ( rc = fdt_traverse ( &pos, desc ) ) != 0 ) {
  276. DBGC ( &fdt, "FDT +%#04x has no property \"%s\": %s\n",
  277. offset, name, strerror ( rc ) );
  278. return rc;
  279. }
  280. /* Check for matching immediate child property */
  281. if ( ( pos.depth == 0 ) && desc->data ) {
  282. DBGC2 ( &fdt, "FDT +%#04x has property \"%s\" len "
  283. "%#zx\n", offset, desc->name, desc->len );
  284. if ( strcmp ( name, desc->name ) == 0 ) {
  285. DBGC2 ( &fdt, "FDT +%#04x found property "
  286. "\"%s\"\n", offset, desc->name );
  287. DBGC2_HDA ( &fdt, 0, desc->data, desc->len );
  288. return 0;
  289. }
  290. }
  291. }
  292. }
  293. /**
  294. * Find string property
  295. *
  296. * @v offset Starting node offset
  297. * @v name Property name
  298. * @ret string String property, or NULL on error
  299. */
  300. const char * fdt_string ( unsigned int offset, const char *name ) {
  301. struct fdt_descriptor desc;
  302. int rc;
  303. /* Find property */
  304. if ( ( rc = fdt_property ( offset, name, &desc ) ) != 0 )
  305. return NULL;
  306. /* Check NUL termination */
  307. if ( strnlen ( desc.data, desc.len ) == desc.len ) {
  308. DBGC ( &fdt, "FDT unterminated string property \"%s\"\n",
  309. name );
  310. return NULL;
  311. }
  312. return desc.data;
  313. }
  314. /**
  315. * Get MAC address from property
  316. *
  317. * @v offset Starting node offset
  318. * @v netdev Network device
  319. * @ret rc Return status code
  320. */
  321. int fdt_mac ( unsigned int offset, struct net_device *netdev ) {
  322. struct fdt_descriptor desc;
  323. size_t len;
  324. int rc;
  325. /* Find applicable MAC address property */
  326. if ( ( ( rc = fdt_property ( offset, "mac-address", &desc ) ) != 0 ) &&
  327. ( ( rc = fdt_property ( offset, "local-mac-address",
  328. &desc ) ) != 0 ) ) {
  329. return rc;
  330. }
  331. /* Check length */
  332. len = netdev->ll_protocol->hw_addr_len;
  333. if ( len != desc.len ) {
  334. DBGC ( &fdt, "FDT malformed MAC address \"%s\":\n",
  335. desc.name );
  336. DBGC_HDA ( &fdt, 0, desc.data, desc.len );
  337. return -ERANGE;
  338. }
  339. /* Fill in MAC address */
  340. memcpy ( netdev->hw_addr, desc.data, len );
  341. return 0;
  342. }
  343. /**
  344. * Register device tree
  345. *
  346. * @v fdt Device tree header
  347. * @ret rc Return status code
  348. */
  349. int register_fdt ( const struct fdt_header *hdr ) {
  350. const uint8_t *end;
  351. /* Record device tree location */
  352. fdt.hdr = hdr;
  353. fdt.len = be32_to_cpu ( hdr->totalsize );
  354. DBGC ( &fdt, "FDT version %d at %p+%#04zx\n",
  355. be32_to_cpu ( hdr->version ), fdt.hdr, fdt.len );
  356. /* Check signature */
  357. if ( hdr->magic != cpu_to_be32 ( FDT_MAGIC ) ) {
  358. DBGC ( &fdt, "FDT has invalid magic value %#08x\n",
  359. be32_to_cpu ( hdr->magic ) );
  360. goto err;
  361. }
  362. /* Check version */
  363. if ( hdr->last_comp_version != cpu_to_be32 ( FDT_VERSION ) ) {
  364. DBGC ( &fdt, "FDT unsupported version %d\n",
  365. be32_to_cpu ( hdr->last_comp_version ) );
  366. goto err;
  367. }
  368. /* Record structure block location */
  369. fdt.structure = be32_to_cpu ( hdr->off_dt_struct );
  370. fdt.structure_len = be32_to_cpu ( hdr->size_dt_struct );
  371. DBGC ( &fdt, "FDT structure block at +[%#04x,%#04zx)\n",
  372. fdt.structure, ( fdt.structure + fdt.structure_len ) );
  373. if ( ( fdt.structure > fdt.len ) ||
  374. ( fdt.structure_len > ( fdt.len - fdt.structure ) ) ) {
  375. DBGC ( &fdt, "FDT structure block exceeds table\n" );
  376. goto err;
  377. }
  378. if ( ( fdt.structure | fdt.structure_len ) &
  379. ( FDT_STRUCTURE_ALIGN - 1 ) ) {
  380. DBGC ( &fdt, "FDT structure block is misaligned\n" );
  381. goto err;
  382. }
  383. /* Record strings block location */
  384. fdt.strings = be32_to_cpu ( hdr->off_dt_strings );
  385. fdt.strings_len = be32_to_cpu ( hdr->size_dt_strings );
  386. DBGC ( &fdt, "FDT strings block at +[%#04x,%#04zx)\n",
  387. fdt.strings, ( fdt.strings + fdt.strings_len ) );
  388. if ( ( fdt.strings > fdt.len ) ||
  389. ( fdt.strings_len > ( fdt.len - fdt.strings ) ) ) {
  390. DBGC ( &fdt, "FDT strings block exceeds table\n" );
  391. goto err;
  392. }
  393. /* Shrink strings block to ensure NUL termination safety */
  394. end = ( fdt.raw + fdt.strings + fdt.strings_len );
  395. for ( ; fdt.strings_len ; fdt.strings_len-- ) {
  396. if ( *(--end) == '\0' )
  397. break;
  398. }
  399. if ( fdt.strings_len != be32_to_cpu ( hdr->size_dt_strings ) ) {
  400. DBGC ( &fdt, "FDT strings block shrunk to +[%#04x,%#04zx)\n",
  401. fdt.strings, ( fdt.strings + fdt.strings_len ) );
  402. }
  403. /* Print model name (for debugging) */
  404. DBGC ( &fdt, "FDT model is \"%s\"\n", fdt_string ( 0, "model" ) );
  405. return 0;
  406. err:
  407. DBGC_HDA ( &fdt, 0, hdr, sizeof ( *hdr ) );
  408. fdt.hdr = NULL;
  409. return -EINVAL;
  410. }
  411. /* Drag in objects via register_fdt */
  412. REQUIRING_SYMBOL ( register_fdt );
  413. /* Drag in device tree configuration */
  414. REQUIRE_OBJECT ( config_fdt );