123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- /*
- * Copyright (C) 2019 Michael Brown <mbrown@fensystems.co.uk>.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301, USA.
- *
- * You can also choose to distribute this program under the terms of
- * the Unmodified Binary Distribution Licence (as given in the file
- * COPYING.UBDL), provided that you have satisfied its requirements.
- */
-
- FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
-
- #include <string.h>
- #include <errno.h>
- #include <assert.h>
- #include <byteswap.h>
- #include <ipxe/netdevice.h>
- #include <ipxe/fdt.h>
-
- /** @file
- *
- * Flattened Device Tree
- *
- */
-
- /** The system flattened device tree (if present) */
- static struct fdt fdt;
-
- /** A position within a device tree */
- struct fdt_cursor {
- /** Offset within structure block */
- unsigned int offset;
- /** Tree depth */
- int depth;
- };
-
- /** A lexical descriptor */
- struct fdt_descriptor {
- /** Node or property name (if applicable) */
- const char *name;
- /** Property data (if applicable) */
- const void *data;
- /** Length of property data (if applicable) */
- size_t len;
- };
-
- /**
- * Check if device tree exists
- *
- * @v has_fdt Device tree exists
- */
- static inline __attribute__ (( always_inline )) int fdt_exists ( void ) {
-
- return ( fdt.hdr != NULL );
- }
-
- /**
- * Traverse device tree
- *
- * @v pos Position within device tree
- * @v desc Lexical descriptor to fill in
- * @ret rc Return status code
- */
- static int fdt_traverse ( struct fdt_cursor *pos,
- struct fdt_descriptor *desc ) {
- const fdt_token_t *token;
- const void *data;
- const struct fdt_prop *prop;
- unsigned int name_off;
- size_t remaining;
- size_t len;
-
- /* Sanity checks */
- assert ( pos->offset < fdt.len );
- assert ( ( pos->offset & ( FDT_STRUCTURE_ALIGN - 1 ) ) == 0 );
-
- /* Clear descriptor */
- memset ( desc, 0, sizeof ( *desc ) );
-
- /* Locate token and calculate remaining space */
- token = ( fdt.raw + fdt.structure + pos->offset );
- remaining = ( fdt.len - pos->offset );
- if ( remaining < sizeof ( *token ) ) {
- DBGC ( &fdt, "FDT truncated tree at +%#04x\n", pos->offset );
- return -EINVAL;
- }
- remaining -= sizeof ( *token );
- data = ( ( ( const void * ) token ) + sizeof ( *token ) );
- len = 0;
-
- /* Handle token */
- switch ( *token ) {
-
- case cpu_to_be32 ( FDT_BEGIN_NODE ):
-
- /* Start of node */
- desc->name = data;
- len = ( strnlen ( desc->name, remaining ) + 1 /* NUL */ );
- if ( remaining < len ) {
- DBGC ( &fdt, "FDT unterminated node name at +%#04x\n",
- pos->offset );
- return -EINVAL;
- }
- pos->depth++;
- break;
-
- case cpu_to_be32 ( FDT_END_NODE ):
-
- /* End of node */
- if ( pos->depth < 0 ) {
- DBGC ( &fdt, "FDT spurious node end at +%#04x\n",
- pos->offset );
- return -EINVAL;
- }
- pos->depth--;
- if ( pos->depth < 0 ) {
- /* End of (sub)tree */
- return -ENOENT;
- }
- break;
-
- case cpu_to_be32 ( FDT_PROP ):
-
- /* Property */
- prop = data;
- if ( remaining < sizeof ( *prop ) ) {
- DBGC ( &fdt, "FDT truncated property at +%#04x\n",
- pos->offset );
- return -EINVAL;
- }
- desc->data = ( ( ( const void * ) prop ) + sizeof ( *prop ) );
- desc->len = be32_to_cpu ( prop->len );
- len = ( sizeof ( *prop ) + desc->len );
- if ( remaining < len ) {
- DBGC ( &fdt, "FDT overlength property at +%#04x\n",
- pos->offset );
- return -EINVAL;
- }
- name_off = be32_to_cpu ( prop->name_off );
- if ( name_off > fdt.strings_len ) {
- DBGC ( &fdt, "FDT property name outside strings "
- "block at +%#04x\n", pos->offset );
- return -EINVAL;
- }
- desc->name = ( fdt.raw + fdt.strings + name_off );
- break;
-
- case cpu_to_be32 ( FDT_NOP ):
-
- /* Do nothing */
- break;
-
- default:
-
- /* Unrecognised or unexpected token */
- DBGC ( &fdt, "FDT unexpected token %#08x at +%#04x\n",
- be32_to_cpu ( *token ), pos->offset );
- return -EINVAL;
- }
-
- /* Update cursor */
- len = ( ( len + FDT_STRUCTURE_ALIGN - 1 ) &
- ~( FDT_STRUCTURE_ALIGN - 1 ) );
- pos->offset += ( sizeof ( *token ) + len );
-
- /* Sanity checks */
- assert ( pos->offset <= fdt.len );
-
- return 0;
- }
-
- /**
- * Find child node
- *
- * @v offset Starting node offset
- * @v name Node name
- * @v child Child node offset to fill in
- * @ret rc Return status code
- */
- static int fdt_child ( unsigned int offset, const char *name,
- unsigned int *child ) {
- struct fdt_cursor pos;
- struct fdt_descriptor desc;
- unsigned int orig_offset;
- int rc;
-
- /* Record original offset (for debugging) */
- orig_offset = offset;
-
- /* Initialise cursor */
- pos.offset = offset;
- pos.depth = -1;
-
- /* Find child node */
- while ( 1 ) {
-
- /* Record current offset */
- *child = pos.offset;
-
- /* Traverse tree */
- if ( ( rc = fdt_traverse ( &pos, &desc ) ) != 0 ) {
- DBGC ( &fdt, "FDT +%#04x has no child node \"%s\": "
- "%s\n", orig_offset, name, strerror ( rc ) );
- return rc;
- }
-
- /* Check for matching immediate child node */
- if ( ( pos.depth == 1 ) && desc.name && ( ! desc.data ) ) {
- DBGC2 ( &fdt, "FDT +%#04x has child node \"%s\"\n",
- orig_offset, desc.name );
- if ( strcmp ( name, desc.name ) == 0 ) {
- DBGC2 ( &fdt, "FDT +%#04x found child node "
- "\"%s\" at +%#04x\n", orig_offset,
- desc.name, *child );
- return 0;
- }
- }
- }
- }
-
- /**
- * Find node by path
- *
- * @v path Node path
- * @v offset Offset to fill in
- * @ret rc Return status code
- */
- int fdt_path ( const char *path, unsigned int *offset ) {
- char *tmp = ( ( char * ) path );
- char *del;
- int rc;
-
- /* Initialise offset */
- *offset = 0;
-
- /* Traverse tree one path segment at a time */
- while ( *tmp ) {
-
- /* Skip any leading '/' */
- while ( *tmp == '/' )
- tmp++;
-
- /* Find next '/' delimiter and convert to NUL */
- del = strchr ( tmp, '/' );
- if ( del )
- *del = '\0';
-
- /* Find child and restore delimiter */
- rc = fdt_child ( *offset, tmp, offset );
- if ( del )
- *del = '/';
- if ( rc != 0 )
- return rc;
-
- /* Move to next path component, if any */
- while ( *tmp && ( *tmp != '/' ) )
- tmp++;
- }
-
- DBGC2 ( &fdt, "FDT found path \"%s\" at +%#04x\n", path, *offset );
- return 0;
- }
-
- /**
- * Find node by alias
- *
- * @v name Alias name
- * @v offset Offset to fill in
- * @ret rc Return status code
- */
- int fdt_alias ( const char *name, unsigned int *offset ) {
- const char *alias;
- int rc;
-
- /* Locate "/aliases" node */
- if ( ( rc = fdt_child ( 0, "aliases", offset ) ) != 0 )
- return rc;
-
- /* Locate alias property */
- if ( ( alias = fdt_string ( *offset, name ) ) == NULL )
- return -ENOENT;
- DBGC ( &fdt, "FDT alias \"%s\" is \"%s\"\n", name, alias );
-
- /* Locate aliased node */
- if ( ( rc = fdt_path ( alias, offset ) ) != 0 )
- return rc;
-
- return 0;
- }
-
- /**
- * Find property
- *
- * @v offset Starting node offset
- * @v name Property name
- * @v desc Lexical descriptor to fill in
- * @ret rc Return status code
- */
- static int fdt_property ( unsigned int offset, const char *name,
- struct fdt_descriptor *desc ) {
- struct fdt_cursor pos;
- int rc;
-
- /* Initialise cursor */
- pos.offset = offset;
- pos.depth = -1;
-
- /* Find property */
- while ( 1 ) {
-
- /* Traverse tree */
- if ( ( rc = fdt_traverse ( &pos, desc ) ) != 0 ) {
- DBGC ( &fdt, "FDT +%#04x has no property \"%s\": %s\n",
- offset, name, strerror ( rc ) );
- return rc;
- }
-
- /* Check for matching immediate child property */
- if ( ( pos.depth == 0 ) && desc->data ) {
- DBGC2 ( &fdt, "FDT +%#04x has property \"%s\" len "
- "%#zx\n", offset, desc->name, desc->len );
- if ( strcmp ( name, desc->name ) == 0 ) {
- DBGC2 ( &fdt, "FDT +%#04x found property "
- "\"%s\"\n", offset, desc->name );
- DBGC2_HDA ( &fdt, 0, desc->data, desc->len );
- return 0;
- }
- }
- }
- }
-
- /**
- * Find string property
- *
- * @v offset Starting node offset
- * @v name Property name
- * @ret string String property, or NULL on error
- */
- const char * fdt_string ( unsigned int offset, const char *name ) {
- struct fdt_descriptor desc;
- int rc;
-
- /* Find property */
- if ( ( rc = fdt_property ( offset, name, &desc ) ) != 0 )
- return NULL;
-
- /* Check NUL termination */
- if ( strnlen ( desc.data, desc.len ) == desc.len ) {
- DBGC ( &fdt, "FDT unterminated string property \"%s\"\n",
- name );
- return NULL;
- }
-
- return desc.data;
- }
-
- /**
- * Get MAC address from property
- *
- * @v offset Starting node offset
- * @v netdev Network device
- * @ret rc Return status code
- */
- int fdt_mac ( unsigned int offset, struct net_device *netdev ) {
- struct fdt_descriptor desc;
- size_t len;
- int rc;
-
- /* Find applicable MAC address property */
- if ( ( ( rc = fdt_property ( offset, "mac-address", &desc ) ) != 0 ) &&
- ( ( rc = fdt_property ( offset, "local-mac-address",
- &desc ) ) != 0 ) ) {
- return rc;
- }
-
- /* Check length */
- len = netdev->ll_protocol->hw_addr_len;
- if ( len != desc.len ) {
- DBGC ( &fdt, "FDT malformed MAC address \"%s\":\n",
- desc.name );
- DBGC_HDA ( &fdt, 0, desc.data, desc.len );
- return -ERANGE;
- }
-
- /* Fill in MAC address */
- memcpy ( netdev->hw_addr, desc.data, len );
-
- return 0;
- }
-
- /**
- * Register device tree
- *
- * @v fdt Device tree header
- * @ret rc Return status code
- */
- int register_fdt ( const struct fdt_header *hdr ) {
- const uint8_t *end;
-
- /* Record device tree location */
- fdt.hdr = hdr;
- fdt.len = be32_to_cpu ( hdr->totalsize );
- DBGC ( &fdt, "FDT version %d at %p+%#04zx\n",
- be32_to_cpu ( hdr->version ), fdt.hdr, fdt.len );
-
- /* Check signature */
- if ( hdr->magic != cpu_to_be32 ( FDT_MAGIC ) ) {
- DBGC ( &fdt, "FDT has invalid magic value %#08x\n",
- be32_to_cpu ( hdr->magic ) );
- goto err;
- }
-
- /* Check version */
- if ( hdr->last_comp_version != cpu_to_be32 ( FDT_VERSION ) ) {
- DBGC ( &fdt, "FDT unsupported version %d\n",
- be32_to_cpu ( hdr->last_comp_version ) );
- goto err;
- }
-
- /* Record structure block location */
- fdt.structure = be32_to_cpu ( hdr->off_dt_struct );
- fdt.structure_len = be32_to_cpu ( hdr->size_dt_struct );
- DBGC ( &fdt, "FDT structure block at +[%#04x,%#04zx)\n",
- fdt.structure, ( fdt.structure + fdt.structure_len ) );
- if ( ( fdt.structure > fdt.len ) ||
- ( fdt.structure_len > ( fdt.len - fdt.structure ) ) ) {
- DBGC ( &fdt, "FDT structure block exceeds table\n" );
- goto err;
- }
- if ( ( fdt.structure | fdt.structure_len ) &
- ( FDT_STRUCTURE_ALIGN - 1 ) ) {
- DBGC ( &fdt, "FDT structure block is misaligned\n" );
- goto err;
- }
-
- /* Record strings block location */
- fdt.strings = be32_to_cpu ( hdr->off_dt_strings );
- fdt.strings_len = be32_to_cpu ( hdr->size_dt_strings );
- DBGC ( &fdt, "FDT strings block at +[%#04x,%#04zx)\n",
- fdt.strings, ( fdt.strings + fdt.strings_len ) );
- if ( ( fdt.strings > fdt.len ) ||
- ( fdt.strings_len > ( fdt.len - fdt.strings ) ) ) {
- DBGC ( &fdt, "FDT strings block exceeds table\n" );
- goto err;
- }
-
- /* Shrink strings block to ensure NUL termination safety */
- end = ( fdt.raw + fdt.strings + fdt.strings_len );
- for ( ; fdt.strings_len ; fdt.strings_len-- ) {
- if ( *(--end) == '\0' )
- break;
- }
- if ( fdt.strings_len != be32_to_cpu ( hdr->size_dt_strings ) ) {
- DBGC ( &fdt, "FDT strings block shrunk to +[%#04x,%#04zx)\n",
- fdt.strings, ( fdt.strings + fdt.strings_len ) );
- }
-
- /* Print model name (for debugging) */
- DBGC ( &fdt, "FDT model is \"%s\"\n", fdt_string ( 0, "model" ) );
-
- return 0;
-
- err:
- DBGC_HDA ( &fdt, 0, hdr, sizeof ( *hdr ) );
- fdt.hdr = NULL;
- return -EINVAL;
- }
-
- /* Drag in objects via register_fdt */
- REQUIRING_SYMBOL ( register_fdt );
-
- /* Drag in device tree configuration */
- REQUIRE_OBJECT ( config_fdt );
|