| /* | |
| * 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 ); | |
| FILE_SECBOOT ( PERMITTED ); | |
| #include <string.h> | |
| #include <ctype.h> | |
| #include <errno.h> | |
| #include <assert.h> | |
| #include <byteswap.h> | |
| #include <ipxe/netdevice.h> | |
| #include <ipxe/image.h> | |
| #include <ipxe/uaccess.h> | |
| #include <ipxe/umalloc.h> | |
| #include <ipxe/fdt.h> | |
| /** @file | |
| * | |
| * Flattened Device Tree | |
| * | |
| */ | |
| /** The system flattened device tree (if present) */ | |
| struct fdt sysfdt; | |
| /** The downloaded flattened device tree tag */ | |
| struct image_tag fdt_image __image_tag = { | |
| .name = "FDT", | |
| }; | |
| /** Amount of free space to add whenever we have to reallocate a tree */ | |
| #define FDT_INSERT_PAD 1024 | |
| /** | |
| * Check if character is permitted in a name | |
| * | |
| * @v ch Character | |
| * @ret is_permitted Character is permitted in a name | |
| */ | |
| static int fdt_permitted ( char ch ) { | |
| static const char permitted[] = ",._+?#-"; | |
| return ( isalnum ( ch ) || strchr ( permitted, ch ) ); | |
| } | |
| /** | |
| * Compare node name | |
| * | |
| * @v desc Token descriptor | |
| * @v name Name (terminated by NUL or any non-permitted character) | |
| * @ret is_match Name matches token descriptor | |
| */ | |
| static int fdt_match ( const struct fdt_descriptor *desc, const char *name ) { | |
| size_t len = strlen ( desc->name ); | |
| /* Check name and terminator */ | |
| return ( ( memcmp ( desc->name, name, len ) == 0 ) && | |
| ( ! ( name[len] && fdt_permitted ( name[len] ) ) ) ); | |
| } | |
| /** | |
| * Describe device tree token | |
| * | |
| * @v fdt Device tree | |
| * @v offset Offset within structure block | |
| * @v desc Token descriptor to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_describe ( struct fdt *fdt, unsigned int offset, | |
| 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 ( offset <= fdt->len ); | |
| assert ( ( offset & ( FDT_STRUCTURE_ALIGN - 1 ) ) == 0 ); | |
| /* Initialise descriptor */ | |
| memset ( desc, 0, sizeof ( *desc ) ); | |
| desc->offset = offset; | |
| /* Locate token and calculate remaining space */ | |
| token = ( fdt->raw + fdt->structure + offset ); | |
| remaining = ( fdt->len - offset ); | |
| if ( remaining < sizeof ( *token ) ) { | |
| DBGC ( fdt, "FDT truncated tree at +%#04x\n", 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", | |
| offset ); | |
| return -EINVAL; | |
| } | |
| desc->depth = +1; | |
| break; | |
| case cpu_to_be32 ( FDT_END_NODE ): | |
| /* End of node */ | |
| desc->depth = -1; | |
| break; | |
| case cpu_to_be32 ( FDT_PROP ): | |
| /* Property */ | |
| prop = data; | |
| if ( remaining < sizeof ( *prop ) ) { | |
| DBGC ( fdt, "FDT truncated property at +%#04x\n", | |
| 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", | |
| 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", 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 ), offset ); | |
| return -EINVAL; | |
| } | |
| /* Calculate offset to next token */ | |
| len = ( ( len + FDT_STRUCTURE_ALIGN - 1 ) & | |
| ~( FDT_STRUCTURE_ALIGN - 1 ) ); | |
| offset += ( sizeof ( *token ) + len ); | |
| desc->next = offset; | |
| /* Sanity checks */ | |
| assert ( offset <= fdt->len ); | |
| return 0; | |
| } | |
| /** | |
| * Describe next device tree token | |
| * | |
| * @v fdt Device tree | |
| * @v desc Token descriptor to update | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_next ( struct fdt *fdt, struct fdt_descriptor *desc ) { | |
| /* Describe next token */ | |
| return fdt_describe ( fdt, desc->next, desc ); | |
| } | |
| /** | |
| * Enter node | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v desc Begin node descriptor to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_enter ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_descriptor *desc ) { | |
| int rc; | |
| /* Find begin node token */ | |
| for ( ; ; offset = desc->next ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_describe ( fdt, offset, desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Check for begin node token */ | |
| if ( desc->depth > 0 ) | |
| return 0; | |
| /* Check for non-NOPs */ | |
| if ( desc->depth ) { | |
| DBGC ( fdt, "FDT +%#04x has spurious node end at " | |
| "+%#04x\n", offset, desc->offset ); | |
| return -EINVAL; | |
| } | |
| if ( desc->name ) { | |
| DBGC ( fdt, "FDT +%#04x has spurious property at " | |
| "+%#04x\n", offset, desc->offset ); | |
| return -EINVAL; | |
| } | |
| } | |
| } | |
| /** | |
| * Find node relative depth | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v target Target node offset | |
| * @ret depth Depth, or negative error | |
| */ | |
| static int fdt_depth ( struct fdt *fdt, unsigned int offset, | |
| unsigned int target ) { | |
| struct fdt_descriptor desc; | |
| int depth; | |
| int rc; | |
| /* Enter node */ | |
| if ( ( rc = fdt_enter ( fdt, offset, &desc ) ) != 0 ) | |
| return rc; | |
| /* Find target node */ | |
| for ( depth = 0 ; depth >= 0 ; depth += desc.depth ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_next ( fdt, &desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Check for target node */ | |
| if ( desc.offset == target ) { | |
| DBGC2 ( fdt, "FDT +%#04x has descendant node +%#04x " | |
| "at depth +%d\n", offset, target, depth ); | |
| return depth; | |
| } | |
| } | |
| DBGC ( fdt, "FDT +#%04x has no descendant node +%#04x\n", | |
| offset, target ); | |
| return -ENOENT; | |
| } | |
| /** | |
| * Find parent node | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v parent Parent node offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_parent ( struct fdt *fdt, unsigned int offset, unsigned int *parent ) { | |
| struct fdt_descriptor desc; | |
| int pdepth; | |
| int depth; | |
| int rc; | |
| /* Find depth from root of tree */ | |
| depth = fdt_depth ( fdt, 0, offset ); | |
| if ( depth < 0 ) { | |
| rc = depth; | |
| return rc; | |
| } | |
| pdepth = ( depth - 1 ); | |
| /* Enter root node */ | |
| if ( ( rc = fdt_enter ( fdt, 0, &desc ) ) != 0 ) | |
| return rc; | |
| *parent = desc.offset; | |
| /* Find parent node */ | |
| for ( depth = 0 ; depth >= 0 ; depth += desc.depth ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_next ( fdt, &desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Record possible parent node */ | |
| if ( ( depth == pdepth ) && desc.name && ( ! desc.data ) ) | |
| *parent = desc.offset; | |
| /* Check for target node */ | |
| if ( desc.offset == offset ) { | |
| DBGC2 ( fdt, "FDT +%#04x has parent node at +%#04x\n", | |
| offset, *parent ); | |
| return 0; | |
| } | |
| } | |
| DBGC ( fdt, "FDT +#%04x has no parent node\n", offset ); | |
| return -ENOENT; | |
| } | |
| /** | |
| * Find child node | |
| * | |
| * @v fdt Device tree | |
| * @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 ( struct fdt *fdt, unsigned int offset, const char *name, | |
| unsigned int *child ) { | |
| struct fdt_descriptor desc; | |
| int depth; | |
| int rc; | |
| /* Enter node */ | |
| if ( ( rc = fdt_enter ( fdt, offset, &desc ) ) != 0 ) | |
| return rc; | |
| /* Find child node */ | |
| for ( depth = 0 ; depth >= 0 ; depth += desc.depth ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_next ( fdt, &desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Check for matching immediate child node */ | |
| if ( ( depth == 0 ) && desc.name && ( ! desc.data ) ) { | |
| DBGC2 ( fdt, "FDT +%#04x has child node \"%s\" at " | |
| "+%#04x\n", offset, desc.name, desc.offset ); | |
| assert ( desc.depth > 0 ); | |
| if ( fdt_match ( &desc, name ) ) { | |
| *child = desc.offset; | |
| return 0; | |
| } | |
| } | |
| } | |
| DBGC2 ( fdt, "FDT +%#04x has no child node \"%s\"\n", offset, name ); | |
| return -ENOENT; | |
| } | |
| /** | |
| * Find end of node | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v end End of node offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_end ( struct fdt *fdt, unsigned int offset, | |
| unsigned int *end ) { | |
| struct fdt_descriptor desc; | |
| int depth; | |
| int rc; | |
| /* Enter node */ | |
| if ( ( rc = fdt_enter ( fdt, offset, &desc ) ) != 0 ) | |
| return rc; | |
| /* Find end of this node */ | |
| for ( depth = 0 ; depth >= 0 ; depth += desc.depth ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_next ( fdt, &desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| } | |
| /* Record end offset */ | |
| *end = desc.offset; | |
| DBGC2 ( fdt, "FDT +%#04x has end at +%#04x\n", offset, *end ); | |
| return 0; | |
| } | |
| /** | |
| * Find node by path | |
| * | |
| * @v fdt Device tree | |
| * @v path Node path | |
| * @v offset Offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_path ( struct fdt *fdt, const char *path, unsigned int *offset ) { | |
| const char *tmp = path; | |
| int rc; | |
| /* Initialise offset */ | |
| *offset = 0; | |
| /* Traverse tree one path segment at a time */ | |
| while ( 1 ) { | |
| /* Skip any leading '/' */ | |
| while ( *tmp == '/' ) | |
| tmp++; | |
| /* Terminate if there are no more path components */ | |
| if ( ! *tmp ) | |
| break; | |
| /* Find child */ | |
| if ( ( rc = fdt_child ( fdt, *offset, tmp, offset ) ) != 0 ) | |
| return rc; | |
| /* Move to next path component, if any */ | |
| tmp = strchr ( tmp, '/' ); | |
| if ( ! tmp ) | |
| break; | |
| } | |
| DBGC2 ( fdt, "FDT found path \"%s\" at +%#04x\n", path, *offset ); | |
| return 0; | |
| } | |
| /** | |
| * Find node by alias | |
| * | |
| * @v fdt Device tree | |
| * @v name Alias name | |
| * @v offset Offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_alias ( struct fdt *fdt, const char *name, unsigned int *offset ) { | |
| const char *alias; | |
| int rc; | |
| /* Locate "/aliases" node */ | |
| if ( ( rc = fdt_child ( fdt, 0, "aliases", offset ) ) != 0 ) | |
| return rc; | |
| /* Locate alias property */ | |
| if ( ( alias = fdt_string ( fdt, *offset, name ) ) == NULL ) | |
| return -ENOENT; | |
| DBGC ( fdt, "FDT alias \"%s\" is \"%s\"\n", name, alias ); | |
| /* Locate aliased node */ | |
| if ( ( rc = fdt_path ( fdt, alias, offset ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Find property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v desc Token descriptor to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_property ( struct fdt *fdt, unsigned int offset, | |
| const char *name, struct fdt_descriptor *desc ) { | |
| int depth; | |
| int rc; | |
| /* Enter node */ | |
| if ( ( rc = fdt_enter ( fdt, offset, desc ) ) != 0 ) | |
| return rc; | |
| /* Find property */ | |
| for ( depth = 0 ; depth == 0 ; depth += desc->depth ) { | |
| /* Describe token */ | |
| if ( ( rc = fdt_next ( fdt, desc ) ) != 0 ) { | |
| DBGC ( fdt, "FDT +%#04x has malformed node: %s\n", | |
| offset, strerror ( rc ) ); | |
| return rc; | |
| } | |
| /* Check for matching immediate child property */ | |
| if ( desc->data ) { | |
| DBGC2 ( fdt, "FDT +%#04x has property \"%s\" at " | |
| "+%#04x len %#zx\n", offset, desc->name, | |
| desc->offset, desc->len ); | |
| assert ( desc->depth == 0 ); | |
| if ( fdt_match ( desc, name ) ) { | |
| DBGC2_HDA ( fdt, 0, desc->data, desc->len ); | |
| return 0; | |
| } | |
| } | |
| } | |
| DBGC2 ( fdt, "FDT +%#04x has no property \"%s\"\n", offset, name ); | |
| return -ENOENT; | |
| } | |
| /** | |
| * Find strings property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v count String count to fill in | |
| * @ret string String property, or NULL on error | |
| */ | |
| const char * fdt_strings ( struct fdt *fdt, unsigned int offset, | |
| const char *name, unsigned int *count ) { | |
| struct fdt_descriptor desc; | |
| const char *data; | |
| size_t len; | |
| int rc; | |
| /* Return a zero count on error */ | |
| *count = 0; | |
| /* Find property */ | |
| if ( ( rc = fdt_property ( fdt, offset, name, &desc ) ) != 0 ) | |
| return NULL; | |
| /* Check NUL termination */ | |
| data = desc.data; | |
| if ( desc.len && ( data[ desc.len - 1 ] != '\0' ) ) { | |
| DBGC ( fdt, "FDT unterminated string property \"%s\"\n", | |
| name ); | |
| return NULL; | |
| } | |
| /* Count number of strings */ | |
| for ( len = desc.len ; len-- ; ) { | |
| if ( data[len] == '\0' ) | |
| (*count)++; | |
| } | |
| return data; | |
| } | |
| /** | |
| * Find string property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @ret string String property, or NULL on error | |
| */ | |
| const char * fdt_string ( struct fdt *fdt, unsigned int offset, | |
| const char *name ) { | |
| unsigned int count; | |
| /* Find strings property */ | |
| return fdt_strings ( fdt, offset, name, &count ); | |
| } | |
| /** | |
| * Get integer property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v index Starting cell index | |
| * @v count Number of cells (or 0 to read all remaining cells) | |
| * @v value Integer value to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_cells ( struct fdt *fdt, unsigned int offset, const char *name, | |
| unsigned int index, unsigned int count, uint64_t *value ) { | |
| struct fdt_descriptor desc; | |
| const uint32_t *cell; | |
| unsigned int total; | |
| int rc; | |
| /* Clear value */ | |
| *value = 0; | |
| /* Find property */ | |
| if ( ( rc = fdt_property ( fdt, offset, name, &desc ) ) != 0 ) | |
| return rc; | |
| cell = desc.data; | |
| /* Determine number of cells */ | |
| total = ( desc.len / sizeof ( *cell ) ); | |
| if ( ( index > total ) || ( count > ( total - index ) ) ) { | |
| DBGC ( fdt, "FDT truncated integer \"%s\"\n", name ); | |
| return -ERANGE; | |
| } | |
| if ( ! count ) | |
| count = ( total - index ); | |
| if ( count > ( sizeof ( *value ) / sizeof ( *cell ) ) ) { | |
| DBGC ( fdt, "FDT overlength integer \"%s\"\n", name ); | |
| return -ERANGE; | |
| } | |
| /* Read value */ | |
| for ( cell += index ; count ; cell++, count-- ) { | |
| *value <<= 32; | |
| *value |= be32_to_cpu ( *cell ); | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get 64-bit integer property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v value Integer value to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_u64 ( struct fdt *fdt, unsigned int offset, const char *name, | |
| uint64_t *value ) { | |
| int rc; | |
| /* Read value */ | |
| if ( ( rc = fdt_cells ( fdt, offset, name, 0, 0, value ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Get 32-bit integer property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v value Integer value to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_u32 ( struct fdt *fdt, unsigned int offset, const char *name, | |
| uint32_t *value ) { | |
| uint64_t value64; | |
| int rc; | |
| /* Read value */ | |
| if ( ( rc = fdt_u64 ( fdt, offset, name, &value64 ) ) != 0 ) | |
| return rc; | |
| /* Check range */ | |
| *value = value64; | |
| if ( *value != value64 ) { | |
| DBGC ( fdt, "FDT overlength 32-bit integer \"%s\"\n", name ); | |
| return -ERANGE; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get package handle (phandle) property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @ret phandle Package handle, or 0 on error | |
| */ | |
| uint32_t fdt_phandle ( struct fdt *fdt, unsigned int offset ) { | |
| uint32_t phandle; | |
| int rc; | |
| /* Get "phandle" or "linux,phandle" property */ | |
| if ( ( ( rc = fdt_u32 ( fdt, offset, "phandle", &phandle ) ) == 0 ) || | |
| ( ( rc = fdt_u32 ( fdt, offset, "linux,phandle", | |
| &phandle ) ) == 0 ) ) { | |
| assert ( phandle != 0 ); | |
| return phandle; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get region cell size specification | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting (parent) node offset | |
| * @v regs Region cell size specification to fill in | |
| * | |
| * Note that #address-cells and #size-cells are defined on the | |
| * immediate parent node, rather than on the node with the "reg" | |
| * property itself. | |
| */ | |
| void fdt_reg_cells ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_reg_cells *regs ) { | |
| int rc; | |
| /* Read #address-cells, if present */ | |
| if ( ( rc = fdt_u32 ( fdt, offset, "#address-cells", | |
| ®s->address_cells ) ) != 0 ) { | |
| regs->address_cells = FDT_DEFAULT_ADDRESS_CELLS; | |
| } | |
| /* Read #size-cells, if present */ | |
| if ( ( rc = fdt_u32 ( fdt, offset, "#size-cells", | |
| ®s->size_cells ) ) != 0 ) { | |
| regs->size_cells = FDT_DEFAULT_SIZE_CELLS; | |
| } | |
| /* Calculate stride */ | |
| regs->stride = ( regs->address_cells + regs->size_cells ); | |
| } | |
| /** | |
| * Get parent region cell size specification | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v regs Region cell size specification to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_parent_reg_cells ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_reg_cells *regs ) { | |
| unsigned int parent; | |
| int rc; | |
| /* Get parent node */ | |
| if ( ( rc = fdt_parent ( fdt, offset, &parent ) ) != 0 ) | |
| return rc; | |
| /* Read #address-cells and #size-cells, if present */ | |
| fdt_reg_cells ( fdt, parent, regs ); | |
| return 0; | |
| } | |
| /** | |
| * Get number of regions | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v regs Region cell size specification | |
| * @ret count Number of regions, or negative error | |
| */ | |
| int fdt_reg_count ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_reg_cells *regs ) { | |
| struct fdt_descriptor desc; | |
| const uint32_t *cell; | |
| unsigned int count; | |
| int rc; | |
| /* Find property */ | |
| if ( ( rc = fdt_property ( fdt, offset, "reg", &desc ) ) != 0 ) | |
| return rc; | |
| /* Determine number of regions */ | |
| count = ( desc.len / ( regs->stride * sizeof ( *cell ) ) ); | |
| return count; | |
| } | |
| /** | |
| * Get region address | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v regs Region cell size specification | |
| * @v index Region index | |
| * @v address Region starting address to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_reg_address ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_reg_cells *regs, unsigned int index, | |
| uint64_t *address ) { | |
| unsigned int cell = ( index * regs->stride ); | |
| int rc; | |
| /* Read relevant portion of region array */ | |
| if ( ( rc = fdt_cells ( fdt, offset, "reg", cell, regs->address_cells, | |
| address ) ) != 0 ) { | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get region size | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v regs Region cell size specification | |
| * @v index Region index | |
| * @v size Region size to fill in | |
| * @ret rc Return status code | |
| */ | |
| int fdt_reg_size ( struct fdt *fdt, unsigned int offset, | |
| struct fdt_reg_cells *regs, unsigned int index, | |
| uint64_t *size ) { | |
| unsigned int cell = ( ( index * regs->stride ) + regs->address_cells ); | |
| int rc; | |
| /* Read relevant portion of region array */ | |
| if ( ( rc = fdt_cells ( fdt, offset, "reg", cell, regs->size_cells, | |
| size ) ) != 0 ) { | |
| return rc; | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Get unsized single-entry region address | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v address Region address to fill in | |
| * @ret rc Return status code | |
| * | |
| * Many region types (e.g. I2C bus addresses) can only ever contain a | |
| * single region with no size cells specified. | |
| */ | |
| int fdt_reg ( struct fdt *fdt, unsigned int offset, uint64_t *region ) { | |
| struct fdt_reg_cells regs; | |
| int rc; | |
| /* Get parent region cell size specification */ | |
| if ( ( rc = fdt_parent_reg_cells ( fdt, offset, ®s ) ) != 0 ) | |
| return rc; | |
| /* Get first region address */ | |
| if ( ( rc = fdt_reg_address ( fdt, offset, ®s, 0, region ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Get MAC address from property | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v netdev Network device | |
| * @ret rc Return status code | |
| */ | |
| int fdt_mac ( struct fdt *fdt, 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 ( fdt, offset, "mac-address", | |
| &desc ) ) != 0 ) && | |
| ( ( rc = fdt_property ( fdt, 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; | |
| } | |
| /** | |
| * Parse device tree | |
| * | |
| * @v fdt Device tree | |
| * @v hdr Device tree header | |
| * @v max_len Maximum device tree length | |
| * @ret rc Return status code | |
| */ | |
| int fdt_parse ( struct fdt *fdt, struct fdt_header *hdr, size_t max_len ) { | |
| const uint8_t *nul; | |
| unsigned int chosen; | |
| size_t end; | |
| /* Sanity check */ | |
| if ( sizeof ( *hdr ) > max_len ) { | |
| DBGC ( fdt, "FDT length %#zx too short for header\n", | |
| max_len ); | |
| goto err; | |
| } | |
| /* Record device tree location */ | |
| fdt->hdr = hdr; | |
| fdt->len = be32_to_cpu ( hdr->totalsize ); | |
| fdt->used = sizeof ( *hdr ); | |
| if ( fdt->len > max_len ) { | |
| DBGC ( fdt, "FDT has invalid length %#zx / %#zx\n", | |
| fdt->len, max_len ); | |
| goto err; | |
| } | |
| DBGC ( fdt, "FDT version %d at %p+%#04zx (phys %#08lx)\n", | |
| be32_to_cpu ( hdr->version ), fdt->hdr, fdt->len, | |
| virt_to_phys ( hdr ) ); | |
| /* 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; | |
| } | |
| end = ( fdt->structure + fdt->structure_len ); | |
| if ( fdt->used < end ) | |
| fdt->used = end; | |
| /* 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; | |
| } | |
| end = ( fdt->strings + fdt->strings_len ); | |
| if ( fdt->used < end ) | |
| fdt->used = end; | |
| /* Shrink strings block to ensure NUL termination safety */ | |
| nul = ( fdt->raw + fdt->strings + fdt->strings_len ); | |
| for ( ; fdt->strings_len ; fdt->strings_len-- ) { | |
| if ( *(--nul) == '\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 ) ); | |
| } | |
| /* Record memory reservation block location */ | |
| fdt->reservations = be32_to_cpu ( hdr->off_mem_rsvmap ); | |
| DBGC ( fdt, "FDT memory reservations at +[%#04x,...)\n", | |
| fdt->reservations ); | |
| if ( fdt->used <= fdt->reservations ) { | |
| /* No size field exists: assume whole table is used */ | |
| fdt->used = fdt->len; | |
| } | |
| /* Identify free space (if any) */ | |
| if ( fdt->used < fdt->len ) { | |
| DBGC ( fdt, "FDT free space at +[%#04zx,%#04zx)\n", | |
| fdt->used, fdt->len ); | |
| } | |
| /* Print model name and boot arguments (for debugging) */ | |
| if ( DBG_LOG ) { | |
| DBGC ( fdt, "FDT model is \"%s\"\n", | |
| fdt_string ( fdt, 0, "model" ) ); | |
| if ( fdt_child ( fdt, 0, "chosen", &chosen ) == 0 ) { | |
| DBGC ( fdt, "FDT boot arguments \"%s\"\n", | |
| fdt_string ( fdt, chosen, "bootargs" ) ); | |
| } | |
| } | |
| return 0; | |
| err: | |
| DBGC_HDA ( fdt, 0, hdr, sizeof ( *hdr ) ); | |
| memset ( fdt, 0, sizeof ( *fdt ) ); | |
| return -EINVAL; | |
| } | |
| /** | |
| * Parse device tree image | |
| * | |
| * @v fdt Device tree | |
| * @v image Image | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_parse_image ( struct fdt *fdt, struct image *image ) { | |
| int rc; | |
| /* Parse image */ | |
| if ( ( rc = fdt_parse ( fdt, image->rwdata, image->len ) ) != 0 ) { | |
| DBGC ( fdt, "FDT image \"%s\" is invalid: %s\n", | |
| image->name, strerror ( rc ) ); | |
| return rc; | |
| } | |
| DBGC ( fdt, "FDT image is \"%s\"\n", image->name ); | |
| return 0; | |
| } | |
| /** | |
| * Insert empty space | |
| * | |
| * @v fdt Device tree | |
| * @v offset Offset at which to insert space | |
| * @v len Length to insert (must be a multiple of FDT_MAX_ALIGN) | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_insert ( struct fdt *fdt, unsigned int offset, size_t len ) { | |
| size_t free; | |
| size_t new; | |
| int rc; | |
| /* Sanity checks */ | |
| assert ( offset <= fdt->used ); | |
| assert ( fdt->used <= fdt->len ); | |
| assert ( ( len % FDT_MAX_ALIGN ) == 0 ); | |
| /* Reallocate tree if necessary */ | |
| free = ( fdt->len - fdt->used ); | |
| if ( free < len ) { | |
| if ( ! fdt->realloc ) { | |
| DBGC ( fdt, "FDT is not reallocatable\n" ); | |
| return -ENOTSUP; | |
| } | |
| new = ( fdt->len + ( len - free ) + FDT_INSERT_PAD ); | |
| if ( ( rc = fdt->realloc ( fdt, new ) ) != 0 ) | |
| return rc; | |
| } | |
| assert ( ( fdt->used + len ) <= fdt->len ); | |
| /* Insert empty space */ | |
| memmove ( ( fdt->raw + offset + len ), ( fdt->raw + offset ), | |
| ( fdt->used - offset ) ); | |
| memset ( ( fdt->raw + offset ), 0, len ); | |
| fdt->used += len; | |
| /* Update offsets | |
| * | |
| * We assume that we never need to legitimately insert data at | |
| * the start of a block, and therefore can unambiguously | |
| * determine which block offsets need to be updated. | |
| * | |
| * It is the caller's responsibility to update the length (and | |
| * contents) of the block into which it has inserted space. | |
| */ | |
| if ( fdt->structure >= offset ) { | |
| fdt->structure += len; | |
| fdt->hdr->off_dt_struct = cpu_to_be32 ( fdt->structure ); | |
| DBGC ( fdt, "FDT structure block now at +[%#04x,%#04zx)\n", | |
| fdt->structure, | |
| ( fdt->structure + fdt->structure_len ) ); | |
| } | |
| if ( fdt->strings >= offset ) { | |
| fdt->strings += len; | |
| fdt->hdr->off_dt_strings = cpu_to_be32 ( fdt->strings ); | |
| DBGC ( fdt, "FDT strings block now at +[%#04x,%#04zx)\n", | |
| fdt->strings, ( fdt->strings + fdt->strings_len ) ); | |
| } | |
| if ( fdt->reservations >= offset ) { | |
| fdt->reservations += len; | |
| fdt->hdr->off_mem_rsvmap = cpu_to_be32 ( fdt->reservations ); | |
| DBGC ( fdt, "FDT memory reservations now at +[%#04x,...)\n", | |
| fdt->reservations ); | |
| } | |
| return 0; | |
| } | |
| /** | |
| * Fill space in structure block with FDT_NOP | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting offset | |
| * @v len Length (must be a multiple of FDT_STRUCTURE_ALIGN) | |
| */ | |
| static void fdt_nop ( struct fdt *fdt, unsigned int offset, size_t len ) { | |
| fdt_token_t *token; | |
| unsigned int count; | |
| /* Sanity check */ | |
| assert ( ( len % FDT_STRUCTURE_ALIGN ) == 0 ); | |
| /* Fill with FDT_NOP */ | |
| token = ( fdt->raw + fdt->structure + offset ); | |
| count = ( len / sizeof ( *token ) ); | |
| while ( count-- ) | |
| *(token++) = cpu_to_be32 ( FDT_NOP ); | |
| } | |
| /** | |
| * Insert FDT_NOP padded space in structure block | |
| * | |
| * @v fdt Device tree | |
| * @v offset Offset at which to insert space | |
| * @v len Minimal length to insert | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_insert_nop ( struct fdt *fdt, unsigned int offset, | |
| size_t len ) { | |
| int rc; | |
| /* Sanity check */ | |
| assert ( ( offset % FDT_STRUCTURE_ALIGN ) == 0 ); | |
| /* Round up inserted length to maximal alignment */ | |
| len = ( ( len + FDT_MAX_ALIGN - 1 ) & ~( FDT_MAX_ALIGN - 1 ) ); | |
| /* Insert empty space in structure block */ | |
| if ( ( rc = fdt_insert ( fdt, ( fdt->structure + offset ), | |
| len ) ) != 0 ) | |
| return rc; | |
| /* Fill with NOPs */ | |
| fdt_nop ( fdt, offset, len ); | |
| /* Update structure block size */ | |
| fdt->structure_len += len; | |
| fdt->hdr->size_dt_struct = cpu_to_be32 ( fdt->structure_len ); | |
| DBGC ( fdt, "FDT structure block now at +[%#04x,%#04zx)\n", | |
| fdt->structure, ( fdt->structure + fdt->structure_len ) ); | |
| return 0; | |
| } | |
| /** | |
| * Insert string in strings block | |
| * | |
| * @v fdt Device tree | |
| * @v string String | |
| * @v offset String offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_insert_string ( struct fdt *fdt, const char *string, | |
| unsigned int *offset ) { | |
| size_t len = ( strlen ( string ) + 1 /* NUL */ ); | |
| int rc; | |
| /* Round up inserted length to maximal alignment */ | |
| len = ( ( len + FDT_MAX_ALIGN - 1 ) & ~( FDT_MAX_ALIGN - 1 ) ); | |
| /* Insert space at end of strings block */ | |
| if ( ( rc = fdt_insert ( fdt, ( fdt->strings + fdt->strings_len ), | |
| len ) ) != 0 ) | |
| return rc; | |
| /* Append string to strings block */ | |
| *offset = fdt->strings_len; | |
| strcpy ( ( fdt->raw + fdt->strings + *offset ), string ); | |
| /* Update strings block size */ | |
| fdt->strings_len += len; | |
| fdt->hdr->size_dt_strings = cpu_to_be32 ( fdt->strings_len ); | |
| DBGC ( fdt, "FDT strings block now at +[%#04x,%#04zx)\n", | |
| fdt->strings, ( fdt->strings + fdt->strings_len ) ); | |
| return 0; | |
| } | |
| /** | |
| * Ensure child node exists | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name New node name | |
| * @v child Child node offset to fill in | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_ensure_child ( struct fdt *fdt, unsigned int offset, | |
| const char *name, unsigned int *child ) { | |
| size_t name_len = ( strlen ( name ) + 1 /* NUL */ ); | |
| fdt_token_t *token; | |
| size_t len; | |
| int rc; | |
| /* Find existing child node, if any */ | |
| if ( ( rc = fdt_child ( fdt, offset, name, child ) ) == 0 ) | |
| return 0; | |
| /* Find end of parent node */ | |
| if ( ( rc = fdt_end ( fdt, offset, child ) ) != 0 ) | |
| return rc; | |
| /* Insert space for child node (with maximal alignment) */ | |
| len = ( sizeof ( fdt_token_t ) /* BEGIN_NODE */ + name_len + | |
| sizeof ( fdt_token_t ) /* END_NODE */ ); | |
| if ( ( rc = fdt_insert_nop ( fdt, *child, len ) ) != 0 ) | |
| return rc; | |
| /* Construct node */ | |
| token = ( fdt->raw + fdt->structure + *child ); | |
| *(token++) = cpu_to_be32 ( FDT_BEGIN_NODE ); | |
| memcpy ( token, name, name_len ); | |
| name_len = ( ( name_len + FDT_STRUCTURE_ALIGN - 1 ) & | |
| ~( FDT_STRUCTURE_ALIGN - 1 ) ); | |
| token = ( ( ( void * ) token ) + name_len ); | |
| *(token++) = cpu_to_be32 ( FDT_END_NODE ); | |
| DBGC2 ( fdt, "FDT +%#04x created child \"%s\" at +%#04x\n", | |
| offset, name, *child ); | |
| return 0; | |
| } | |
| /** | |
| * Set property value | |
| * | |
| * @v fdt Device tree | |
| * @v offset Starting node offset | |
| * @v name Property name | |
| * @v data Property data, or NULL to delete property | |
| * @v len Length of property data | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_set ( struct fdt *fdt, unsigned int offset, const char *name, | |
| const void *data, size_t len ) { | |
| struct fdt_descriptor desc; | |
| struct { | |
| fdt_token_t token; | |
| struct fdt_prop prop; | |
| uint8_t data[0]; | |
| } __attribute__ (( packed )) *hdr; | |
| unsigned int string; | |
| size_t erase; | |
| size_t insert; | |
| int rc; | |
| /* Find and reuse existing property, if any */ | |
| if ( ( rc = fdt_property ( fdt, offset, name, &desc ) ) == 0 ) { | |
| /* Reuse existing name */ | |
| hdr = ( fdt->raw + fdt->structure + desc.offset ); | |
| string = be32_to_cpu ( hdr->prop.name_off ); | |
| /* Erase existing property */ | |
| erase = ( sizeof ( *hdr ) + desc.len ); | |
| erase = ( ( erase + FDT_STRUCTURE_ALIGN - 1 ) & | |
| ~( FDT_STRUCTURE_ALIGN - 1 ) ); | |
| fdt_nop ( fdt, desc.offset, erase ); | |
| DBGC2 ( fdt, "FDT +%#04x erased property \"%s\"\n", | |
| offset, name ); | |
| /* Calculate insertion position and length */ | |
| insert = ( ( desc.len < len ) ? ( len - desc.len ) : 0 ); | |
| } else { | |
| /* Create name */ | |
| if ( ( rc = fdt_insert_string ( fdt, name, &string ) ) != 0 ) | |
| return rc; | |
| /* Enter node */ | |
| if ( ( rc = fdt_enter ( fdt, offset, &desc ) ) != 0 ) | |
| return rc; | |
| assert ( desc.depth > 0 ); | |
| desc.offset = desc.next; | |
| /* Calculate insertion length */ | |
| insert = ( sizeof ( *hdr ) + len ); | |
| } | |
| /* Leave property erased if applicable */ | |
| if ( ! data ) | |
| return 0; | |
| /* Insert space */ | |
| if ( ( rc = fdt_insert_nop ( fdt, desc.offset, insert ) ) != 0 ) | |
| return rc; | |
| /* Construct property */ | |
| hdr = ( fdt->raw + fdt->structure + desc.offset ); | |
| hdr->token = cpu_to_be32 ( FDT_PROP ); | |
| hdr->prop.len = cpu_to_be32 ( len ); | |
| hdr->prop.name_off = cpu_to_be32 ( string ); | |
| memset ( hdr->data, 0, ( ( len + FDT_STRUCTURE_ALIGN - 1 ) & | |
| ~( FDT_STRUCTURE_ALIGN - 1 ) ) ); | |
| memcpy ( hdr->data, data, len ); | |
| DBGC2 ( fdt, "FDT +%#04x created property \"%s\"\n", offset, name ); | |
| DBGC2_HDA ( fdt, 0, hdr->data, len ); | |
| return 0; | |
| } | |
| /** | |
| * Reallocate device tree via urealloc() | |
| * | |
| * @v fdt Device tree | |
| * @v len New total length | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_urealloc ( struct fdt *fdt, size_t len ) { | |
| void *new; | |
| /* Sanity check */ | |
| assert ( len >= fdt->used ); | |
| /* Attempt reallocation */ | |
| new = urealloc ( fdt->raw, len ); | |
| if ( ! new ) { | |
| DBGC ( fdt, "FDT could not reallocate from +%#04zx to " | |
| "+%#04zx\n", fdt->len, len ); | |
| return -ENOMEM; | |
| } | |
| DBGC ( fdt, "FDT reallocated from +%#04zx to +%#04zx\n", | |
| fdt->len, len ); | |
| /* Update device tree */ | |
| fdt->raw = new; | |
| fdt->len = len; | |
| fdt->hdr->totalsize = cpu_to_be32 ( len ); | |
| return 0; | |
| } | |
| /** | |
| * Populate device tree with boot arguments | |
| * | |
| * @v fdt Device tree | |
| * @v cmdline Command line, or NULL | |
| * @v initrd Initial ramdisk address (or 0 for no initrd) | |
| * @v initrd_len Initial ramdisk length (or 0 for no initrd) | |
| * @ret rc Return status code | |
| */ | |
| static int fdt_bootargs ( struct fdt *fdt, const char *cmdline, | |
| physaddr_t initrd, size_t initrd_len ) { | |
| unsigned int chosen; | |
| physaddr_t addr; | |
| const void *data; | |
| size_t len; | |
| int rc; | |
| /* Ensure "chosen" node exists */ | |
| if ( ( rc = fdt_ensure_child ( fdt, 0, "chosen", &chosen ) ) != 0 ) | |
| return rc; | |
| /* Set or clear "bootargs" property */ | |
| len = ( cmdline ? ( strlen ( cmdline ) + 1 /* NUL */ ) : 0 ); | |
| if ( ( rc = fdt_set ( fdt, chosen, "bootargs", cmdline, len ) ) != 0 ) | |
| return rc; | |
| /* Set or clear initrd properties */ | |
| data = ( initrd_len ? &addr : NULL ); | |
| len = ( initrd_len ? sizeof ( addr ) : 0 ); | |
| addr = initrd; | |
| addr = ( ( sizeof ( addr ) == sizeof ( uint64_t ) ) ? | |
| cpu_to_be64 ( addr ) : cpu_to_be32 ( addr ) ); | |
| if ( ( rc = fdt_set ( fdt, chosen, "linux,initrd-start", data, | |
| len ) ) != 0 ) | |
| return rc; | |
| addr = ( initrd + initrd_len ); | |
| addr = ( ( sizeof ( addr ) == sizeof ( uint64_t ) ) ? | |
| cpu_to_be64 ( addr ) : cpu_to_be32 ( addr ) ); | |
| if ( ( rc = fdt_set ( fdt, chosen, "linux,initrd-end", data, | |
| len ) ) != 0 ) | |
| return rc; | |
| return 0; | |
| } | |
| /** | |
| * Create device tree | |
| * | |
| * @v hdr Device tree header to fill in (may be set to NULL) | |
| * @v cmdline Command line, or NULL | |
| * @v initrd Initial ramdisk address (or 0 for no initrd) | |
| * @v initrd_len Initial ramdisk length (or 0 for no initrd) | |
| * @ret rc Return status code | |
| */ | |
| int fdt_create ( struct fdt_header **hdr, const char *cmdline, | |
| physaddr_t initrd, size_t initrd_len ) { | |
| struct image *image; | |
| struct fdt fdt; | |
| void *copy; | |
| int rc; | |
| /* Use system FDT as the base by default */ | |
| memcpy ( &fdt, &sysfdt, sizeof ( fdt ) ); | |
| /* If an FDT image exists, use this instead */ | |
| image = find_image_tag ( &fdt_image ); | |
| if ( image && ( ( rc = fdt_parse_image ( &fdt, image ) ) != 0 ) ) | |
| goto err_image; | |
| /* Exit successfully if we have no base FDT */ | |
| if ( ! fdt.len ) { | |
| DBGC ( &fdt, "FDT has no base tree\n" ); | |
| goto no_fdt; | |
| } | |
| /* Create modifiable copy */ | |
| copy = umalloc ( fdt.len ); | |
| if ( ! copy ) { | |
| rc = -ENOMEM; | |
| goto err_alloc; | |
| } | |
| memcpy ( copy, fdt.raw, fdt.len ); | |
| fdt.raw = copy; | |
| fdt.realloc = fdt_urealloc; | |
| /* Populate boot arguments */ | |
| if ( ( rc = fdt_bootargs ( &fdt, cmdline, initrd, initrd_len ) ) != 0 ) | |
| goto err_bootargs; | |
| no_fdt: | |
| *hdr = fdt.raw; | |
| return 0; | |
| err_bootargs: | |
| ufree ( fdt.raw ); | |
| err_alloc: | |
| err_image: | |
| return rc; | |
| } | |
| /** | |
| * Remove device tree | |
| * | |
| * @v hdr Device tree header, or NULL | |
| */ | |
| void fdt_remove ( struct fdt_header *hdr ) { | |
| /* Free modifiable copy */ | |
| ufree ( hdr ); | |
| } | |
| /* Drag in objects via fdt_describe() */ | |
| REQUIRING_SYMBOL ( fdt_describe ); | |
| /* Drag in device tree configuration */ | |
| REQUIRE_OBJECT ( config_fdt ); |