[fdt] Add ability to parse a MAC address from a flattened device tree
[ipxe.git] / src / core / fdt.c
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
24 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
25
26 #include <string.h>
27 #include <errno.h>
28 #include <assert.h>
29 #include <byteswap.h>
30 #include <ipxe/netdevice.h>
31 #include <ipxe/fdt.h>
32
33 /** @file
34 *
35 * Flattened Device Tree
36 *
37 */
38
39 /** The system flattened device tree (if present) */
40 static struct fdt fdt;
41
42 /** A position within a device tree */
43 struct fdt_cursor {
44 /** Offset within structure block */
45 unsigned int offset;
46 /** Tree depth */
47 int depth;
48 };
49
50 /** A lexical descriptor */
51 struct fdt_descriptor {
52 /** Node or property name (if applicable) */
53 const char *name;
54 /** Property data (if applicable) */
55 const void *data;
56 /** Length of property data (if applicable) */
57 size_t len;
58 };
59
60 /**
61 * Check if device tree exists
62 *
63 * @v has_fdt Device tree exists
64 */
65 static inline __attribute__ (( always_inline )) int fdt_exists ( void ) {
66
67 return ( fdt.hdr != NULL );
68 }
69
70 /**
71 * Traverse device tree
72 *
73 * @v pos Position within device tree
74 * @v desc Lexical descriptor to fill in
75 * @ret rc Return status code
76 */
77 static int fdt_traverse ( struct fdt_cursor *pos,
78 struct fdt_descriptor *desc ) {
79 const fdt_token_t *token;
80 const void *data;
81 const struct fdt_prop *prop;
82 unsigned int name_off;
83 size_t remaining;
84 size_t len;
85
86 /* Sanity checks */
87 assert ( pos->offset < fdt.len );
88 assert ( ( pos->offset & ( FDT_STRUCTURE_ALIGN - 1 ) ) == 0 );
89
90 /* Clear descriptor */
91 memset ( desc, 0, sizeof ( *desc ) );
92
93 /* Locate token and calculate remaining space */
94 token = ( fdt.raw + fdt.structure + pos->offset );
95 remaining = ( fdt.len - pos->offset );
96 if ( remaining < sizeof ( *token ) ) {
97 DBGC ( &fdt, "FDT truncated tree at +%#04x\n", pos->offset );
98 return -EINVAL;
99 }
100 remaining -= sizeof ( *token );
101 data = ( ( ( const void * ) token ) + sizeof ( *token ) );
102 len = 0;
103
104 /* Handle token */
105 switch ( *token ) {
106
107 case cpu_to_be32 ( FDT_BEGIN_NODE ):
108
109 /* Start of node */
110 desc->name = data;
111 len = ( strnlen ( desc->name, remaining ) + 1 /* NUL */ );
112 if ( remaining < len ) {
113 DBGC ( &fdt, "FDT unterminated node name at +%#04x\n",
114 pos->offset );
115 return -EINVAL;
116 }
117 pos->depth++;
118 break;
119
120 case cpu_to_be32 ( FDT_END_NODE ):
121
122 /* End of node */
123 if ( pos->depth < 0 ) {
124 DBGC ( &fdt, "FDT spurious node end at +%#04x\n",
125 pos->offset );
126 return -EINVAL;
127 }
128 pos->depth--;
129 if ( pos->depth < 0 ) {
130 /* End of (sub)tree */
131 return -ENOENT;
132 }
133 break;
134
135 case cpu_to_be32 ( FDT_PROP ):
136
137 /* Property */
138 prop = data;
139 if ( remaining < sizeof ( *prop ) ) {
140 DBGC ( &fdt, "FDT truncated property at +%#04x\n",
141 pos->offset );
142 return -EINVAL;
143 }
144 desc->data = ( ( ( const void * ) prop ) + sizeof ( *prop ) );
145 desc->len = be32_to_cpu ( prop->len );
146 len = ( sizeof ( *prop ) + desc->len );
147 if ( remaining < len ) {
148 DBGC ( &fdt, "FDT overlength property at +%#04x\n",
149 pos->offset );
150 return -EINVAL;
151 }
152 name_off = be32_to_cpu ( prop->name_off );
153 if ( name_off > fdt.strings_len ) {
154 DBGC ( &fdt, "FDT property name outside strings "
155 "block at +%#04x\n", pos->offset );
156 return -EINVAL;
157 }
158 desc->name = ( fdt.raw + fdt.strings + name_off );
159 break;
160
161 case cpu_to_be32 ( FDT_NOP ):
162
163 /* Do nothing */
164 break;
165
166 default:
167
168 /* Unrecognised or unexpected token */
169 DBGC ( &fdt, "FDT unexpected token %#08x at +%#04x\n",
170 be32_to_cpu ( *token ), pos->offset );
171 return -EINVAL;
172 }
173
174 /* Update cursor */
175 len = ( ( len + FDT_STRUCTURE_ALIGN - 1 ) &
176 ~( FDT_STRUCTURE_ALIGN - 1 ) );
177 pos->offset += ( sizeof ( *token ) + len );
178
179 /* Sanity checks */
180 assert ( pos->offset <= fdt.len );
181
182 return 0;
183 }
184
185 /**
186 * Find child node
187 *
188 * @v offset Starting node offset
189 * @v name Node name
190 * @v child Child node offset to fill in
191 * @ret rc Return status code
192 */
193 static int fdt_child ( unsigned int offset, const char *name,
194 unsigned int *child ) {
195 struct fdt_cursor pos;
196 struct fdt_descriptor desc;
197 unsigned int orig_offset;
198 int rc;
199
200 /* Record original offset (for debugging) */
201 orig_offset = offset;
202
203 /* Initialise cursor */
204 pos.offset = offset;
205 pos.depth = -1;
206
207 /* Find child node */
208 while ( 1 ) {
209
210 /* Record current offset */
211 *child = pos.offset;
212
213 /* Traverse tree */
214 if ( ( rc = fdt_traverse ( &pos, &desc ) ) != 0 ) {
215 DBGC ( &fdt, "FDT +%#04x has no child node \"%s\": "
216 "%s\n", orig_offset, name, strerror ( rc ) );
217 return rc;
218 }
219
220 /* Check for matching immediate child node */
221 if ( ( pos.depth == 1 ) && desc.name && ( ! desc.data ) ) {
222 DBGC2 ( &fdt, "FDT +%#04x has child node \"%s\"\n",
223 orig_offset, desc.name );
224 if ( strcmp ( name, desc.name ) == 0 ) {
225 DBGC2 ( &fdt, "FDT +%#04x found child node "
226 "\"%s\" at +%#04x\n", orig_offset,
227 desc.name, *child );
228 return 0;
229 }
230 }
231 }
232 }
233
234 /**
235 * Find node by path
236 *
237 * @v path Node path
238 * @v offset Offset to fill in
239 * @ret rc Return status code
240 */
241 int fdt_path ( const char *path, unsigned int *offset ) {
242 char *tmp = ( ( char * ) path );
243 char *del;
244 int rc;
245
246 /* Initialise offset */
247 *offset = 0;
248
249 /* Traverse tree one path segment at a time */
250 while ( *tmp ) {
251
252 /* Skip any leading '/' */
253 while ( *tmp == '/' )
254 tmp++;
255
256 /* Find next '/' delimiter and convert to NUL */
257 del = strchr ( tmp, '/' );
258 if ( del )
259 *del = '\0';
260
261 /* Find child and restore delimiter */
262 rc = fdt_child ( *offset, tmp, offset );
263 if ( del )
264 *del = '/';
265 if ( rc != 0 )
266 return rc;
267
268 /* Move to next path component, if any */
269 while ( *tmp && ( *tmp != '/' ) )
270 tmp++;
271 }
272
273 DBGC2 ( &fdt, "FDT found path \"%s\" at +%#04x\n", path, *offset );
274 return 0;
275 }
276
277 /**
278 * Find node by alias
279 *
280 * @v name Alias name
281 * @v offset Offset to fill in
282 * @ret rc Return status code
283 */
284 int fdt_alias ( const char *name, unsigned int *offset ) {
285 const char *alias;
286 int rc;
287
288 /* Locate "/aliases" node */
289 if ( ( rc = fdt_child ( 0, "aliases", offset ) ) != 0 )
290 return rc;
291
292 /* Locate alias property */
293 if ( ( alias = fdt_string ( *offset, name ) ) == NULL )
294 return -ENOENT;
295 DBGC ( &fdt, "FDT alias \"%s\" is \"%s\"\n", name, alias );
296
297 /* Locate aliased node */
298 if ( ( rc = fdt_path ( alias, offset ) ) != 0 )
299 return rc;
300
301 return 0;
302 }
303
304 /**
305 * Find property
306 *
307 * @v offset Starting node offset
308 * @v name Property name
309 * @v desc Lexical descriptor to fill in
310 * @ret rc Return status code
311 */
312 static int fdt_property ( unsigned int offset, const char *name,
313 struct fdt_descriptor *desc ) {
314 struct fdt_cursor pos;
315 int rc;
316
317 /* Initialise cursor */
318 pos.offset = offset;
319 pos.depth = -1;
320
321 /* Find property */
322 while ( 1 ) {
323
324 /* Traverse tree */
325 if ( ( rc = fdt_traverse ( &pos, desc ) ) != 0 ) {
326 DBGC ( &fdt, "FDT +%#04x has no property \"%s\": %s\n",
327 offset, name, strerror ( rc ) );
328 return rc;
329 }
330
331 /* Check for matching immediate child property */
332 if ( ( pos.depth == 0 ) && desc->data ) {
333 DBGC2 ( &fdt, "FDT +%#04x has property \"%s\" len "
334 "%#zx\n", offset, desc->name, desc->len );
335 if ( strcmp ( name, desc->name ) == 0 ) {
336 DBGC2 ( &fdt, "FDT +%#04x found property "
337 "\"%s\"\n", offset, desc->name );
338 DBGC2_HDA ( &fdt, 0, desc->data, desc->len );
339 return 0;
340 }
341 }
342 }
343 }
344
345 /**
346 * Find string property
347 *
348 * @v offset Starting node offset
349 * @v name Property name
350 * @ret string String property, or NULL on error
351 */
352 const char * fdt_string ( unsigned int offset, const char *name ) {
353 struct fdt_descriptor desc;
354 int rc;
355
356 /* Find property */
357 if ( ( rc = fdt_property ( offset, name, &desc ) ) != 0 )
358 return NULL;
359
360 /* Check NUL termination */
361 if ( strnlen ( desc.data, desc.len ) == desc.len ) {
362 DBGC ( &fdt, "FDT unterminated string property \"%s\"\n",
363 name );
364 return NULL;
365 }
366
367 return desc.data;
368 }
369
370 /**
371 * Get MAC address from property
372 *
373 * @v offset Starting node offset
374 * @v netdev Network device
375 * @ret rc Return status code
376 */
377 int fdt_mac ( unsigned int offset, struct net_device *netdev ) {
378 struct fdt_descriptor desc;
379 size_t len;
380 int rc;
381
382 /* Find applicable MAC address property */
383 if ( ( ( rc = fdt_property ( offset, "mac-address", &desc ) ) != 0 ) &&
384 ( ( rc = fdt_property ( offset, "local-mac-address",
385 &desc ) ) != 0 ) ) {
386 return rc;
387 }
388
389 /* Check length */
390 len = netdev->ll_protocol->hw_addr_len;
391 if ( len != desc.len ) {
392 DBGC ( &fdt, "FDT malformed MAC address \"%s\":\n",
393 desc.name );
394 DBGC_HDA ( &fdt, 0, desc.data, desc.len );
395 return -ERANGE;
396 }
397
398 /* Fill in MAC address */
399 memcpy ( netdev->hw_addr, desc.data, len );
400
401 return 0;
402 }
403
404 /**
405 * Register device tree
406 *
407 * @v fdt Device tree header
408 * @ret rc Return status code
409 */
410 int register_fdt ( const struct fdt_header *hdr ) {
411 const uint8_t *end;
412
413 /* Record device tree location */
414 fdt.hdr = hdr;
415 fdt.len = be32_to_cpu ( hdr->totalsize );
416 DBGC ( &fdt, "FDT version %d at %p+%#04zx\n",
417 be32_to_cpu ( hdr->version ), fdt.hdr, fdt.len );
418
419 /* Check signature */
420 if ( hdr->magic != cpu_to_be32 ( FDT_MAGIC ) ) {
421 DBGC ( &fdt, "FDT has invalid magic value %#08x\n",
422 be32_to_cpu ( hdr->magic ) );
423 goto err;
424 }
425
426 /* Check version */
427 if ( hdr->last_comp_version != cpu_to_be32 ( FDT_VERSION ) ) {
428 DBGC ( &fdt, "FDT unsupported version %d\n",
429 be32_to_cpu ( hdr->last_comp_version ) );
430 goto err;
431 }
432
433 /* Record structure block location */
434 fdt.structure = be32_to_cpu ( hdr->off_dt_struct );
435 fdt.structure_len = be32_to_cpu ( hdr->size_dt_struct );
436 DBGC ( &fdt, "FDT structure block at +[%#04x,%#04zx)\n",
437 fdt.structure, ( fdt.structure + fdt.structure_len ) );
438 if ( ( fdt.structure > fdt.len ) ||
439 ( fdt.structure_len > ( fdt.len - fdt.structure ) ) ) {
440 DBGC ( &fdt, "FDT structure block exceeds table\n" );
441 goto err;
442 }
443 if ( ( fdt.structure | fdt.structure_len ) &
444 ( FDT_STRUCTURE_ALIGN - 1 ) ) {
445 DBGC ( &fdt, "FDT structure block is misaligned\n" );
446 goto err;
447 }
448
449 /* Record strings block location */
450 fdt.strings = be32_to_cpu ( hdr->off_dt_strings );
451 fdt.strings_len = be32_to_cpu ( hdr->size_dt_strings );
452 DBGC ( &fdt, "FDT strings block at +[%#04x,%#04zx)\n",
453 fdt.strings, ( fdt.strings + fdt.strings_len ) );
454 if ( ( fdt.strings > fdt.len ) ||
455 ( fdt.strings_len > ( fdt.len - fdt.strings ) ) ) {
456 DBGC ( &fdt, "FDT strings block exceeds table\n" );
457 goto err;
458 }
459
460 /* Shrink strings block to ensure NUL termination safety */
461 end = ( fdt.raw + fdt.strings + fdt.strings_len );
462 for ( ; fdt.strings_len ; fdt.strings_len-- ) {
463 if ( *(--end) == '\0' )
464 break;
465 }
466 if ( fdt.strings_len != be32_to_cpu ( hdr->size_dt_strings ) ) {
467 DBGC ( &fdt, "FDT strings block shrunk to +[%#04x,%#04zx)\n",
468 fdt.strings, ( fdt.strings + fdt.strings_len ) );
469 }
470
471 /* Print model name (for debugging) */
472 DBGC ( &fdt, "FDT model is \"%s\"\n", fdt_string ( 0, "model" ) );
473
474 return 0;
475
476 err:
477 DBGC_HDA ( &fdt, 0, hdr, sizeof ( *hdr ) );
478 fdt.hdr = NULL;
479 return -EINVAL;
480 }
481
482 /* Drag in objects via register_fdt */
483 REQUIRING_SYMBOL ( register_fdt );
484
485 /* Drag in device tree configuration */
486 REQUIRE_OBJECT ( config_fdt );