diff --git a/src/config/config_fdt.c b/src/config/config_fdt.c new file mode 100644 index 000000000..85d62ace1 --- /dev/null +++ b/src/config/config_fdt.c @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2019 Michael Brown . + * + * 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 + +/** @file + * + * Flattened Device Tree configuration options + * + */ + +PROVIDE_REQUIRING_SYMBOL(); + +/* + * Drag in devicetree sources + */ diff --git a/src/config/fdt.h b/src/config/fdt.h new file mode 100644 index 000000000..4d13e0535 --- /dev/null +++ b/src/config/fdt.h @@ -0,0 +1,16 @@ +#ifndef CONFIG_FDT_H +#define CONFIG_FDT_H + +/** @file + * + * Flattened Device Tree configuration + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +#include + +#endif /* CONFIG_FDT_H */ diff --git a/src/core/fdt.c b/src/core/fdt.c new file mode 100644 index 000000000..f439422cf --- /dev/null +++ b/src/core/fdt.c @@ -0,0 +1,486 @@ +/* + * Copyright (C) 2019 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include + +/** @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 ); diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 1a92b6ce2..242f91f82 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -74,6 +74,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_block ( ERRFILE_CORE | 0x00220000 ) #define ERRFILE_sanboot ( ERRFILE_CORE | 0x00230000 ) #define ERRFILE_dummy_sanboot ( ERRFILE_CORE | 0x00240000 ) +#define ERRFILE_fdt ( ERRFILE_CORE | 0x00250000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/include/ipxe/fdt.h b/src/include/ipxe/fdt.h new file mode 100644 index 000000000..97efa100c --- /dev/null +++ b/src/include/ipxe/fdt.h @@ -0,0 +1,102 @@ +#ifndef _IPXE_FDT_H +#define _IPXE_FDT_H + +/** @file + * + * Flattened Device Tree + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +struct net_device; + +/** Device tree header */ +struct fdt_header { + /** Magic signature */ + uint32_t magic; + /** Total size of device tree */ + uint32_t totalsize; + /** Offset to structure block */ + uint32_t off_dt_struct; + /** Offset to strings block */ + uint32_t off_dt_strings; + /** Offset to memory reservation block */ + uint32_t off_mem_rsvmap; + /** Version of this data structure */ + uint32_t version; + /** Lowest version to which this structure is compatible */ + uint32_t last_comp_version; + /** Physical ID of the boot CPU */ + uint32_t boot_cpuid_phys; + /** Length of string block */ + uint32_t size_dt_strings; + /** Length of structure block */ + uint32_t size_dt_struct; +} __attribute__ (( packed )); + +/** Magic signature */ +#define FDT_MAGIC 0xd00dfeed + +/** Expected device tree version */ +#define FDT_VERSION 16 + +/** Device tree token */ +typedef uint32_t fdt_token_t; + +/** Begin node token */ +#define FDT_BEGIN_NODE 0x00000001 + +/** End node token */ +#define FDT_END_NODE 0x00000002 + +/** Property token */ +#define FDT_PROP 0x00000003 + +/** Property fragment */ +struct fdt_prop { + /** Data length */ + uint32_t len; + /** Name offset */ + uint32_t name_off; +} __attribute__ (( packed )); + +/** NOP token */ +#define FDT_NOP 0x00000004 + +/** End of structure block */ +#define FDT_END 0x00000009 + +/** Alignment of structure block */ +#define FDT_STRUCTURE_ALIGN ( sizeof ( fdt_token_t ) ) + +/** A device tree */ +struct fdt { + /** Tree data */ + union { + /** Tree header */ + const struct fdt_header *hdr; + /** Raw data */ + const void *raw; + }; + /** Length of tree */ + size_t len; + /** Offset to structure block */ + unsigned int structure; + /** Length of structure block */ + size_t structure_len; + /** Offset to strings block */ + unsigned int strings; + /** Length of strings block */ + size_t strings_len; +}; + +extern int fdt_path ( const char *path, unsigned int *offset ); +extern int fdt_alias ( const char *name, unsigned int *offset ); +extern const char * fdt_string ( unsigned int offset, const char *name ); +extern int fdt_mac ( unsigned int offset, struct net_device *netdev ); +extern int register_fdt ( const struct fdt_header *hdr ); + +#endif /* _IPXE_FDT_H */