coreboot/src/lib/device_tree.c
Alper Nebi Yasak 377157c7fb device_tree: Add function to get top of memory from a FDT blob
coreboot needs to figure out top of memory to place CBMEM data. On some
non-x86 QEMU virtual machines, this is achieved by probing the RAM space
to find where the VM starts discarding data since it's not backed by
actual RAM. This behaviour seems to have changed on the QEMU side since
then, VMs using the "virt" model have started raising exceptions/errors
instead of silently discarding data (likely [1] for example) which has
previously broken coreboot on these emulation boards.

The qemu-aarch64 and qemu-riscv mainboards are intended for the "virt"
models and had this issue, which were mostly fixed by using exception
handlers in the RAM detection process [2][3]. But on 32-bit RISC-V we
fail to initialize CBMEM if we have 2048 MiB or more of RAM, and on
64-bit RISC-V we had to limit probing to 16383 MiB because it can run
into MMIO regions otherwise.

The qemu-armv7 mainboard code is intended for the "vexpress-a9" model VM
which doesn't appear to suffer from this issue. Still, the issue can be
observed on the ARMv7 "virt" model via a port based on qemu-aarch64.

QEMU docs for ARM and RISC-V "virt" models [4][5] recommend reading the
device tree blob it provides for device information (incl. RAM size).
Implement functions that parse the device tree blob to find described
memory regions and calculate the top of memory in order to use it in
mainboard code as an alternative to probing RAM space. ARM64 code
initializes CBMEM in romstage where malloc isn't available, so take care
to do parsing without unflattening the blob and make the code available
in romstage as well.

[1] https://lore.kernel.org/qemu-devel/1504626814-23124-1-git-send-email-peter.maydell@linaro.org/T/#u
[2] https://review.coreboot.org/c/coreboot/+/34774
[3] https://review.coreboot.org/c/coreboot/+/36486
[4] https://qemu-project.gitlab.io/qemu/system/arm/virt.html
[5] https://qemu-project.gitlab.io/qemu/system/riscv/virt.html

Change-Id: I8bef09bc1bc4e324ebeaa37f78d67d3aa315f52c
Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/80322
Reviewed-by: Maximilian Brune <maximilian.brune@9elements.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
2024-06-03 15:38:55 +00:00

2013 lines
58 KiB
C

/* Taken from depthcharge: src/base/device_tree.c */
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include <assert.h>
#include <console/console.h>
#include <ctype.h>
#include <device_tree.h>
#include <endian.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <limits.h>
#define FDT_PATH_MAX_DEPTH 10 // should be a good enough upper bound
#define FDT_PATH_MAX_LEN 128 // should be a good enough upper bound
#define FDT_MAX_MEMORY_NODES 4 // should be a good enough upper bound
#define FDT_MAX_MEMORY_REGIONS 16 // should be a good enough upper bound
/*
* Functions for picking apart flattened trees.
*/
static int fdt_skip_nops(const void *blob, uint32_t offset)
{
uint32_t *ptr = (uint32_t *)(((uint8_t *)blob) + offset);
int index = 0;
while (be32toh(ptr[index]) == FDT_TOKEN_NOP)
index++;
return index * sizeof(uint32_t);
}
int fdt_next_property(const void *blob, uint32_t offset,
struct fdt_property *prop)
{
struct fdt_header *header = (struct fdt_header *)blob;
uint32_t *ptr = (uint32_t *)(((uint8_t *)blob) + offset);
// skip NOP tokens
offset += fdt_skip_nops(blob, offset);
int index = 0;
if (be32toh(ptr[index++]) != FDT_TOKEN_PROPERTY)
return 0;
uint32_t size = be32toh(ptr[index++]);
uint32_t name_offset = be32toh(ptr[index++]);
name_offset += be32toh(header->strings_offset);
if (prop) {
prop->name = (char *)((uint8_t *)blob + name_offset);
prop->data = &ptr[index];
prop->size = size;
}
index += DIV_ROUND_UP(size, sizeof(uint32_t));
return index * sizeof(uint32_t);
}
/*
* fdt_next_node_name reads a node name
*
* @params blob address of FDT
* @params offset offset to the node to read the name from
* @params name parameter to hold the name that has been read or NULL
*
* @returns Either 0 on error or offset to the properties that come after the node name
*/
int fdt_next_node_name(const void *blob, uint32_t offset, const char **name)
{
// skip NOP tokens
offset += fdt_skip_nops(blob, offset);
char *ptr = ((char *)blob) + offset;
if (be32dec(ptr) != FDT_TOKEN_BEGIN_NODE)
return 0;
ptr += 4;
if (name)
*name = ptr;
return ALIGN_UP(strlen(ptr) + 1, 4) + 4;
}
/*
* A utility function to skip past nodes in flattened trees.
*/
int fdt_skip_node(const void *blob, uint32_t start_offset)
{
uint32_t offset = start_offset;
const char *name;
int size = fdt_next_node_name(blob, offset, &name);
if (!size)
return 0;
offset += size;
while ((size = fdt_next_property(blob, offset, NULL)))
offset += size;
while ((size = fdt_skip_node(blob, offset)))
offset += size;
// skip NOP tokens
offset += fdt_skip_nops(blob, offset);
return offset - start_offset + sizeof(uint32_t);
}
/*
* fdt_read_prop reads a property inside a node
*
* @params blob address of FDT
* @params node_offset offset to the node to read the property from
* @params prop_name name of the property to read
* @params fdt_prop property is saved inside this parameter
*
* @returns Either 0 if no property has been found or an offset that points to the location
* of the property
*/
u32 fdt_read_prop(const void *blob, u32 node_offset, const char *prop_name,
struct fdt_property *fdt_prop)
{
u32 offset = node_offset;
offset += fdt_next_node_name(blob, offset, NULL); // skip node name
size_t size;
while ((size = fdt_next_property(blob, offset, fdt_prop))) {
if (strcmp(fdt_prop->name, prop_name) == 0)
return offset;
offset += size;
}
return 0; // property not found
}
/*
* fdt_read_reg_prop reads the reg property inside a node
*
* @params blob address of FDT
* @params node_offset offset to the node to read the reg property from
* @params addr_cells number of cells used for one address
* @params size_cells number of cells used for one size
* @params regions all regions that are read inside the reg property are saved inside
* this array
* @params regions_count maximum number of entries that can be saved inside the regions array.
*
* Returns: Either 0 on error or returns the number of regions put into the regions array.
*/
u32 fdt_read_reg_prop(const void *blob, u32 node_offset, u32 addr_cells, u32 size_cells,
struct device_tree_region regions[], size_t regions_count)
{
struct fdt_property prop;
u32 offset = fdt_read_prop(blob, node_offset, "reg", &prop);
if (!offset) {
printk(BIOS_DEBUG, "no reg property found in node_offset: %x\n", node_offset);
return 0;
}
// we found the reg property, now need to parse all regions in 'reg'
size_t count = prop.size / (4 * addr_cells + 4 * size_cells);
if (count > regions_count) {
printk(BIOS_ERR, "reg property at node_offset: %x has more entries (%zd) than regions array can hold (%zd)\n", node_offset, count, regions_count);
count = regions_count;
}
if (addr_cells > 2 || size_cells > 2) {
printk(BIOS_ERR, "addr_cells (%d) or size_cells (%d) bigger than 2\n",
addr_cells, size_cells);
return 0;
}
uint32_t *ptr = prop.data;
for (int i = 0; i < count; i++) {
if (addr_cells == 1)
regions[i].addr = be32dec(ptr);
else if (addr_cells == 2)
regions[i].addr = be64dec(ptr);
ptr += addr_cells;
if (size_cells == 1)
regions[i].size = be32dec(ptr);
else if (size_cells == 2)
regions[i].size = be64dec(ptr);
ptr += size_cells;
}
return count; // return the number of regions found in the reg property
}
static u32 fdt_read_cell_props(const void *blob, u32 node_offset, u32 *addrcp, u32 *sizecp)
{
struct fdt_property prop;
u32 offset = node_offset;
size_t size;
while ((size = fdt_next_property(blob, offset, &prop))) {
if (addrcp && !strcmp(prop.name, "#address-cells"))
*addrcp = be32dec(prop.data);
if (sizecp && !strcmp(prop.name, "#size-cells"))
*sizecp = be32dec(prop.data);
offset += size;
}
return offset;
}
/*
* fdt_find_node searches for a node relative to another node
*
* @params blob address of FDT
*
* @params parent_node_offset offset to node from which to traverse the tree
*
* @params path null terminated array of node names specifying a
* relative path (e.g: { "cpus", "cpu0", NULL })
*
* @params addrcp/sizecp If any address-cells and size-cells properties are found that are
* part of the parent node of the node we are looking, addrcp and sizecp
* are set to these respectively.
*
* @returns: Either 0 if no node has been found or the offset to the node found
*/
static u32 fdt_find_node(const void *blob, u32 parent_node_offset, char **path,
u32 *addrcp, u32 *sizecp)
{
if (*path == NULL)
return parent_node_offset; // node found
size_t size = fdt_next_node_name(blob, parent_node_offset, NULL); // skip node name
/*
* get address-cells and size-cells properties while skipping the others.
* According to spec address-cells and size-cells are not inherited, but we
* intentionally follow the Linux implementation here and treat them as inheritable.
*/
u32 node_offset = fdt_read_cell_props(blob, parent_node_offset + size, addrcp, sizecp);
const char *node_name;
// walk all children nodes
while ((size = fdt_next_node_name(blob, node_offset, &node_name))) {
if (!strcmp(*path, node_name)) {
// traverse one level deeper into the path
return fdt_find_node(blob, node_offset, path + 1, addrcp, sizecp);
}
// node is not the correct one. skip current node
node_offset += fdt_skip_node(blob, node_offset);
}
// we have searched everything and could not find a fitting node
return 0;
}
/*
* fdt_find_node_by_path finds a node behind a given node path
*
* @params blob address of FDT
* @params path absolute path to the node that should be searched for
*
* @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
* value found in the node of the node specified by node_offset. Either
* may be NULL to ignore. If no #address-cells and #size-cells is found
* default values of #address-cells=2 and #size-cells=1 are returned.
*
* @returns Either 0 on error or the offset to the node found behind the path
*/
u32 fdt_find_node_by_path(const void *blob, const char *path, u32 *addrcp, u32 *sizecp)
{
// sanity check
if (path[0] != '/') {
printk(BIOS_ERR, "devicetree path must start with a /\n");
return 0;
}
if (!blob) {
printk(BIOS_ERR, "devicetree blob is NULL\n");
return 0;
}
if (addrcp)
*addrcp = 2;
if (sizecp)
*sizecp = 1;
struct fdt_header *fdt_hdr = (struct fdt_header *)blob;
/*
* split path into separate nodes
* e.g: "/cpus/cpu0" -> { "cpus", "cpu0" }
*/
char *path_array[FDT_PATH_MAX_DEPTH];
size_t path_size = strlen(path);
assert(path_size < FDT_PATH_MAX_LEN);
char path_copy[FDT_PATH_MAX_LEN];
memcpy(path_copy, path, path_size + 1);
char *cur = path_copy;
int i;
for (i = 0; i < FDT_PATH_MAX_DEPTH; i++) {
path_array[i] = strtok_r(NULL, "/", &cur);
if (!path_array[i])
break;
}
assert(i < FDT_PATH_MAX_DEPTH);
return fdt_find_node(blob, be32toh(fdt_hdr->structure_offset), path_array, addrcp, sizecp);
}
/*
* fdt_find_subnodes_by_prefix finds a node with a given prefix relative to a parent node
*
* @params blob The FDT to search.
*
* @params node_offset offset to the node of which the children should be searched
*
* @params prefix A string to search for a node with a given prefix. This can for example
* be 'cpu' to look for all nodes matching this prefix. Only children of
* node_offset are searched. Therefore in order to search all nodes matching
* the 'cpu' prefix, node_offset should probably point to the 'cpus' node.
* An empty prefix ("") searches for all children nodes of node_offset.
*
* @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
* value found in the node of the node specified by node_offset. Either
* may be NULL to ignore. If no #address-cells and #size-cells is found
* addrcp and sizecp are left untouched.
*
* @params results Array of offsets pointing to each node matching the given prefix.
* @params results_len Number of entries allocated for the 'results' array
*
* @returns offset to last node found behind path or 0 if no node has been found
*/
size_t fdt_find_subnodes_by_prefix(const void *blob, u32 node_offset, const char *prefix,
u32 *addrcp, u32 *sizecp, u32 *results, size_t results_len)
{
// sanity checks
if (!blob || !results || !prefix) {
printk(BIOS_ERR, "%s: input parameter cannot be null/\n", __func__);
return 0;
}
u32 offset = node_offset;
// we don't care about the name of the current node
u32 size = fdt_next_node_name(blob, offset, NULL);
if (!size) {
printk(BIOS_ERR, "%s: node_offset: %x does not point to a node\n",
__func__, node_offset);
return 0;
}
offset += size;
/*
* update addrcp and sizecp if the node contains an address-cells and size-cells
* property. Otherwise use addrcp and sizecp provided by caller.
*/
offset = fdt_read_cell_props(blob, offset, addrcp, sizecp);
size_t count_results = 0;
int prefix_len = strlen(prefix);
const char *node_name;
// walk all children nodes of offset
while ((size = fdt_next_node_name(blob, offset, &node_name))) {
if (count_results >= results_len) {
printk(BIOS_WARNING,
"%s: results_len (%zd) smaller than count_results (%zd)\n",
__func__, results_len, count_results);
break;
}
if (!strncmp(prefix, node_name, prefix_len)) {
// we found a node that matches the prefix
results[count_results++] = offset;
}
// node does not match the prefix. skip current node
offset += fdt_skip_node(blob, offset);
}
// return last occurrence
return count_results;
}
static const char *fdt_read_alias_prop(const void *blob, const char *alias_name)
{
u32 node_offset = fdt_find_node_by_path(blob, "/aliases", NULL, NULL);
if (!node_offset) {
printk(BIOS_DEBUG, "no /aliases node found\n");
return NULL;
}
struct fdt_property alias_prop;
if (!fdt_read_prop(blob, node_offset, alias_name, &alias_prop)) {
printk(BIOS_DEBUG, "property %s in /aliases node not found\n", alias_name);
return NULL;
}
return (const char *)alias_prop.data;
}
/*
* Find a node in the tree from a string device tree path.
*
* @params blob Address to the FDT
* @params alias_name node name alias that should be searched for.
* @params addrcp/sizecp Pointer that will be updated with any #address-cells and #size-cells
* value found in the node of the node specified by node_offset. Either
* may be NULL to ignore. If no #address-cells and #size-cells is found
* default values of #address-cells=2 and #size-cells=1 are returned.
*
* @returns offset to last node found behind path or 0 if no node has been found
*/
u32 fdt_find_node_by_alias(const void *blob, const char *alias_name, u32 *addrcp, u32 *sizecp)
{
const char *node_name = fdt_read_alias_prop(blob, alias_name);
if (!node_name) {
printk(BIOS_DEBUG, "alias %s not found\n", alias_name);
return 0;
}
u32 node_offset = fdt_find_node_by_path(blob, node_name, addrcp, sizecp);
if (!node_offset) {
// This should not happen (invalid devicetree)
printk(BIOS_WARNING,
"Could not find node '%s', which alias was referring to '%s'\n",
node_name, alias_name);
return 0;
}
return node_offset;
}
/*
* Functions for printing flattened trees.
*/
static void print_indent(int depth)
{
printk(BIOS_DEBUG, "%*s", depth * 8, "");
}
static void print_property(const struct fdt_property *prop, int depth)
{
int is_string = prop->size > 0 &&
((char *)prop->data)[prop->size - 1] == '\0';
if (is_string) {
for (int i = 0; i < prop->size - 1; i++) {
if (!isprint(((char *)prop->data)[i])) {
is_string = 0;
break;
}
}
}
print_indent(depth);
if (is_string) {
printk(BIOS_DEBUG, "%s = \"%s\";\n",
prop->name, (const char *)prop->data);
} else {
printk(BIOS_DEBUG, "%s = < ", prop->name);
for (int i = 0; i < MIN(128, prop->size); i += 4) {
uint32_t val = 0;
for (int j = 0; j < MIN(4, prop->size - i); j++)
val |= ((uint8_t *)prop->data)[i + j] <<
(24 - j * 8);
printk(BIOS_DEBUG, "%#.2x ", val);
}
if (prop->size > 128)
printk(BIOS_DEBUG, "...");
printk(BIOS_DEBUG, ">;\n");
}
}
static int print_flat_node(const void *blob, uint32_t start_offset, int depth)
{
int offset = start_offset;
const char *name;
int size;
size = fdt_next_node_name(blob, offset, &name);
if (!size)
return 0;
offset += size;
print_indent(depth);
printk(BIOS_DEBUG, "%s {\n", name);
struct fdt_property prop;
while ((size = fdt_next_property(blob, offset, &prop))) {
print_property(&prop, depth + 1);
offset += size;
}
printk(BIOS_DEBUG, "\n"); /* empty line between props and nodes */
while ((size = print_flat_node(blob, offset, depth + 1)))
offset += size;
print_indent(depth);
printk(BIOS_DEBUG, "}\n");
return offset - start_offset + sizeof(uint32_t);
}
void fdt_print_node(const void *blob, uint32_t offset)
{
print_flat_node(blob, offset, 0);
}
/*
* fdt_read_memory_regions finds memory ranges from a flat device-tree
*
* @params blob address of FDT
* @params regions all regions that are read inside the reg property of
* memory nodes are saved inside this array
* @params regions_count maximum number of entries that can be saved inside
* the regions array.
*
* Returns: Either 0 on error or returns the number of regions put into the regions array.
*/
size_t fdt_read_memory_regions(const void *blob,
struct device_tree_region regions[],
size_t regions_count)
{
u32 node, root, addrcp, sizecp;
u32 nodes[FDT_MAX_MEMORY_NODES] = {0};
size_t region_idx = 0;
size_t node_count = 0;
if (!fdt_is_valid(blob))
return 0;
node = fdt_find_node_by_path(blob, "/memory", &addrcp, &sizecp);
if (node) {
region_idx += fdt_read_reg_prop(blob, node, addrcp, sizecp,
regions, regions_count);
if (region_idx >= regions_count) {
printk(BIOS_WARNING, "FDT: Too many memory regions\n");
goto out;
}
}
root = fdt_find_node_by_path(blob, "/", &addrcp, &sizecp);
node_count = fdt_find_subnodes_by_prefix(blob, root, "memory@",
&addrcp, &sizecp, nodes,
FDT_MAX_MEMORY_NODES);
if (node_count >= FDT_MAX_MEMORY_NODES) {
printk(BIOS_WARNING, "FDT: Too many memory nodes\n");
/* Can still reading the regions for those we got */
}
for (size_t i = 0; i < MIN(node_count, FDT_MAX_MEMORY_NODES); i++) {
region_idx += fdt_read_reg_prop(blob, nodes[i], addrcp, sizecp,
&regions[region_idx],
regions_count - region_idx);
if (region_idx >= regions_count) {
printk(BIOS_WARNING, "FDT: Too many memory regions\n");
goto out;
}
}
out:
for (size_t i = 0; i < MIN(region_idx, regions_count); i++) {
printk(BIOS_DEBUG, "FDT: Memory region [%#llx - %#llx]\n",
regions[i].addr, regions[i].addr + regions[i].size);
}
return region_idx;
}
/*
* fdt_get_memory_top finds top of memory from a flat device-tree
*
* @params blob address of FDT
*
* Returns: Either 0 on error or returns the maximum memory address
*/
uint64_t fdt_get_memory_top(const void *blob)
{
struct device_tree_region regions[FDT_MAX_MEMORY_REGIONS] = {0};
uint64_t top = 0;
uint64_t total = 0;
size_t count;
if (!fdt_is_valid(blob))
return 0;
count = fdt_read_memory_regions(blob, regions, FDT_MAX_MEMORY_REGIONS);
for (size_t i = 0; i < MIN(count, FDT_MAX_MEMORY_REGIONS); i++) {
top = MAX(top, regions[i].addr + regions[i].size);
total += regions[i].size;
}
printk(BIOS_DEBUG, "FDT: Found %u MiB of RAM\n",
(uint32_t)(total / MiB));
return top;
}
/*
* Functions to turn a flattened tree into an unflattened one.
*/
static int dt_prop_is_phandle(struct device_tree_property *prop)
{
return !(strcmp("phandle", prop->prop.name) &&
strcmp("linux,phandle", prop->prop.name));
}
static int fdt_unflatten_node(const void *blob, uint32_t start_offset,
struct device_tree *tree,
struct device_tree_node **new_node)
{
struct list_node *last;
int offset = start_offset;
const char *name;
int size;
size = fdt_next_node_name(blob, offset, &name);
if (!size)
return 0;
offset += size;
struct device_tree_node *node = xzalloc(sizeof(*node));
*new_node = node;
node->name = name;
struct fdt_property fprop;
last = &node->properties;
while ((size = fdt_next_property(blob, offset, &fprop))) {
struct device_tree_property *prop = xzalloc(sizeof(*prop));
prop->prop = fprop;
if (dt_prop_is_phandle(prop)) {
node->phandle = be32dec(prop->prop.data);
if (node->phandle > tree->max_phandle)
tree->max_phandle = node->phandle;
}
list_insert_after(&prop->list_node, last);
last = &prop->list_node;
offset += size;
}
struct device_tree_node *child;
last = &node->children;
while ((size = fdt_unflatten_node(blob, offset, tree, &child))) {
list_insert_after(&child->list_node, last);
last = &child->list_node;
offset += size;
}
return offset - start_offset + sizeof(uint32_t);
}
static int fdt_unflatten_map_entry(const void *blob, uint32_t offset,
struct device_tree_reserve_map_entry **new)
{
const uint64_t *ptr = (const uint64_t *)(((uint8_t *)blob) + offset);
const uint64_t start = be64toh(ptr[0]);
const uint64_t size = be64toh(ptr[1]);
if (!size)
return 0;
struct device_tree_reserve_map_entry *entry = xzalloc(sizeof(*entry));
*new = entry;
entry->start = start;
entry->size = size;
return sizeof(uint64_t) * 2;
}
bool fdt_is_valid(const void *blob)
{
const struct fdt_header *header = (const struct fdt_header *)blob;
uint32_t magic = be32toh(header->magic);
uint32_t version = be32toh(header->version);
uint32_t last_comp_version = be32toh(header->last_comp_version);
if (magic != FDT_HEADER_MAGIC) {
printk(BIOS_ERR, "Invalid device tree magic %#.8x!\n", magic);
return false;
}
if (last_comp_version > FDT_SUPPORTED_VERSION) {
printk(BIOS_ERR, "Unsupported device tree version %u(>=%u)\n",
version, last_comp_version);
return false;
}
if (version > FDT_SUPPORTED_VERSION)
printk(BIOS_NOTICE, "FDT version %u too new, should add support!\n",
version);
return true;
}
struct device_tree *fdt_unflatten(const void *blob)
{
struct device_tree *tree = xzalloc(sizeof(*tree));
const struct fdt_header *header = (const struct fdt_header *)blob;
tree->header = header;
if (!fdt_is_valid(blob))
return NULL;
uint32_t struct_offset = be32toh(header->structure_offset);
uint32_t strings_offset = be32toh(header->strings_offset);
uint32_t reserve_offset = be32toh(header->reserve_map_offset);
uint32_t min_offset = 0;
min_offset = MIN(struct_offset, strings_offset);
min_offset = MIN(min_offset, reserve_offset);
/* Assume everything up to the first non-header component is part of
the header and needs to be preserved. This will protect us against
new elements being added in the future. */
tree->header_size = min_offset;
struct device_tree_reserve_map_entry *entry;
uint32_t offset = reserve_offset;
int size;
struct list_node *last = &tree->reserve_map;
while ((size = fdt_unflatten_map_entry(blob, offset, &entry))) {
list_insert_after(&entry->list_node, last);
last = &entry->list_node;
offset += size;
}
fdt_unflatten_node(blob, struct_offset, tree, &tree->root);
return tree;
}
/*
* Functions to find the size of the device tree if it was flattened.
*/
static void dt_flat_prop_size(struct device_tree_property *prop,
uint32_t *struct_size, uint32_t *strings_size)
{
/* Starting token. */
*struct_size += sizeof(uint32_t);
/* Size. */
*struct_size += sizeof(uint32_t);
/* Name offset. */
*struct_size += sizeof(uint32_t);
/* Property value. */
*struct_size += ALIGN_UP(prop->prop.size, sizeof(uint32_t));
/* Property name. */
*strings_size += strlen(prop->prop.name) + 1;
}
static void dt_flat_node_size(struct device_tree_node *node,
uint32_t *struct_size, uint32_t *strings_size)
{
/* Starting token. */
*struct_size += sizeof(uint32_t);
/* Node name. */
*struct_size += ALIGN_UP(strlen(node->name) + 1, sizeof(uint32_t));
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node)
dt_flat_prop_size(prop, struct_size, strings_size);
struct device_tree_node *child;
list_for_each(child, node->children, list_node)
dt_flat_node_size(child, struct_size, strings_size);
/* End token. */
*struct_size += sizeof(uint32_t);
}
uint32_t dt_flat_size(const struct device_tree *tree)
{
uint32_t size = tree->header_size;
struct device_tree_reserve_map_entry *entry;
list_for_each(entry, tree->reserve_map, list_node)
size += sizeof(uint64_t) * 2;
size += sizeof(uint64_t) * 2;
uint32_t struct_size = 0;
uint32_t strings_size = 0;
dt_flat_node_size(tree->root, &struct_size, &strings_size);
size += struct_size;
/* End token. */
size += sizeof(uint32_t);
size += strings_size;
return size;
}
/*
* Functions to flatten a device tree.
*/
static void dt_flatten_map_entry(struct device_tree_reserve_map_entry *entry,
void **map_start)
{
((uint64_t *)*map_start)[0] = htobe64(entry->start);
((uint64_t *)*map_start)[1] = htobe64(entry->size);
*map_start = ((uint8_t *)*map_start) + sizeof(uint64_t) * 2;
}
static void dt_flatten_prop(struct device_tree_property *prop,
void **struct_start, void *strings_base,
void **strings_start)
{
uint8_t *dstruct = (uint8_t *)*struct_start;
uint8_t *dstrings = (uint8_t *)*strings_start;
be32enc(dstruct, FDT_TOKEN_PROPERTY);
dstruct += sizeof(uint32_t);
be32enc(dstruct, prop->prop.size);
dstruct += sizeof(uint32_t);
uint32_t name_offset = (uintptr_t)dstrings - (uintptr_t)strings_base;
be32enc(dstruct, name_offset);
dstruct += sizeof(uint32_t);
strcpy((char *)dstrings, prop->prop.name);
dstrings += strlen(prop->prop.name) + 1;
memcpy(dstruct, prop->prop.data, prop->prop.size);
dstruct += ALIGN_UP(prop->prop.size, sizeof(uint32_t));
*struct_start = dstruct;
*strings_start = dstrings;
}
static void dt_flatten_node(const struct device_tree_node *node,
void **struct_start, void *strings_base,
void **strings_start)
{
uint8_t *dstruct = (uint8_t *)*struct_start;
uint8_t *dstrings = (uint8_t *)*strings_start;
be32enc(dstruct, FDT_TOKEN_BEGIN_NODE);
dstruct += sizeof(uint32_t);
strcpy((char *)dstruct, node->name);
dstruct += ALIGN_UP(strlen(node->name) + 1, sizeof(uint32_t));
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node)
dt_flatten_prop(prop, (void **)&dstruct, strings_base,
(void **)&dstrings);
struct device_tree_node *child;
list_for_each(child, node->children, list_node)
dt_flatten_node(child, (void **)&dstruct, strings_base,
(void **)&dstrings);
be32enc(dstruct, FDT_TOKEN_END_NODE);
dstruct += sizeof(uint32_t);
*struct_start = dstruct;
*strings_start = dstrings;
}
void dt_flatten(const struct device_tree *tree, void *start_dest)
{
uint8_t *dest = (uint8_t *)start_dest;
memcpy(dest, tree->header, tree->header_size);
struct fdt_header *header = (struct fdt_header *)dest;
dest += tree->header_size;
struct device_tree_reserve_map_entry *entry;
list_for_each(entry, tree->reserve_map, list_node)
dt_flatten_map_entry(entry, (void **)&dest);
((uint64_t *)dest)[0] = ((uint64_t *)dest)[1] = 0;
dest += sizeof(uint64_t) * 2;
uint32_t struct_size = 0;
uint32_t strings_size = 0;
dt_flat_node_size(tree->root, &struct_size, &strings_size);
uint8_t *struct_start = dest;
header->structure_offset = htobe32(dest - (uint8_t *)start_dest);
header->structure_size = htobe32(struct_size);
dest += struct_size;
*((uint32_t *)dest) = htobe32(FDT_TOKEN_END);
dest += sizeof(uint32_t);
uint8_t *strings_start = dest;
header->strings_offset = htobe32(dest - (uint8_t *)start_dest);
header->strings_size = htobe32(strings_size);
dest += strings_size;
dt_flatten_node(tree->root, (void **)&struct_start, strings_start,
(void **)&strings_start);
header->totalsize = htobe32(dest - (uint8_t *)start_dest);
}
/*
* Functions for printing a non-flattened device tree.
*/
static void print_node(const struct device_tree_node *node, int depth)
{
print_indent(depth);
if (depth == 0) /* root node has no name, print a starting slash */
printk(BIOS_DEBUG, "/");
printk(BIOS_DEBUG, "%s {\n", node->name);
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node)
print_property(&prop->prop, depth + 1);
printk(BIOS_DEBUG, "\n"); /* empty line between props and nodes */
struct device_tree_node *child;
list_for_each(child, node->children, list_node)
print_node(child, depth + 1);
print_indent(depth);
printk(BIOS_DEBUG, "};\n");
}
void dt_print_node(const struct device_tree_node *node)
{
print_node(node, 0);
}
/*
* Functions for reading and manipulating an unflattened device tree.
*/
/*
* Read #address-cells and #size-cells properties from a node.
*
* @param node The device tree node to read from.
* @param addrcp Pointer to store #address-cells in, skipped if NULL.
* @param sizecp Pointer to store #size-cells in, skipped if NULL.
*/
void dt_read_cell_props(const struct device_tree_node *node, u32 *addrcp,
u32 *sizecp)
{
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (addrcp && !strcmp("#address-cells", prop->prop.name))
*addrcp = be32dec(prop->prop.data);
if (sizecp && !strcmp("#size-cells", prop->prop.name))
*sizecp = be32dec(prop->prop.data);
}
}
/*
* Find a node from a device tree path, relative to a parent node.
*
* @param parent The node from which to start the relative path lookup.
* @param path An array of path component strings that will be looked
* up in order to find the node. Must be terminated with
* a NULL pointer. Example: {'firmware', 'coreboot', NULL}
* @param addrcp Pointer that will be updated with any #address-cells
* value found in the path. May be NULL to ignore.
* @param sizecp Pointer that will be updated with any #size-cells
* value found in the path. May be NULL to ignore.
* @param create 1: Create node(s) if not found. 0: Return NULL instead.
* @return The found/created node, or NULL.
*/
struct device_tree_node *dt_find_node(struct device_tree_node *parent,
const char **path, u32 *addrcp,
u32 *sizecp, int create)
{
struct device_tree_node *node, *found = NULL;
/* Update #address-cells and #size-cells for this level. */
dt_read_cell_props(parent, addrcp, sizecp);
if (!*path)
return parent;
/* Find the next node in the path, if it exists. */
list_for_each(node, parent->children, list_node) {
if (!strcmp(node->name, *path)) {
found = node;
break;
}
}
/* Otherwise create it or return NULL. */
if (!found) {
if (!create)
return NULL;
found = calloc(1, sizeof(*found));
if (!found)
return NULL;
found->name = strdup(*path);
if (!found->name)
return NULL;
list_insert_after(&found->list_node, &parent->children);
}
return dt_find_node(found, path + 1, addrcp, sizecp, create);
}
/*
* Find a node in the tree from a string device tree path.
*
* @param tree The device tree to search.
* @param path A string representing a path in the device tree, with
* nodes separated by '/'. Example: "/firmware/coreboot"
* @param addrcp Pointer that will be updated with any #address-cells
* value found in the path. May be NULL to ignore.
* @param sizecp Pointer that will be updated with any #size-cells
* value found in the path. May be NULL to ignore.
* @param create 1: Create node(s) if not found. 0: Return NULL instead.
* @return The found/created node, or NULL.
*
* It is the caller responsibility to provide a path string that doesn't end
* with a '/' and doesn't contain any "//". If the path does not start with a
* '/', the first segment is interpreted as an alias. */
struct device_tree_node *dt_find_node_by_path(struct device_tree *tree,
const char *path, u32 *addrcp,
u32 *sizecp, int create)
{
char *sub_path;
char *duped_str;
struct device_tree_node *parent;
char *next_slash;
/* Hopefully enough depth for any node. */
const char *path_array[15];
int i;
struct device_tree_node *node = NULL;
if (path[0] == '/') { /* regular path */
if (path[1] == '\0') { /* special case: "/" is root node */
dt_read_cell_props(tree->root, addrcp, sizecp);
return tree->root;
}
sub_path = duped_str = strdup(&path[1]);
if (!sub_path)
return NULL;
parent = tree->root;
} else { /* alias */
char *alias;
alias = duped_str = strdup(path);
if (!alias)
return NULL;
sub_path = strchr(alias, '/');
if (sub_path)
*sub_path = '\0';
parent = dt_find_node_by_alias(tree, alias);
if (!parent) {
printk(BIOS_DEBUG,
"Could not find node '%s', alias '%s' does not exist\n",
path, alias);
free(duped_str);
return NULL;
}
if (!sub_path) {
/* it's just the alias, no sub-path */
free(duped_str);
return parent;
}
sub_path++;
}
next_slash = sub_path;
path_array[0] = sub_path;
for (i = 1; i < (ARRAY_SIZE(path_array) - 1); i++) {
next_slash = strchr(next_slash, '/');
if (!next_slash)
break;
*next_slash++ = '\0';
path_array[i] = next_slash;
}
if (!next_slash) {
path_array[i] = NULL;
node = dt_find_node(parent, path_array,
addrcp, sizecp, create);
}
free(duped_str);
return node;
}
/*
* Find a node from an alias
*
* @param tree The device tree.
* @param alias The alias name.
* @return The found node, or NULL.
*/
struct device_tree_node *dt_find_node_by_alias(struct device_tree *tree,
const char *alias)
{
struct device_tree_node *node;
const char *alias_path;
node = dt_find_node_by_path(tree, "/aliases", NULL, NULL, 0);
if (!node)
return NULL;
alias_path = dt_find_string_prop(node, alias);
if (!alias_path)
return NULL;
return dt_find_node_by_path(tree, alias_path, NULL, NULL, 0);
}
struct device_tree_node *dt_find_node_by_phandle(struct device_tree_node *root,
uint32_t phandle)
{
if (!root)
return NULL;
if (root->phandle == phandle)
return root;
struct device_tree_node *node;
struct device_tree_node *result;
list_for_each(node, root->children, list_node) {
result = dt_find_node_by_phandle(node, phandle);
if (result)
return result;
}
return NULL;
}
/*
* Check if given node is compatible.
*
* @param node The node which is to be checked for compatible property.
* @param compat The compatible string to match.
* @return 1 = compatible, 0 = not compatible.
*/
static int dt_check_compat_match(struct device_tree_node *node,
const char *compat)
{
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (!strcmp("compatible", prop->prop.name)) {
size_t bytes = prop->prop.size;
const char *str = prop->prop.data;
while (bytes > 0) {
if (!strncmp(compat, str, bytes))
return 1;
size_t len = strnlen(str, bytes) + 1;
if (bytes <= len)
break;
str += len;
bytes -= len;
}
break;
}
}
return 0;
}
/*
* Find a node from a compatible string, in the subtree of a parent node.
*
* @param parent The parent node under which to look.
* @param compat The compatible string to find.
* @return The found node, or NULL.
*/
struct device_tree_node *dt_find_compat(struct device_tree_node *parent,
const char *compat)
{
/* Check if the parent node itself is compatible. */
if (dt_check_compat_match(parent, compat))
return parent;
struct device_tree_node *child;
list_for_each(child, parent->children, list_node) {
struct device_tree_node *found = dt_find_compat(child, compat);
if (found)
return found;
}
return NULL;
}
/*
* Find the next compatible child of a given parent. All children up to the
* child passed in by caller are ignored. If child is NULL, it considers all the
* children to find the first child which is compatible.
*
* @param parent The parent node under which to look.
* @param child The child node to start search from (exclusive). If NULL
* consider all children.
* @param compat The compatible string to find.
* @return The found node, or NULL.
*/
struct device_tree_node *
dt_find_next_compat_child(struct device_tree_node *parent,
struct device_tree_node *child,
const char *compat)
{
struct device_tree_node *next;
int ignore = 0;
if (child)
ignore = 1;
list_for_each(next, parent->children, list_node) {
if (ignore) {
if (child == next)
ignore = 0;
continue;
}
if (dt_check_compat_match(next, compat))
return next;
}
return NULL;
}
/*
* Find a node with matching property value, in the subtree of a parent node.
*
* @param parent The parent node under which to look.
* @param name The property name to look for.
* @param data The property value to look for.
* @param size The property size.
*/
struct device_tree_node *dt_find_prop_value(struct device_tree_node *parent,
const char *name, void *data,
size_t size)
{
struct device_tree_property *prop;
/* Check if parent itself has the required property value. */
list_for_each(prop, parent->properties, list_node) {
if (!strcmp(name, prop->prop.name)) {
size_t bytes = prop->prop.size;
const void *prop_data = prop->prop.data;
if (size != bytes)
break;
if (!memcmp(data, prop_data, size))
return parent;
break;
}
}
struct device_tree_node *child;
list_for_each(child, parent->children, list_node) {
struct device_tree_node *found = dt_find_prop_value(child, name,
data, size);
if (found)
return found;
}
return NULL;
}
/*
* Write an arbitrary sized big-endian integer into a pointer.
*
* @param dest Pointer to the DT property data buffer to write.
* @param src The integer to write (in CPU endianness).
* @param length the length of the destination integer in bytes.
*/
void dt_write_int(u8 *dest, u64 src, size_t length)
{
while (length--) {
dest[length] = (u8)src;
src >>= 8;
}
}
/*
* Delete a property by name in a given node if it exists.
*
* @param node The device tree node to operate on.
* @param name The name of the property to delete.
*/
void dt_delete_prop(struct device_tree_node *node, const char *name)
{
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (!strcmp(prop->prop.name, name)) {
list_remove(&prop->list_node);
return;
}
}
}
/*
* Add an arbitrary property to a node, or update it if it already exists.
*
* @param node The device tree node to add to.
* @param name The name of the new property.
* @param data The raw data blob to be stored in the property.
* @param size The size of data in bytes.
*/
void dt_add_bin_prop(struct device_tree_node *node, const char *name,
void *data, size_t size)
{
struct device_tree_property *prop;
list_for_each(prop, node->properties, list_node) {
if (!strcmp(prop->prop.name, name)) {
prop->prop.data = data;
prop->prop.size = size;
return;
}
}
prop = xzalloc(sizeof(*prop));
list_insert_after(&prop->list_node, &node->properties);
prop->prop.name = name;
prop->prop.data = data;
prop->prop.size = size;
}
/*
* Find given string property in a node and return its content.
*
* @param node The device tree node to search.
* @param name The name of the property.
* @return The found string, or NULL.
*/
const char *dt_find_string_prop(const struct device_tree_node *node,
const char *name)
{
const void *content;
size_t size;
dt_find_bin_prop(node, name, &content, &size);
return content;
}
/*
* Find given property in a node.
*
* @param node The device tree node to search.
* @param name The name of the property.
* @param data Pointer to return raw data blob in the property.
* @param size Pointer to return the size of data in bytes.
*/
void dt_find_bin_prop(const struct device_tree_node *node, const char *name,
const void **data, size_t *size)
{
struct device_tree_property *prop;
*data = NULL;
*size = 0;
list_for_each(prop, node->properties, list_node) {
if (!strcmp(prop->prop.name, name)) {
*data = prop->prop.data;
*size = prop->prop.size;
return;
}
}
}
/*
* Add a string property to a node, or update it if it already exists.
*
* @param node The device tree node to add to.
* @param name The name of the new property.
* @param str The zero-terminated string to be stored in the property.
*/
void dt_add_string_prop(struct device_tree_node *node, const char *name,
const char *str)
{
dt_add_bin_prop(node, name, (char *)str, strlen(str) + 1);
}
/*
* Add a 32-bit integer property to a node, or update it if it already exists.
*
* @param node The device tree node to add to.
* @param name The name of the new property.
* @param val The integer to be stored in the property.
*/
void dt_add_u32_prop(struct device_tree_node *node, const char *name, u32 val)
{
u32 *val_ptr = xmalloc(sizeof(val));
*val_ptr = htobe32(val);
dt_add_bin_prop(node, name, val_ptr, sizeof(*val_ptr));
}
/*
* Add a 64-bit integer property to a node, or update it if it already exists.
*
* @param node The device tree node to add to.
* @param name The name of the new property.
* @param val The integer to be stored in the property.
*/
void dt_add_u64_prop(struct device_tree_node *node, const char *name, u64 val)
{
u64 *val_ptr = xmalloc(sizeof(val));
*val_ptr = htobe64(val);
dt_add_bin_prop(node, name, val_ptr, sizeof(*val_ptr));
}
/*
* Add a 'reg' address list property to a node, or update it if it exists.
*
* @param node The device tree node to add to.
* @param regions Array of address values to be stored in the property.
* @param sizes Array of corresponding size values to 'addrs'.
* @param count Number of values in 'addrs' and 'sizes' (must be equal).
* @param addr_cells Value of #address-cells property valid for this node.
* @param size_cells Value of #size-cells property valid for this node.
*/
void dt_add_reg_prop(struct device_tree_node *node, u64 *addrs, u64 *sizes,
int count, u32 addr_cells, u32 size_cells)
{
int i;
size_t length = (addr_cells + size_cells) * sizeof(u32) * count;
u8 *data = xmalloc(length);
u8 *cur = data;
for (i = 0; i < count; i++) {
dt_write_int(cur, addrs[i], addr_cells * sizeof(u32));
cur += addr_cells * sizeof(u32);
dt_write_int(cur, sizes[i], size_cells * sizeof(u32));
cur += size_cells * sizeof(u32);
}
dt_add_bin_prop(node, "reg", data, length);
}
/*
* Fixups to apply to a kernel's device tree before booting it.
*/
struct list_node device_tree_fixups;
int dt_apply_fixups(struct device_tree *tree)
{
struct device_tree_fixup *fixup;
list_for_each(fixup, device_tree_fixups, list_node) {
assert(fixup->fixup);
if (fixup->fixup(fixup, tree))
return 1;
}
return 0;
}
int dt_set_bin_prop_by_path(struct device_tree *tree, const char *path,
void *data, size_t data_size, int create)
{
char *path_copy, *prop_name;
struct device_tree_node *dt_node;
path_copy = strdup(path);
if (!path_copy) {
printk(BIOS_ERR, "Failed to allocate a copy of path %s\n",
path);
return 1;
}
prop_name = strrchr(path_copy, '/');
if (!prop_name) {
free(path_copy);
printk(BIOS_ERR, "Path %s does not include '/'\n", path);
return 1;
}
*prop_name++ = '\0'; /* Separate path from the property name. */
dt_node = dt_find_node_by_path(tree, path_copy, NULL,
NULL, create);
if (!dt_node) {
printk(BIOS_ERR, "Failed to %s %s in the device tree\n",
create ? "create" : "find", path_copy);
free(path_copy);
return 1;
}
dt_add_bin_prop(dt_node, prop_name, data, data_size);
free(path_copy);
return 0;
}
/*
* Prepare the /reserved-memory/ node.
*
* Technically, this can be called more than one time, to init and/or retrieve
* the node. But dt_add_u32_prop() may leak a bit of memory if you do.
*
* @tree: Device tree to add/retrieve from.
* @return: The /reserved-memory/ node (or NULL, if error).
*/
struct device_tree_node *dt_init_reserved_memory_node(struct device_tree *tree)
{
struct device_tree_node *reserved;
u32 addr = 0, size = 0;
reserved = dt_find_node_by_path(tree, "/reserved-memory", &addr,
&size, 1);
if (!reserved)
return NULL;
/* Binding doc says this should have the same #{address,size}-cells as
the root. */
dt_add_u32_prop(reserved, "#address-cells", addr);
dt_add_u32_prop(reserved, "#size-cells", size);
/* Binding doc says this should be empty (1:1 mapping from root). */
dt_add_bin_prop(reserved, "ranges", NULL, 0);
return reserved;
}
/*
* Increment a single phandle in prop at a given offset by a given adjustment.
*
* @param prop Property whose phandle should be adjusted.
* @param adjustment Value that should be added to the existing phandle.
* @param offset Byte offset of the phandle in the property data.
*
* @return New phandle value, or 0 on error.
*/
static uint32_t dt_adjust_phandle(struct device_tree_property *prop,
uint32_t adjustment, uint32_t offset)
{
if (offset + 4 > prop->prop.size)
return 0;
uint32_t phandle = be32dec(prop->prop.data + offset);
if (phandle == 0 ||
phandle == FDT_PHANDLE_ILLEGAL ||
phandle == 0xffffffff)
return 0;
phandle += adjustment;
if (phandle >= FDT_PHANDLE_ILLEGAL)
return 0;
be32enc(prop->prop.data + offset, phandle);
return phandle;
}
/*
* Adjust all phandles in subtree by adding a new base offset.
*
* @param node Root node of the subtree to work on.
* @param base New phandle base to be added to all phandles.
*
* @return New highest phandle in the subtree, or 0 on error.
*/
static uint32_t dt_adjust_all_phandles(struct device_tree_node *node,
uint32_t base)
{
uint32_t new_max = MAX(base, 1); /* make sure we don't return 0 */
struct device_tree_property *prop;
struct device_tree_node *child;
if (!node)
return new_max;
list_for_each(prop, node->properties, list_node)
if (dt_prop_is_phandle(prop)) {
node->phandle = dt_adjust_phandle(prop, base, 0);
if (!node->phandle)
return 0;
new_max = MAX(new_max, node->phandle);
} /* no break -- can have more than one phandle prop */
list_for_each(child, node->children, list_node)
new_max = MAX(new_max, dt_adjust_all_phandles(child, base));
return new_max;
}
/*
* Apply a /__local_fixup__ subtree to the corresponding overlay subtree.
*
* @param node Root node of the overlay subtree to fix up.
* @param node Root node of the /__local_fixup__ subtree.
* @param base Adjustment that was added to phandles in the overlay.
*
* @return 0 on success, -1 on error.
*/
static int dt_fixup_locals(struct device_tree_node *node,
struct device_tree_node *fixup, uint32_t base)
{
struct device_tree_property *prop;
struct device_tree_property *fixup_prop;
struct device_tree_node *child;
struct device_tree_node *fixup_child;
int i;
/*
* For local fixups the /__local_fixup__ subtree contains the same node
* hierarchy as the main tree we're fixing up. Each property contains
* the fixup offsets for the respective property in the main tree. For
* each property in the fixup node, find the corresponding property in
* the base node and apply fixups to all offsets it specifies.
*/
list_for_each(fixup_prop, fixup->properties, list_node) {
struct device_tree_property *base_prop = NULL;
list_for_each(prop, node->properties, list_node)
if (!strcmp(prop->prop.name, fixup_prop->prop.name)) {
base_prop = prop;
break;
}
/* We should always find a corresponding base prop for a fixup,
and fixup props contain a list of 32-bit fixup offsets. */
if (!base_prop || fixup_prop->prop.size % sizeof(uint32_t))
return -1;
for (i = 0; i < fixup_prop->prop.size; i += sizeof(uint32_t))
if (!dt_adjust_phandle(base_prop, base, be32dec(
fixup_prop->prop.data + i)))
return -1;
}
/* Now recursively descend both the base tree and the /__local_fixups__
subtree in sync to apply all fixups. */
list_for_each(fixup_child, fixup->children, list_node) {
struct device_tree_node *base_child = NULL;
list_for_each(child, node->children, list_node)
if (!strcmp(child->name, fixup_child->name)) {
base_child = child;
break;
}
/* All fixup nodes should have a corresponding base node. */
if (!base_child)
return -1;
if (dt_fixup_locals(base_child, fixup_child, base) < 0)
return -1;
}
return 0;
}
/*
* Update all /__symbols__ properties in an overlay that start with
* "/fragment@X/__overlay__" with corresponding path prefix in the base tree.
*
* @param symbols /__symbols__ done to update.
* @param fragment /fragment@X node that references to should be updated.
* @param base_path Path of base tree node that the fragment overlaid.
*/
static void dt_fix_symbols(struct device_tree_node *symbols,
struct device_tree_node *fragment,
const char *base_path)
{
struct device_tree_property *prop;
char buf[512]; /* Should be enough for maximum DT path length? */
char node_path[64]; /* easily enough for /fragment@XXXX/__overlay__ */
if (!symbols) /* If the overlay has no /__symbols__ node, we're done! */
return;
int len = snprintf(node_path, sizeof(node_path), "/%s/__overlay__",
fragment->name);
list_for_each(prop, symbols->properties, list_node)
if (!strncmp(prop->prop.data, node_path, len)) {
prop->prop.size = snprintf(buf, sizeof(buf), "%s%s",
base_path, (char *)prop->prop.data + len) + 1;
free(prop->prop.data);
prop->prop.data = strdup(buf);
}
}
/*
* Fix up overlay according to a property in /__fixup__. If the fixed property
* is a /fragment@X:target, also update /__symbols__ references to fragment.
*
* @params overlay Overlay to fix up.
* @params fixup /__fixup__ property.
* @params phandle phandle value to insert where the fixup points to.
* @params base_path Path to the base DT node that the fixup points to.
* @params overlay_symbols /__symbols__ node of the overlay.
*
* @return 0 on success, -1 on error.
*/
static int dt_fixup_external(struct device_tree *overlay,
struct device_tree_property *fixup,
uint32_t phandle, const char *base_path,
struct device_tree_node *overlay_symbols)
{
struct device_tree_property *prop;
/* External fixup properties are encoded as "<path>:<prop>:<offset>". */
char *entry = fixup->prop.data;
while ((void *)entry < fixup->prop.data + fixup->prop.size) {
/* okay to destroy fixup property value, won't need it again */
char *node_path = entry;
entry = strchr(node_path, ':');
if (!entry)
return -1;
*entry++ = '\0';
char *prop_name = entry;
entry = strchr(prop_name, ':');
if (!entry)
return -1;
*entry++ = '\0';
struct device_tree_node *ovl_node = dt_find_node_by_path(
overlay, node_path, NULL, NULL, 0);
if (!ovl_node || !isdigit(*entry))
return -1;
struct device_tree_property *ovl_prop = NULL;
list_for_each(prop, ovl_node->properties, list_node)
if (!strcmp(prop->prop.name, prop_name)) {
ovl_prop = prop;
break;
}
/* Move entry to first char after number, must be a '\0'. */
uint32_t offset = skip_atoi(&entry);
if (!ovl_prop || offset + 4 > ovl_prop->prop.size || entry[0])
return -1;
entry++; /* jump over '\0' to potential next fixup */
be32enc(ovl_prop->prop.data + offset, phandle);
/* If this is a /fragment@X:target property, update references
to this fragment in the overlay __symbols__ now. */
if (offset == 0 && !strcmp(prop_name, "target") &&
!strchr(node_path + 1, '/')) /* only toplevel nodes */
dt_fix_symbols(overlay_symbols, ovl_node, base_path);
}
return 0;
}
/*
* Apply all /__fixup__ properties in the overlay. This will destroy the
* property data in /__fixup__ and it should not be accessed again.
*
* @params tree Base device tree that the overlay updates.
* @params symbols /__symbols__ node of the base device tree.
* @params overlay Overlay to fix up.
* @params fixups /__fixup__ node in the overlay.
* @params overlay_symbols /__symbols__ node of the overlay.
*
* @return 0 on success, -1 on error.
*/
static int dt_fixup_all_externals(struct device_tree *tree,
struct device_tree_node *symbols,
struct device_tree *overlay,
struct device_tree_node *fixups,
struct device_tree_node *overlay_symbols)
{
struct device_tree_property *fix;
/* If we have any external fixups, base tree must have /__symbols__. */
if (!symbols)
return -1;
/*
* Unlike /__local_fixups__, /__fixups__ is not a whole subtree that
* mirrors the node hierarchy. It's just a directory of fixup properties
* that each directly contain all information necessary to apply them.
*/
list_for_each(fix, fixups->properties, list_node) {
/* The name of a fixup property is the label of the node we want
a property to phandle-reference. Look up in /__symbols__. */
const char *path = dt_find_string_prop(symbols, fix->prop.name);
if (!path)
return -1;
/* Find node the label pointed to figure out its phandle. */
struct device_tree_node *node = dt_find_node_by_path(tree, path,
NULL, NULL, 0);
if (!node)
return -1;
/* Write into the overlay property(s) pointing to that node. */
if (dt_fixup_external(overlay, fix, node->phandle,
path, overlay_symbols) < 0)
return -1;
}
return 0;
}
/*
* Copy all nodes and properties from one DT subtree into another. This is a
* shallow copy so both trees will point to the same property data afterwards.
*
* @params dst Destination subtree to copy into.
* @params src Source subtree to copy from.
* @params upd 1 to overwrite same-name properties, 0 to discard them.
*/
static void dt_copy_subtree(struct device_tree_node *dst,
struct device_tree_node *src, int upd)
{
struct device_tree_property *prop;
struct device_tree_property *src_prop;
list_for_each(src_prop, src->properties, list_node) {
if (dt_prop_is_phandle(src_prop) ||
!strcmp(src_prop->prop.name, "name")) {
printk(BIOS_DEBUG,
"WARNING: ignoring illegal overlay prop '%s'\n",
src_prop->prop.name);
continue;
}
struct device_tree_property *dst_prop = NULL;
list_for_each(prop, dst->properties, list_node)
if (!strcmp(prop->prop.name, src_prop->prop.name)) {
dst_prop = prop;
break;
}
if (dst_prop) {
if (!upd) {
printk(BIOS_DEBUG,
"WARNING: ignoring prop update '%s'\n",
src_prop->prop.name);
continue;
}
} else {
dst_prop = xzalloc(sizeof(*dst_prop));
list_insert_after(&dst_prop->list_node,
&dst->properties);
}
dst_prop->prop = src_prop->prop;
}
struct device_tree_node *node;
struct device_tree_node *src_node;
list_for_each(src_node, src->children, list_node) {
struct device_tree_node *dst_node = NULL;
list_for_each(node, dst->children, list_node)
if (!strcmp(node->name, src_node->name)) {
dst_node = node;
break;
}
if (!dst_node) {
dst_node = xzalloc(sizeof(*dst_node));
*dst_node = *src_node;
list_insert_after(&dst_node->list_node, &dst->children);
} else {
dt_copy_subtree(dst_node, src_node, upd);
}
}
}
/*
* Apply an overlay /fragment@X node to a base device tree.
*
* @param tree Base device tree.
* @param fragment /fragment@X node.
* @params overlay_symbols /__symbols__ node of the overlay.
*
* @return 0 on success, -1 on error.
*/
static int dt_import_fragment(struct device_tree *tree,
struct device_tree_node *fragment,
struct device_tree_node *overlay_symbols)
{
/* The actual overlaid nodes/props are in an __overlay__ child node. */
static const char *overlay_path[] = { "__overlay__", NULL };
struct device_tree_node *overlay = dt_find_node(fragment, overlay_path,
NULL, NULL, 0);
/* If it doesn't have an __overlay__ child, it's not a fragment. */
if (!overlay)
return 0;
/* Target node of the fragment can be given by path or by phandle. */
struct device_tree_property *prop;
struct device_tree_property *phandle = NULL;
struct device_tree_property *path = NULL;
list_for_each(prop, fragment->properties, list_node) {
if (!strcmp(prop->prop.name, "target")) {
phandle = prop;
break; /* phandle target has priority, stop looking */
}
if (!strcmp(prop->prop.name, "target-path"))
path = prop;
}
struct device_tree_node *target = NULL;
if (phandle) {
if (phandle->prop.size != sizeof(uint32_t))
return -1;
target = dt_find_node_by_phandle(tree->root,
be32dec(phandle->prop.data));
/* Symbols already updated as part of dt_fixup_external(). */
} else if (path) {
target = dt_find_node_by_path(tree, path->prop.data,
NULL, NULL, 0);
dt_fix_symbols(overlay_symbols, fragment, path->prop.data);
}
if (!target)
return -1;
dt_copy_subtree(target, overlay, 1);
return 0;
}
/*
* Apply a device tree overlay to a base device tree. This will
* destroy/incorporate the overlay data, so it should not be freed or reused.
* See dtc.git/Documentation/dt-object-internal.txt for overlay format details.
*
* @param tree Unflattened base device tree to add the overlay into.
* @param overlay Unflattened overlay device tree to apply to the base.
*
* @return 0 on success, -1 on error.
*/
int dt_apply_overlay(struct device_tree *tree, struct device_tree *overlay)
{
/*
* First, we need to make sure phandles inside the overlay don't clash
* with those in the base tree. We just define the highest phandle value
* in the base tree as the "phandle offset" for this overlay and
* increment all phandles in it by that value.
*/
uint32_t phandle_base = tree->max_phandle;
uint32_t new_max = dt_adjust_all_phandles(overlay->root, phandle_base);
if (!new_max) {
printk(BIOS_ERR, "invalid phandles in overlay\n");
return -1;
}
tree->max_phandle = new_max;
/* Now that we changed phandles in the overlay, we need to update any
nodes referring to them. Those are listed in /__local_fixups__. */
struct device_tree_node *local_fixups = dt_find_node_by_path(overlay,
"/__local_fixups__", NULL, NULL, 0);
if (local_fixups && dt_fixup_locals(overlay->root, local_fixups,
phandle_base) < 0) {
printk(BIOS_ERR, "invalid local fixups in overlay\n");
return -1;
}
/*
* Besides local phandle references (from nodes within the overlay to
* other nodes within the overlay), the overlay may also contain phandle
* references to the base tree. These are stored with invalid values and
* must be updated now. /__symbols__ contains a list of all labels in
* the base tree, and /__fixups__ describes all nodes in the overlay
* that contain external phandle references.
* We also take this opportunity to update all /fragment@X/__overlay__/
* prefixes in the overlay's /__symbols__ node to the correct path that
* the fragment will be placed in later, since this is the only step
* where we have all necessary information for that easily available.
*/
struct device_tree_node *symbols = dt_find_node_by_path(tree,
"/__symbols__", NULL, NULL, 0);
struct device_tree_node *fixups = dt_find_node_by_path(overlay,
"/__fixups__", NULL, NULL, 0);
struct device_tree_node *overlay_symbols = dt_find_node_by_path(overlay,
"/__symbols__", NULL, NULL, 0);
if (fixups && dt_fixup_all_externals(tree, symbols, overlay,
fixups, overlay_symbols) < 0) {
printk(BIOS_ERR, "cannot match external fixups from overlay\n");
return -1;
}
/* After all this fixing up, we can finally merge overlay into the tree
(one fragment at a time, because for some reason it's split up). */
struct device_tree_node *fragment;
list_for_each(fragment, overlay->root->children, list_node)
if (dt_import_fragment(tree, fragment, overlay_symbols) < 0) {
printk(BIOS_ERR, "bad DT fragment '%s'\n",
fragment->name);
return -1;
}
/*
* We need to also update /__symbols__ to include labels from this
* overlay, in case we want to load further overlays with external
* phandle references to it. If the base tree already has a /__symbols__
* we merge them together, otherwise we just insert the overlay's
* /__symbols__ node into the base tree root.
*/
if (overlay_symbols) {
if (symbols)
dt_copy_subtree(symbols, overlay_symbols, 0);
else
list_insert_after(&overlay_symbols->list_node,
&tree->root->children);
}
return 0;
}