mirror of
https://github.com/Qortal/Brooklyn.git
synced 2025-02-07 06:44:18 +00:00
2834 lines
80 KiB
C
2834 lines
80 KiB
C
/*
|
|
Copyright (c) 2016-2019 Raspberry Pi (Trading) Ltd.
|
|
All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions are met:
|
|
* Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
* Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
* Neither the name of the copyright holder nor the
|
|
names of its contributors may be used to endorse or promote products
|
|
derived from this software without specific prior written permission.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
|
|
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <libfdt.h>
|
|
#include <assert.h>
|
|
|
|
#include "dtoverlay.h"
|
|
|
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
|
|
|
typedef enum
|
|
{
|
|
FIXUP_ABSOLUTE,
|
|
FIXUP_RELATIVE
|
|
} fixup_type_t;
|
|
|
|
#define DTOVERRIDE_END 0
|
|
#define DTOVERRIDE_INTEGER 1
|
|
#define DTOVERRIDE_BOOLEAN 2
|
|
#define DTOVERRIDE_BOOLEAN_INV 3
|
|
#define DTOVERRIDE_STRING 4
|
|
#define DTOVERRIDE_OVERLAY 5
|
|
#define DTOVERRIDE_BYTE_STRING 6
|
|
|
|
static int dtoverlay_extract_override(const char *override_name,
|
|
char *override_value, int value_size,
|
|
int *phandle_ptr,
|
|
const char **datap, const char *dataendp,
|
|
const char **namep, int *namelenp,
|
|
int *offp, int *sizep);
|
|
|
|
static const char *dtoverlay_lookup_key(const char *lookup_string, const char *data_end,
|
|
const char *key, char *buf, int buf_len);
|
|
|
|
static int dtoverlay_set_node_name(DTBLOB_T *dtb, int node_off,
|
|
const char *name);
|
|
|
|
static void dtoverlay_stdio_logging(dtoverlay_logging_type_t type,
|
|
const char *fmt, va_list args);
|
|
|
|
#define phandle_debug if (0) dtoverlay_debug
|
|
|
|
static DTOVERLAY_LOGGING_FUNC *dtoverlay_logging_func = dtoverlay_stdio_logging;
|
|
static int dtoverlay_debug_enabled = 0;
|
|
static DTBLOB_T *overlay_map;
|
|
static const char *platform_name;
|
|
static int platform_name_len;
|
|
|
|
static int strmemcmp(const char *mem, int mem_len, const char *str)
|
|
{
|
|
int ret = strncmp(mem, str, mem_len);
|
|
if (ret == 0 && str[mem_len] != 0)
|
|
ret = 1;
|
|
return ret;
|
|
}
|
|
|
|
uint8_t dtoverlay_read_u8(const void *src, int off)
|
|
{
|
|
const unsigned char *p = src;
|
|
return (p[off + 0] << 0);
|
|
}
|
|
|
|
uint16_t dtoverlay_read_u16(const void *src, int off)
|
|
{
|
|
const unsigned char *p = src;
|
|
return (p[off + 0] << 8) | (p[off + 1] << 0);
|
|
}
|
|
|
|
uint32_t dtoverlay_read_u32(const void *src, int off)
|
|
{
|
|
const unsigned char *p = src;
|
|
return (p[off + 0] << 24) | (p[off + 1] << 16) |
|
|
(p[off + 2] << 8) | (p[off + 3] << 0);
|
|
}
|
|
|
|
uint64_t dtoverlay_read_u64(const void *src, int off)
|
|
{
|
|
const unsigned char *p = src;
|
|
return ((uint64_t)p[off + 0] << 56) | ((uint64_t)p[off + 1] << 48) |
|
|
((uint64_t)p[off + 2] << 40) | ((uint64_t)p[off + 3] << 32) |
|
|
(p[off + 4] << 24) | (p[off + 5] << 16) |
|
|
(p[off + 6] << 8) | (p[off + 7] << 0);
|
|
}
|
|
|
|
void dtoverlay_write_u8(void *dst, int off, uint32_t val)
|
|
{
|
|
unsigned char *p = dst;
|
|
p[off] = (val >> 0) & 0xff;
|
|
}
|
|
|
|
void dtoverlay_write_u16(void *dst, int off, uint32_t val)
|
|
{
|
|
unsigned char *p = dst;
|
|
p[off + 0] = (val >> 8) & 0xff;
|
|
p[off + 1] = (val >> 0) & 0xff;
|
|
}
|
|
|
|
void dtoverlay_write_u32(void *dst, int off, uint32_t val)
|
|
{
|
|
unsigned char *p = dst;
|
|
p[off + 0] = (val >> 24) & 0xff;
|
|
p[off + 1] = (val >> 16) & 0xff;
|
|
p[off + 2] = (val >> 8) & 0xff;
|
|
p[off + 3] = (val >> 0) & 0xff;
|
|
}
|
|
|
|
void dtoverlay_write_u64(void *dst, int off, uint64_t val)
|
|
{
|
|
unsigned char *p = dst;
|
|
p[off + 0] = (val >> 56) & 0xff;
|
|
p[off + 1] = (val >> 48) & 0xff;
|
|
p[off + 2] = (val >> 40) & 0xff;
|
|
p[off + 3] = (val >> 32) & 0xff;
|
|
p[off + 4] = (val >> 24) & 0xff;
|
|
p[off + 5] = (val >> 16) & 0xff;
|
|
p[off + 6] = (val >> 8) & 0xff;
|
|
p[off + 7] = (val >> 0) & 0xff;
|
|
}
|
|
|
|
// Returns the offset of the node indicated by the absolute path, creating
|
|
// it and any intermediates as necessary, or a negative error code.
|
|
int dtoverlay_create_node(DTBLOB_T *dtb, const char *node_path, int path_len)
|
|
{
|
|
const char *path_ptr;
|
|
const char *path_end;
|
|
int node_off = 0;
|
|
|
|
if (!path_len)
|
|
path_len = strlen(node_path);
|
|
|
|
path_ptr = node_path;
|
|
path_end = node_path + path_len;
|
|
|
|
if ((path_len > 0) && (path_ptr[path_len - 1] == '/'))
|
|
path_end--;
|
|
|
|
while (path_ptr < path_end)
|
|
{
|
|
const char *path_next;
|
|
int subnode_off;
|
|
|
|
if (*path_ptr != '/')
|
|
return -FDT_ERR_BADPATH;
|
|
|
|
// find the next path separator (or the end of the string)
|
|
path_ptr++;
|
|
for (path_next = path_ptr;
|
|
(path_next != path_end) && (*path_next != '/');
|
|
path_next++)
|
|
continue;
|
|
|
|
subnode_off = fdt_subnode_offset_namelen(dtb->fdt, node_off, path_ptr,
|
|
path_next - path_ptr);
|
|
if (subnode_off >= 0)
|
|
node_off = subnode_off;
|
|
else
|
|
node_off = fdt_add_subnode_namelen(dtb->fdt, node_off, path_ptr,
|
|
path_next - path_ptr);
|
|
if (node_off < 0)
|
|
break;
|
|
|
|
path_ptr = path_next;
|
|
}
|
|
|
|
if ((node_off >= 0) && (path_ptr != path_end))
|
|
return -FDT_ERR_BADPATH;
|
|
|
|
return node_off;
|
|
}
|
|
|
|
// Returns 0 on success, otherwise <0 error code
|
|
int dtoverlay_delete_node(DTBLOB_T *dtb, const char *node_path, int path_len)
|
|
{
|
|
int node_off = 0;
|
|
if (!path_len)
|
|
path_len = strlen(node_path);
|
|
|
|
dtoverlay_debug("delete_node(%.*s)", path_len, node_path);
|
|
node_off = fdt_path_offset_namelen(dtb->fdt, node_path, path_len);
|
|
if (node_off < 0)
|
|
return node_off;
|
|
return fdt_del_node(dtb->fdt, node_off);
|
|
}
|
|
|
|
// Returns the offset of the node indicated by the absolute path or a negative
|
|
// error code.
|
|
int dtoverlay_find_node(DTBLOB_T *dtb, const char *node_path, int path_len)
|
|
{
|
|
if (!path_len)
|
|
path_len = strlen(node_path);
|
|
return fdt_path_offset_namelen(dtb->fdt, node_path, path_len);
|
|
}
|
|
|
|
// Returns 0 on success, otherwise <0 error code
|
|
int dtoverlay_set_node_properties(DTBLOB_T *dtb, const char *node_path,
|
|
DTOVERLAY_PARAM_T *properties,
|
|
unsigned int num_properties)
|
|
{
|
|
int err = 0;
|
|
int node_off;
|
|
|
|
node_off = fdt_path_offset(dtb->fdt, node_path);
|
|
if (node_off < 0)
|
|
node_off = dtoverlay_create_node(dtb, node_path, 0);
|
|
if (node_off >= 0)
|
|
{
|
|
int i;
|
|
for (i = 0; (i < num_properties) && (err == 0); i++)
|
|
{
|
|
DTOVERLAY_PARAM_T *p;
|
|
|
|
p = properties + i;
|
|
err = fdt_setprop(dtb->fdt, node_off, p->param, p->b, p->len);
|
|
}
|
|
}
|
|
else
|
|
err = node_off;
|
|
return err;
|
|
}
|
|
|
|
struct dynstring
|
|
{
|
|
char *buf;
|
|
int size;
|
|
int len;
|
|
};
|
|
|
|
static void dynstring_init(struct dynstring *ds)
|
|
{
|
|
ds->size = 0;
|
|
ds->len = 0;
|
|
ds->buf = NULL;
|
|
}
|
|
|
|
static int dynstring_init_size(struct dynstring *ds, int initial_size)
|
|
{
|
|
if (initial_size < 32)
|
|
initial_size = 32;
|
|
ds->size = initial_size;
|
|
ds->len = 0;
|
|
ds->buf = malloc(initial_size);
|
|
if (!ds->buf)
|
|
{
|
|
dtoverlay_error(" out of memory");
|
|
return -FDT_ERR_NOSPACE;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int dynstring_set_size(struct dynstring *ds, int size)
|
|
{
|
|
if (size > ds->size)
|
|
{
|
|
size = (size * 5)/4; // Add a 25% headroom
|
|
ds->buf = realloc(ds->buf, size);
|
|
if (!ds->buf)
|
|
{
|
|
dtoverlay_error(" out of memory");
|
|
return -FDT_ERR_NOSPACE;
|
|
}
|
|
ds->size = size;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int dynstring_dup(struct dynstring *ds, const char *src, int len)
|
|
{
|
|
int err = 0;
|
|
|
|
if (!len)
|
|
len = strlen(src);
|
|
|
|
err = dynstring_set_size(ds, len + 1);
|
|
if (!err)
|
|
{
|
|
memcpy(ds->buf, src, len + 1);
|
|
ds->len = len;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int dynstring_patch(struct dynstring *ds, int pos, int width,
|
|
const char *src, int len)
|
|
{
|
|
int newlen = ds->len + (len - width);
|
|
int err = dynstring_set_size(ds, newlen + 1);
|
|
if (!err)
|
|
{
|
|
if (width != len)
|
|
{
|
|
// Move any data following the patch
|
|
memmove(ds->buf + pos + len, ds->buf + pos + width,
|
|
ds->len + 1 - (pos + width));
|
|
ds->len = newlen;
|
|
}
|
|
memcpy(ds->buf + pos, src, len);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int dynstring_grow(struct dynstring *ds)
|
|
{
|
|
return dynstring_set_size(ds, (3*ds->size)/2);
|
|
}
|
|
|
|
static void dynstring_free(struct dynstring *ds)
|
|
{
|
|
free(ds->buf);
|
|
dynstring_init(ds);
|
|
}
|
|
|
|
static int dtoverlay_set_node_name(DTBLOB_T *dtb, int node_off,
|
|
const char *name)
|
|
{
|
|
struct dynstring path_buf;
|
|
struct dynstring prop_buf;
|
|
char *old_path;
|
|
const char *old_name;
|
|
const char *fixup_nodes[] =
|
|
{
|
|
"/__fixups__",
|
|
"/__local_fixups__", // For old-style dtbos
|
|
"/__symbols__" // Just in case the kernel cares
|
|
};
|
|
int old_name_len;
|
|
int old_path_len; // All of it
|
|
int dir_len; // Excluding the node name, but with the trailling slash
|
|
int name_len;
|
|
int offset;
|
|
int fixup_idx;
|
|
int err = 0;
|
|
|
|
// Fixups and local-fixups both use node names, so this
|
|
// function must be patch them up when a node is renamed
|
|
// unless the fixups have already been applied.
|
|
// Calculating a node's name is expensive, so only do it if
|
|
// necessary. Since renaming a node can move things around,
|
|
// don't use node_off afterwards.
|
|
err = dynstring_init_size(&path_buf, 100);
|
|
if (err)
|
|
return err;
|
|
|
|
if (!dtb->fixups_applied)
|
|
{
|
|
while (1)
|
|
{
|
|
err = fdt_get_path(dtb->fdt, node_off, path_buf.buf, path_buf.size);
|
|
if (!err)
|
|
break;
|
|
if (err != -FDT_ERR_NOSPACE)
|
|
return err;
|
|
dynstring_grow(&path_buf);
|
|
}
|
|
}
|
|
old_path = path_buf.buf;
|
|
|
|
err = fdt_set_name(dtb->fdt, node_off, name);
|
|
if (err || dtb->fixups_applied)
|
|
goto clean_up;
|
|
|
|
// Find the node name in old_path
|
|
old_name = strrchr(old_path, '/');
|
|
assert(old_name);
|
|
if (!old_name)
|
|
return -FDT_ERR_INTERNAL;
|
|
old_name++;
|
|
old_name_len = strlen(old_name);
|
|
dir_len = old_name - old_path;
|
|
old_path_len = dir_len + old_name_len;
|
|
|
|
// Short-circuit the case where the name isn't changing
|
|
if (strcmp(name, old_name) == 0)
|
|
goto clean_up;
|
|
|
|
name_len = strlen(name);
|
|
|
|
// Search the fixups and symbols for the old path (including as
|
|
// a parent) and replace with the new name
|
|
|
|
dynstring_init(&prop_buf);
|
|
for (fixup_idx = 0; fixup_idx < ARRAY_SIZE(fixup_nodes); fixup_idx++)
|
|
{
|
|
int prop_off;
|
|
|
|
offset = fdt_path_offset(dtb->fdt, fixup_nodes[fixup_idx]);
|
|
if (offset > 0)
|
|
{
|
|
|
|
// Iterate through the properties
|
|
for (prop_off = fdt_first_property_offset(dtb->fdt, offset);
|
|
(prop_off >= 0) && (err == 0);
|
|
prop_off = fdt_next_property_offset(dtb->fdt, prop_off))
|
|
{
|
|
const char *prop_name;
|
|
const char *prop_val;
|
|
int prop_len;
|
|
int pos;
|
|
int changed = 0;
|
|
|
|
prop_val = fdt_getprop_by_offset(dtb->fdt, prop_off,
|
|
&prop_name, &prop_len);
|
|
err = dynstring_dup(&prop_buf, prop_val, prop_len);
|
|
if (err)
|
|
break;
|
|
|
|
// Scan each property for matching paths
|
|
pos = 0;
|
|
while (pos < prop_len)
|
|
{
|
|
if ((pos + old_path_len < prop_len) &&
|
|
(memcmp(prop_buf.buf + pos, old_path, old_path_len) == 0) &&
|
|
((prop_buf.buf[pos + old_path_len] == ':') ||
|
|
(prop_buf.buf[pos + old_path_len] == '/') ||
|
|
(prop_buf.buf[pos + old_path_len] == '\0')))
|
|
{
|
|
// Patch the string, replacing old name with new
|
|
err = dynstring_patch(&prop_buf, pos + dir_len, old_name_len,
|
|
name, name_len);
|
|
if (err)
|
|
break;
|
|
|
|
prop_len += name_len - old_name_len;
|
|
changed = 1;
|
|
}
|
|
pos += strlen(prop_buf.buf + pos) + 1;
|
|
}
|
|
|
|
if (!err && changed)
|
|
{
|
|
// Caution - may change offsets, but only by shuffling everything
|
|
// afterwards, i.e. the offset to this node or property does not
|
|
// change.
|
|
err = fdt_setprop(dtb->fdt, offset, prop_name, prop_buf.buf,
|
|
prop_len);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dynstring_free(&prop_buf);
|
|
|
|
if (err)
|
|
goto clean_up;
|
|
|
|
// Then look for a "/__local_fixups__<old_path>" node, and rename
|
|
// that as well.
|
|
offset = fdt_path_offset(dtb->fdt, "/__local_fixups__");
|
|
if (offset > 0)
|
|
{
|
|
const char *p, *end;
|
|
|
|
p = old_path;
|
|
end = old_path + old_path_len;
|
|
while (p < end)
|
|
{
|
|
const char *q;
|
|
|
|
while (*p == '/') {
|
|
p++;
|
|
if (p == end)
|
|
break;
|
|
}
|
|
q = memchr(p, '/', end - p);
|
|
if (! q)
|
|
q = end;
|
|
|
|
offset = fdt_subnode_offset_namelen(dtb->fdt, offset, p, q-p);
|
|
if (offset < 0)
|
|
break;
|
|
|
|
p = q;
|
|
}
|
|
|
|
if (offset > 0)
|
|
err = fdt_set_name(dtb->fdt, offset, name);
|
|
}
|
|
|
|
// __overrides__ don't need patching because nodes are identified
|
|
// using phandles, which are unaffected by renaming and resizing nodes.
|
|
|
|
clean_up:
|
|
dynstring_free(&path_buf);
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, otherwise <0 error code
|
|
int dtoverlay_create_prop_fragment(DTBLOB_T *dtb, int idx, int target_phandle,
|
|
const char *prop_name, const void *prop_data,
|
|
int prop_len)
|
|
{
|
|
char fragment_name[20];
|
|
int frag_off, ovl_off;
|
|
int ret;
|
|
snprintf(fragment_name, sizeof(fragment_name), "fragment-%u", idx);
|
|
frag_off = fdt_add_subnode(dtb->fdt, 0, fragment_name);
|
|
if (frag_off < 0)
|
|
return frag_off;
|
|
ret = fdt_setprop_u32(dtb->fdt, frag_off, "target", target_phandle);
|
|
if (ret < 0)
|
|
return ret;
|
|
ovl_off = fdt_add_subnode(dtb->fdt, frag_off, "__overlay__");
|
|
if (ovl_off < 0)
|
|
return ovl_off;
|
|
return fdt_setprop(dtb->fdt, ovl_off, prop_name, prop_data, prop_len);
|
|
}
|
|
|
|
// Returns 0 on success, otherwise <0 error code
|
|
static int dtoverlay_merge_fragment(DTBLOB_T *base_dtb, int target_off,
|
|
const DTBLOB_T *overlay_dtb,
|
|
int overlay_off, int depth)
|
|
{
|
|
int prop_off, subnode_off;
|
|
int err = 0;
|
|
|
|
if (dtoverlay_debug_enabled)
|
|
{
|
|
char base_path[DTOVERLAY_MAX_PATH];
|
|
char overlay_path[DTOVERLAY_MAX_PATH];
|
|
fdt_get_path(base_dtb->fdt, target_off, base_path, sizeof(base_path));
|
|
fdt_get_path(overlay_dtb->fdt, overlay_off, overlay_path,
|
|
sizeof(overlay_path));
|
|
|
|
dtoverlay_debug("merge_fragment(%s,%s)", base_path,
|
|
overlay_path);
|
|
}
|
|
|
|
// Merge each property of the node
|
|
for (prop_off = fdt_first_property_offset(overlay_dtb->fdt, overlay_off);
|
|
(prop_off >= 0) && (err == 0);
|
|
prop_off = fdt_next_property_offset(overlay_dtb->fdt, prop_off))
|
|
{
|
|
const char *prop_name;
|
|
const void *prop_val;
|
|
int prop_len;
|
|
struct fdt_property *target_prop;
|
|
int target_len;
|
|
|
|
prop_val = fdt_getprop_by_offset(overlay_dtb->fdt, prop_off,
|
|
&prop_name, &prop_len);
|
|
|
|
/* Skip these system properties (only phandles in the first level) */
|
|
if ((strcmp(prop_name, "name") == 0) ||
|
|
((depth == 0) && ((strcmp(prop_name, "phandle") == 0) ||
|
|
(strcmp(prop_name, "linux,phandle") == 0))))
|
|
continue;
|
|
|
|
dtoverlay_debug(" +prop(%s)", prop_name);
|
|
|
|
if ((strcmp(prop_name, "bootargs") == 0) &&
|
|
((target_prop = fdt_get_property_w(base_dtb->fdt, target_off, prop_name, &target_len)) != NULL) &&
|
|
(target_len > 0) && *target_prop->data)
|
|
{
|
|
target_prop->data[target_len - 1] = ' ';
|
|
err = fdt_appendprop(base_dtb->fdt, target_off, prop_name, prop_val, prop_len);
|
|
}
|
|
else
|
|
err = fdt_setprop(base_dtb->fdt, target_off, prop_name, prop_val, prop_len);
|
|
}
|
|
|
|
// Merge each subnode of the node
|
|
for (subnode_off = fdt_first_subnode(overlay_dtb->fdt, overlay_off);
|
|
(subnode_off >= 0) && (err == 0);
|
|
subnode_off = fdt_next_subnode(overlay_dtb->fdt, subnode_off))
|
|
{
|
|
const char *subnode_name;
|
|
int name_len;
|
|
int subtarget_off;
|
|
|
|
subnode_name = fdt_get_name(overlay_dtb->fdt, subnode_off, &name_len);
|
|
|
|
subtarget_off = fdt_subnode_offset_namelen(base_dtb->fdt, target_off,
|
|
subnode_name, name_len);
|
|
if (subtarget_off < 0)
|
|
subtarget_off = fdt_add_subnode_namelen(base_dtb->fdt, target_off,
|
|
subnode_name, name_len);
|
|
|
|
if (subtarget_off >= 0)
|
|
{
|
|
err = dtoverlay_merge_fragment(base_dtb, subtarget_off,
|
|
overlay_dtb, subnode_off,
|
|
depth + 1);
|
|
}
|
|
else
|
|
{
|
|
err = subtarget_off;
|
|
}
|
|
}
|
|
|
|
dtoverlay_debug("merge_fragment() end");
|
|
|
|
return err;
|
|
}
|
|
|
|
static int dtoverlay_phandle_relocate(DTBLOB_T *dtb, int node_off,
|
|
const char *prop_name,
|
|
uint32_t phandle_increment)
|
|
{
|
|
int len;
|
|
const fdt32_t *prop_val = fdt_getprop(dtb->fdt, node_off, prop_name, &len);
|
|
int err = 0; // The absence of the property is not an error
|
|
|
|
if (prop_val)
|
|
{
|
|
uint32_t phandle;
|
|
|
|
if (len < 4)
|
|
{
|
|
dtoverlay_error("%s property too small", prop_name);
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
}
|
|
|
|
phandle = fdt32_to_cpu(*prop_val) + phandle_increment;
|
|
phandle_debug(" phandle_relocate %d->%d", fdt32_to_cpu(*prop_val), phandle);
|
|
|
|
err = fdt_setprop_inplace_u32(dtb->fdt, node_off, prop_name, phandle);
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, or an FDT error code
|
|
static int dtoverlay_apply_fixups(DTBLOB_T *dtb, const char *fixups_stringlist,
|
|
uint32_t phandle, fixup_type_t type)
|
|
{
|
|
// The fixups arrive as a sequence of NUL-terminated strings, of the form:
|
|
// "path:property:offset"
|
|
// Use an empty string as an end marker, since:
|
|
// 1) all tags begin 0x00 0x00 0x00,
|
|
// 2) all string properties must be followed by a tag,
|
|
// 3) an empty string is not a valid fixup, and
|
|
// 4) the code is simpler as a result.
|
|
|
|
const char *fixup = fixups_stringlist;
|
|
|
|
while (fixup[0])
|
|
{
|
|
const char *prop_name, *offset_str;
|
|
char *offset_end;
|
|
const void *prop_ptr;
|
|
int prop_len;
|
|
int node_off;
|
|
unsigned long offset;
|
|
uint32_t patch;
|
|
|
|
prop_name = strchr(fixup, ':');
|
|
if (!prop_name)
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
prop_name++;
|
|
|
|
offset_str = strchr(prop_name, ':');
|
|
if (!offset_str)
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
offset_str++;
|
|
|
|
offset = strtoul(offset_str, &offset_end, 10);
|
|
if ((offset_end == offset_str) || (offset_end[0] != 0))
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
|
|
node_off = fdt_path_offset_namelen(dtb->fdt, fixup, prop_name - 1 - fixup);
|
|
if (node_off < 0)
|
|
return node_off;
|
|
|
|
prop_ptr = fdt_getprop_namelen(dtb->fdt, node_off, prop_name,
|
|
offset_str - 1 - prop_name, &prop_len);
|
|
if (!prop_ptr)
|
|
return prop_len;
|
|
if (offset > (prop_len - 4))
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
|
|
// Now apply the patch. Yes, prop_ptr is a const void *, but the
|
|
// alternative (copying the whole property, patching, then updating as
|
|
// a whole) is ridiculous.
|
|
if (type == FIXUP_RELATIVE)
|
|
{
|
|
patch = phandle + dtoverlay_read_u32(prop_ptr, offset);
|
|
phandle_debug(" phandle fixup %d+%d->%d", phandle, patch - phandle, patch);
|
|
}
|
|
else
|
|
{
|
|
patch = phandle;
|
|
phandle_debug(" phandle ref '%s'->%d", prop_name, patch);
|
|
}
|
|
|
|
dtoverlay_write_u32((void *)prop_ptr, offset, patch);
|
|
|
|
fixup = offset_end + 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// Returns 0 on success, or an FDT error code
|
|
static int dtoverlay_apply_fixups_node(DTBLOB_T *dtb, int fix_off,
|
|
int target_off, uint32_t phandle_offset)
|
|
{
|
|
// The fixups are arranged as a subtree mirroring the structure of the
|
|
// overall tree. Walk this tree in order. Each property is an array of cells
|
|
// containing offsets to patch within the corresponding node/property of
|
|
// the target tree.
|
|
int err = 0;
|
|
int prop_off;
|
|
int subfix_off;
|
|
|
|
// Merge each property of the node
|
|
for (prop_off = fdt_first_property_offset(dtb->fdt, fix_off);
|
|
(prop_off >= 0) && (err == 0);
|
|
prop_off = fdt_next_property_offset(dtb->fdt, prop_off))
|
|
{
|
|
const char *prop_name;
|
|
const void *prop_val;
|
|
int prop_len;
|
|
void *target_ptr;
|
|
int target_len;
|
|
int off;
|
|
|
|
prop_val = fdt_getprop_by_offset(dtb->fdt, prop_off,
|
|
&prop_name, &prop_len);
|
|
if (!prop_val)
|
|
return -FDT_ERR_INTERNAL;
|
|
|
|
target_ptr = fdt_getprop_w(dtb->fdt, target_off, prop_name, &target_len);
|
|
if (!target_ptr)
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
|
|
for (off = 0; (off + 4) <= prop_len; off += 4)
|
|
{
|
|
uint32_t patch;
|
|
int patch_offset = dtoverlay_read_u32(prop_val, off);
|
|
if ((patch_offset + 4) > target_len)
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
|
|
patch = phandle_offset + dtoverlay_read_u32(target_ptr, patch_offset);
|
|
phandle_debug(" phandle fixup %d+%d->%d", phandle_offset, patch - phandle_offset, patch);
|
|
|
|
dtoverlay_write_u32(target_ptr, patch_offset, patch);
|
|
}
|
|
}
|
|
|
|
// Merge each subnode of the node
|
|
for (subfix_off = fdt_first_subnode(dtb->fdt, fix_off);
|
|
(subfix_off >= 0) && (err == 0);
|
|
subfix_off = fdt_next_subnode(dtb->fdt, subfix_off))
|
|
{
|
|
const char *subnode_name;
|
|
int name_len;
|
|
int subtarget_off;
|
|
|
|
subnode_name = fdt_get_name(dtb->fdt, subfix_off, &name_len);
|
|
|
|
subtarget_off = fdt_subnode_offset_namelen(dtb->fdt, target_off,
|
|
subnode_name, name_len);
|
|
|
|
if (subtarget_off >= 0)
|
|
{
|
|
err = dtoverlay_apply_fixups_node(dtb, subfix_off, subtarget_off,
|
|
phandle_offset);
|
|
}
|
|
else
|
|
{
|
|
err = subtarget_off;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, or a negative FDT error.
|
|
static int dtoverlay_resolve_phandles(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb)
|
|
{
|
|
int local_fixups_off;
|
|
int node_off;
|
|
int err = 0;
|
|
|
|
// First find and update the phandles in the overlay
|
|
|
|
for (node_off = 0;
|
|
node_off >= 0;
|
|
node_off = fdt_next_node(overlay_dtb->fdt, node_off, NULL))
|
|
{
|
|
dtoverlay_phandle_relocate(overlay_dtb, node_off, "phandle",
|
|
base_dtb->max_phandle);
|
|
dtoverlay_phandle_relocate(overlay_dtb, node_off, "linux,phandle",
|
|
base_dtb->max_phandle);
|
|
}
|
|
|
|
local_fixups_off = fdt_path_offset(overlay_dtb->fdt, "/__local_fixups__");
|
|
if (local_fixups_off >= 0)
|
|
{
|
|
const char *fixups_stringlist;
|
|
|
|
// Update the references to local phandles using the local fixups.
|
|
// The property name is "fixup".
|
|
// The value is a NUL-separated stringlist of descriptors of the form:
|
|
// path:property:offset
|
|
fixups_stringlist =
|
|
fdt_getprop(overlay_dtb->fdt, local_fixups_off, "fixup", &err);
|
|
if (fixups_stringlist)
|
|
{
|
|
// Relocate the overlay phandle references
|
|
err = dtoverlay_apply_fixups(overlay_dtb, fixups_stringlist,
|
|
base_dtb->max_phandle, FIXUP_RELATIVE);
|
|
}
|
|
else
|
|
{
|
|
err = dtoverlay_apply_fixups_node(overlay_dtb, local_fixups_off,
|
|
0, base_dtb->max_phandle);
|
|
}
|
|
if (err < 0)
|
|
{
|
|
dtoverlay_error("error applying local fixups");
|
|
return err;
|
|
}
|
|
}
|
|
|
|
overlay_dtb->max_phandle += base_dtb->max_phandle;
|
|
phandle_debug(" +overlay max phandle +%d -> %d", base_dtb->max_phandle, overlay_dtb->max_phandle);
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, or an FDT error code
|
|
static int dtoverlay_resolve_fixups(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb)
|
|
{
|
|
int fixups_off;
|
|
int err = 0;
|
|
|
|
fixups_off = fdt_path_offset(overlay_dtb->fdt, "/__fixups__");
|
|
|
|
if (fixups_off >= 0)
|
|
{
|
|
int fixup_off, symbols_off = -1;
|
|
|
|
fixup_off = fdt_first_property_offset(overlay_dtb->fdt, fixups_off);
|
|
|
|
if (fixup_off >= 0)
|
|
{
|
|
// Find the symbols, which will be needed to resolve the fixups
|
|
symbols_off = fdt_path_offset(base_dtb->fdt, "/__symbols__");
|
|
|
|
if (symbols_off < 0)
|
|
{
|
|
dtoverlay_error("no symbols found");
|
|
return -FDT_ERR_NOTFOUND;
|
|
}
|
|
}
|
|
|
|
for (;
|
|
fixup_off >= 0;
|
|
fixup_off = fdt_next_property_offset(overlay_dtb->fdt, fixup_off))
|
|
{
|
|
const char *fixups_stringlist, *symbol_name, *target_path;
|
|
const char *ref_type;
|
|
int target_off;
|
|
uint32_t target_phandle;
|
|
|
|
// The property name identifies a symbol (or alias) in the base.
|
|
// The value is a comma-separated list of descriptors of the form:
|
|
// path:property:offset
|
|
fixups_stringlist = fdt_getprop_by_offset(overlay_dtb->fdt, fixup_off,
|
|
&symbol_name, &err);
|
|
if (!fixups_stringlist)
|
|
{
|
|
dtoverlay_error("__fixups__ are borked");
|
|
break;
|
|
}
|
|
|
|
// 1) Find the target node.
|
|
if (symbol_name[0] == '/')
|
|
{
|
|
/* This is a new-style path reference */
|
|
target_path = symbol_name;
|
|
ref_type = "path";
|
|
}
|
|
else
|
|
{
|
|
target_path = fdt_getprop(base_dtb->fdt, symbols_off, symbol_name,
|
|
&err);
|
|
if (!target_path)
|
|
{
|
|
dtoverlay_error("can't find symbol '%s'", symbol_name);
|
|
break;
|
|
}
|
|
|
|
ref_type = "symbol";
|
|
}
|
|
|
|
target_off = fdt_path_offset(base_dtb->fdt, target_path);
|
|
if (target_off < 0)
|
|
{
|
|
dtoverlay_error("%s '%s' is invalid", ref_type, symbol_name);
|
|
err = target_off;
|
|
break;
|
|
}
|
|
|
|
// 2) Ensure that the target node has a phandle.
|
|
target_phandle = fdt_get_phandle(base_dtb->fdt, target_off);
|
|
if (!target_phandle)
|
|
{
|
|
// It doesn't, so give it one
|
|
fdt32_t temp;
|
|
target_phandle = ++base_dtb->max_phandle;
|
|
temp = cpu_to_fdt32(target_phandle);
|
|
|
|
err = fdt_setprop(base_dtb->fdt, target_off, "phandle",
|
|
&temp, 4);
|
|
|
|
if (err != 0)
|
|
{
|
|
dtoverlay_error("failed to add a phandle");
|
|
break;
|
|
}
|
|
phandle_debug(" phandle '%s'->%d", target_path, target_phandle);
|
|
|
|
// The symbols may have moved, so recalculate
|
|
symbols_off = fdt_path_offset(base_dtb->fdt, "/__symbols__");
|
|
}
|
|
|
|
// Now apply the valid target_phandle to the items in the fixup string
|
|
|
|
err = dtoverlay_apply_fixups(overlay_dtb, fixups_stringlist,
|
|
target_phandle, FIXUP_ABSOLUTE);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int dtoverlay_get_target_offset(DTBLOB_T *base_dtb,
|
|
DTBLOB_T *overlay_dtb,
|
|
int frag_off)
|
|
{
|
|
const char *target_path;
|
|
int target_off;
|
|
int len;
|
|
|
|
target_path = fdt_getprop(overlay_dtb->fdt, frag_off, "target-path", &len);
|
|
if (target_path)
|
|
{
|
|
if (!base_dtb)
|
|
return -FDT_ERR_NOTFOUND;
|
|
if (len && (target_path[len - 1] == '\0'))
|
|
len--;
|
|
target_off = fdt_path_offset_namelen(base_dtb->fdt, target_path, len);
|
|
if (target_off < 0)
|
|
{
|
|
dtoverlay_error("invalid target-path '%.*s'", len, target_path);
|
|
return NON_FATAL(target_off);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const void *target_prop;
|
|
int phandle;
|
|
|
|
target_prop = fdt_getprop(overlay_dtb->fdt, frag_off, "target", &len);
|
|
if (!target_prop)
|
|
{
|
|
dtoverlay_error("no target or target-path");
|
|
return NON_FATAL(len);
|
|
}
|
|
|
|
if (len != 4)
|
|
return NON_FATAL(FDT_ERR_BADSTRUCTURE);
|
|
|
|
phandle = fdt32_to_cpu(*(fdt32_t *)target_prop);
|
|
if (!base_dtb)
|
|
{
|
|
if (phandle < 0 || phandle > overlay_dtb->max_phandle)
|
|
return -FDT_ERR_NOTFOUND;
|
|
return fdt_node_offset_by_phandle(overlay_dtb->fdt, phandle);
|
|
}
|
|
|
|
target_off =
|
|
fdt_node_offset_by_phandle(base_dtb->fdt, phandle);
|
|
if (target_off < 0)
|
|
{
|
|
dtoverlay_error("invalid target (phandle %d)", phandle);
|
|
return NON_FATAL(target_off);
|
|
}
|
|
}
|
|
|
|
return target_off;
|
|
}
|
|
|
|
// Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors
|
|
int dtoverlay_merge_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb)
|
|
{
|
|
// Merge each fragment node
|
|
int frag_off;
|
|
int frag_idx;
|
|
int err = 0;
|
|
int overlay_size = fdt_totalsize(overlay_dtb->fdt);
|
|
void *overlay_copy = NULL;
|
|
|
|
dtoverlay_filter_symbols(overlay_dtb);
|
|
|
|
for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), frag_idx = 0;
|
|
frag_off >= 0;
|
|
frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), frag_idx++)
|
|
{
|
|
const char *node_name;
|
|
const char *frag_name;
|
|
int target_off, overlay_off;
|
|
DTBLOB_T clone_dtb;
|
|
int idx;
|
|
|
|
node_name = fdt_get_name(overlay_dtb->fdt, frag_off, NULL);
|
|
|
|
if (strncmp(node_name, "fragment@", 9) != 0 &&
|
|
strncmp(node_name, "fragment-", 9) != 0)
|
|
continue;
|
|
|
|
frag_name = node_name + 9;
|
|
|
|
// Find the target and overlay nodes
|
|
overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__");
|
|
if (overlay_off < 0)
|
|
{
|
|
if (fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__dormant__") >= 0)
|
|
dtoverlay_debug("fragment %s disabled", frag_name);
|
|
else
|
|
dtoverlay_error("no overlay in fragment %s", frag_name);
|
|
continue;
|
|
}
|
|
|
|
target_off = dtoverlay_get_target_offset(NULL, overlay_dtb, frag_off);
|
|
|
|
if (target_off < 0)
|
|
continue;
|
|
|
|
// Merge the fragment with the overlay
|
|
// We can't just call dtoverlay_merge_fragment with the overlay_dtb
|
|
// as source and destination because the source is not expected to
|
|
// change. Instead, clone the overlay, apply the fragment, then switch.
|
|
|
|
if (!overlay_copy)
|
|
{
|
|
overlay_copy = malloc(overlay_size);
|
|
if (!overlay_copy)
|
|
{
|
|
err = -FDT_ERR_NOSPACE;
|
|
break;
|
|
}
|
|
}
|
|
memcpy(overlay_copy, overlay_dtb->fdt, overlay_size);
|
|
memcpy(&clone_dtb, overlay_dtb, sizeof(DTBLOB_T));
|
|
clone_dtb.fdt = overlay_copy;
|
|
err = dtoverlay_merge_fragment(&clone_dtb, target_off, overlay_dtb,
|
|
overlay_off, 0);
|
|
if (err)
|
|
break;
|
|
// Swap the buffers
|
|
{
|
|
void *temp = overlay_dtb->fdt;
|
|
overlay_dtb->fdt = overlay_copy;
|
|
overlay_copy = temp;
|
|
}
|
|
|
|
// Disable this fragment (and resync with the changed overlay)
|
|
for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), idx = 0;
|
|
idx < frag_idx;
|
|
frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), idx++)
|
|
continue;
|
|
|
|
overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__");
|
|
if (overlay_off >= 0)
|
|
dtoverlay_set_node_name(overlay_dtb, overlay_off, "__dormant__");
|
|
// As the new name is the same length, the offsets are still valid
|
|
}
|
|
|
|
if (overlay_copy)
|
|
free(overlay_copy);
|
|
|
|
if (err || !base_dtb)
|
|
goto no_base_dtb;
|
|
|
|
for (frag_off = fdt_first_subnode(overlay_dtb->fdt, 0), frag_idx = 0;
|
|
frag_off >= 0;
|
|
frag_off = fdt_next_subnode(overlay_dtb->fdt, frag_off), frag_idx++)
|
|
{
|
|
const char *node_name;
|
|
const char *frag_name;
|
|
int target_off, overlay_off;
|
|
|
|
node_name = fdt_get_name(overlay_dtb->fdt, frag_off, NULL);
|
|
|
|
if (strcmp(node_name, "__symbols__") == 0)
|
|
{
|
|
/* At this point, only exported symbols should remain */
|
|
int sym_off;
|
|
|
|
fdt_for_each_property_offset(sym_off, overlay_dtb->fdt, frag_off)
|
|
{
|
|
char target_path[DTOVERLAY_MAX_PATH];
|
|
const char *sym_name = NULL;
|
|
const char *sym_path;
|
|
const char *p;
|
|
int sym_len;
|
|
int sym_frag_off;
|
|
int target_path_len;
|
|
int new_path_len;
|
|
int base_symbols;
|
|
|
|
sym_path = fdt_getprop_by_offset(overlay_dtb->fdt, sym_off,
|
|
&sym_name, &sym_len);
|
|
if (!sym_path)
|
|
break;
|
|
|
|
/* Rebase the symbol path so that
|
|
* /fragment@0/__overlay__/<something>
|
|
* becomes
|
|
* <path-to-fragment-target>/<something>
|
|
*/
|
|
|
|
/* Skip non-overlay symbols
|
|
* Overlay symbol paths should be of the form:
|
|
* /<fragment>/__overlay__/<something>
|
|
* It doesn't actually matter what <fragment> is.
|
|
*/
|
|
if (sym_path[0] != '/')
|
|
continue;
|
|
|
|
p = strchr(sym_path + 1, '/');
|
|
if (!p || strncmp(p + 1, "__overlay__/", 12) != 0)
|
|
continue;
|
|
|
|
/* Find the offset to the fragment */
|
|
sym_frag_off = dtoverlay_find_node(overlay_dtb, sym_path,
|
|
p - sym_path);
|
|
|
|
p += 12; /* p points to /<something> */
|
|
|
|
/* Locate the path to the fragment target */
|
|
target_off = dtoverlay_get_target_offset(base_dtb, overlay_dtb,
|
|
sym_frag_off);
|
|
if (target_off < 0)
|
|
return target_off;
|
|
|
|
err = fdt_get_path(base_dtb->fdt, target_off,
|
|
target_path, sizeof(target_path));
|
|
if (err)
|
|
{
|
|
dtoverlay_error("bad target path for %s", sym_path);
|
|
break;
|
|
}
|
|
|
|
/* Append the fragment-relative path to the target path */
|
|
target_path_len = strlen(target_path);
|
|
if (strcmp(target_path, "/") == 0)
|
|
p++; // Avoid a '//' if the target is the root
|
|
new_path_len = target_path_len + (sym_path + sym_len - p);
|
|
if (new_path_len >= sizeof(target_path))
|
|
{
|
|
dtoverlay_error("exported symbol path too long for %s", sym_path);
|
|
err = -FDT_ERR_NOSPACE;
|
|
break;
|
|
}
|
|
strcpy(target_path + target_path_len, p);
|
|
|
|
base_symbols = fdt_path_offset(base_dtb->fdt, "/__symbols__");
|
|
fdt_setprop(base_dtb->fdt, base_symbols,
|
|
sym_name, target_path, new_path_len);
|
|
dtoverlay_debug("set label '%s' path to '%s'",
|
|
sym_name, target_path);
|
|
}
|
|
continue;
|
|
}
|
|
else if (strncmp(node_name, "fragment@", 9) != 0 &&
|
|
strncmp(node_name, "fragment-", 9) != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
frag_name = node_name + 9;
|
|
|
|
// Find the target and overlay nodes
|
|
overlay_off = fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__overlay__");
|
|
if (overlay_off < 0)
|
|
{
|
|
if (fdt_subnode_offset(overlay_dtb->fdt, frag_off, "__dormant__") >= 0)
|
|
dtoverlay_debug("fragment %s disabled", frag_name);
|
|
else
|
|
dtoverlay_error("no overlay in fragment %s", frag_name);
|
|
continue;
|
|
}
|
|
|
|
target_off = dtoverlay_get_target_offset(base_dtb, overlay_dtb, frag_off);
|
|
if (target_off < 0)
|
|
{
|
|
err = target_off;
|
|
break;
|
|
}
|
|
|
|
// Now do the merge
|
|
err = dtoverlay_merge_fragment(base_dtb, target_off, overlay_dtb,
|
|
overlay_off, 0);
|
|
}
|
|
|
|
if (err == 0)
|
|
base_dtb->max_phandle = overlay_dtb->max_phandle;
|
|
|
|
no_base_dtb:
|
|
if (err)
|
|
dtoverlay_error("merge failed");
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors
|
|
int dtoverlay_fixup_overlay(DTBLOB_T *base_dtb, DTBLOB_T *overlay_dtb)
|
|
{
|
|
int err;
|
|
|
|
// To do: Check the "compatible" string?
|
|
|
|
err = dtoverlay_resolve_fixups(base_dtb, overlay_dtb);
|
|
|
|
if (err >= 0)
|
|
err = dtoverlay_resolve_phandles(base_dtb, overlay_dtb);
|
|
|
|
overlay_dtb->fixups_applied = 1;
|
|
|
|
return NON_FATAL(err);
|
|
}
|
|
|
|
// Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors
|
|
int dtoverlay_merge_params(DTBLOB_T *dtb, const DTOVERLAY_PARAM_T *params,
|
|
unsigned int num_params)
|
|
{
|
|
int err = 0;
|
|
unsigned int i;
|
|
|
|
for (i=0; (i<num_params) && (err == 0); i++) {
|
|
const DTOVERLAY_PARAM_T *p;
|
|
const char *node_name, *slash;
|
|
int node_off, path_len;
|
|
|
|
p = params + i;
|
|
node_name = p->param;
|
|
slash = strrchr(node_name, '/');
|
|
|
|
if (!slash)
|
|
{
|
|
err = NON_FATAL(FDT_ERR_BADPATH);
|
|
break;
|
|
}
|
|
|
|
// Ensure that root properties ("/xxx") work
|
|
if (slash == node_name)
|
|
path_len = 1;
|
|
else
|
|
path_len = slash - node_name;
|
|
|
|
// find node, create if it does not exist yet
|
|
node_off = dtoverlay_create_node(dtb, node_name, path_len);
|
|
if (node_off >= 0)
|
|
{
|
|
const char *prop_name = slash + 1;
|
|
int prop_len;
|
|
struct fdt_property *prop;
|
|
|
|
if ((strcmp(prop_name, "bootargs") == 0) &&
|
|
((prop = fdt_get_property_w(dtb->fdt, node_off, prop_name, &prop_len)) != NULL) &&
|
|
(prop_len > 0) && *prop->data)
|
|
{
|
|
prop->data[prop_len - 1] = ' ';
|
|
err = fdt_appendprop(dtb->fdt, node_off, prop_name, p->b, p->len);
|
|
}
|
|
else
|
|
err = fdt_setprop(dtb->fdt, node_off, prop_name, p->b, p->len);
|
|
}
|
|
else
|
|
err = node_off;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int dtoverlay_filter_symbols(DTBLOB_T *dtb)
|
|
{
|
|
int symbols_off;
|
|
int exports_off;
|
|
struct str_item *exports = NULL;
|
|
int prop_off;
|
|
|
|
struct str_item
|
|
{
|
|
struct str_item *next;
|
|
char str[0];
|
|
};
|
|
|
|
symbols_off = dtoverlay_find_node(dtb, "/__symbols__", 0);
|
|
if (symbols_off < 0)
|
|
return 0;
|
|
|
|
exports_off = dtoverlay_find_node(dtb, "/__exports__", 0);
|
|
if (exports_off < 0)
|
|
{
|
|
/* There are no exports, so keep all symbols private. */
|
|
fdt_del_node(dtb->fdt, symbols_off);
|
|
return 0;
|
|
}
|
|
|
|
/* Internalise the names of the exported properties for speed
|
|
* and to protect against the FDT contents moving. */
|
|
fdt_for_each_property_offset(prop_off, dtb->fdt, exports_off)
|
|
{
|
|
struct str_item *new_str;
|
|
const char *name = NULL;
|
|
fdt_getprop_by_offset(dtb->fdt, prop_off, &name, NULL);
|
|
if (!name)
|
|
break;
|
|
new_str = malloc(sizeof(*new_str) + strlen(name) + 1);
|
|
if (!new_str)
|
|
{
|
|
/* Free all of the internalised exports */
|
|
while (exports)
|
|
{
|
|
struct str_item *str = exports;
|
|
exports = str->next;
|
|
free(str);
|
|
}
|
|
dtoverlay_error(" out of memory");
|
|
return -FDT_ERR_NOSPACE;
|
|
}
|
|
|
|
strcpy(new_str->str, name);
|
|
new_str->next = exports;
|
|
exports = new_str;
|
|
}
|
|
|
|
/* Iterate through the symbols, deleting any that aren't
|
|
* exported.
|
|
*/
|
|
prop_off = fdt_first_property_offset(dtb->fdt, symbols_off);
|
|
while (prop_off >= 0)
|
|
{
|
|
const char *name = NULL;
|
|
struct str_item *str;
|
|
|
|
(void)fdt_getprop_by_offset(dtb->fdt, prop_off, &name, NULL);
|
|
if (!name)
|
|
break;
|
|
|
|
for (str = exports; str; str = str->next)
|
|
{
|
|
if (!strcmp(str->str, name))
|
|
break;
|
|
}
|
|
|
|
if (str)
|
|
/* This symbol is exported */
|
|
prop_off = fdt_next_property_offset(dtb->fdt, prop_off);
|
|
else
|
|
fdt_delprop(dtb->fdt, symbols_off, name);
|
|
}
|
|
|
|
/* Free all of the internalised exports */
|
|
while (exports)
|
|
{
|
|
struct str_item *str = exports;
|
|
exports = str->next;
|
|
free(str);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Returns a pointer to the override data and (through data_len) its length.
|
|
On error, sets *data_len to be the error code. */
|
|
const char *dtoverlay_find_override(DTBLOB_T *dtb, const char *override_name,
|
|
int *data_len)
|
|
{
|
|
int overrides_off;
|
|
const char *data;
|
|
int len;
|
|
|
|
// Find the table of overrides
|
|
overrides_off = fdt_path_offset(dtb->fdt, "/__overrides__");
|
|
|
|
if (overrides_off < 0)
|
|
{
|
|
dtoverlay_debug("/__overrides__ node not found");
|
|
*data_len = overrides_off;
|
|
return NULL;
|
|
}
|
|
|
|
// Locate the property
|
|
data = fdt_getprop(dtb->fdt, overrides_off, override_name, &len);
|
|
*data_len = len;
|
|
if (data)
|
|
dtoverlay_debug("found override %s", override_name);
|
|
else
|
|
dtoverlay_debug("/__overrides__ has no %s property", override_name);
|
|
|
|
return data;
|
|
}
|
|
|
|
int hex_digit(char c)
|
|
{
|
|
if (c >= '0' && c <= '9')
|
|
return c - '0';
|
|
else if (c >= 'A' && c <= 'F')
|
|
return 10 + c - 'A';
|
|
else if (c >= 'a' && c <= 'f')
|
|
return 10 + c - 'a';
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
int dtoverlay_override_one_target(int override_type,
|
|
const char *override_value,
|
|
DTBLOB_T *dtb, int node_off,
|
|
const char *prop_name, int target_phandle,
|
|
int target_off, int target_size,
|
|
void *callback_state)
|
|
{
|
|
int err = 0;
|
|
|
|
if (override_type == DTOVERRIDE_STRING)
|
|
{
|
|
char *prop_val;
|
|
int prop_len;
|
|
|
|
/* Replace the whole property with the string */
|
|
if ((strcmp(prop_name, "bootargs") == 0) &&
|
|
((prop_val = fdt_getprop_w(dtb->fdt, node_off, prop_name,
|
|
&prop_len)) != NULL) &&
|
|
(prop_len > 0) && prop_val[0])
|
|
{
|
|
prop_val[prop_len - 1] = ' ';
|
|
err = fdt_appendprop_string(dtb->fdt, node_off, prop_name, override_value);
|
|
}
|
|
else if (strcmp(prop_name, "name") == 0) // "name" is a pseudo-property
|
|
{
|
|
err = dtoverlay_set_node_name(dtb, node_off, override_value);
|
|
}
|
|
else
|
|
err = fdt_setprop_string(dtb->fdt, node_off, prop_name, override_value);
|
|
}
|
|
else if (override_type == DTOVERRIDE_BYTE_STRING)
|
|
{
|
|
/* Replace the whole property with the byte string */
|
|
uint8_t bytes_buf[32]; // For efficiency/laziness, place a limit on the length
|
|
const char *p = override_value;
|
|
int byte_count = 0;
|
|
|
|
while (*p)
|
|
{
|
|
int nib1, nib2;
|
|
// whitespace and colons are legal separators
|
|
if (*p == ':' || *p == ' ' || *p == '\t')
|
|
{
|
|
p++;
|
|
continue;
|
|
}
|
|
nib1 = hex_digit(*p++);
|
|
nib2 = hex_digit(*p++);
|
|
if (nib1 < 0 || nib2 < 0)
|
|
{
|
|
dtoverlay_error("invalid bytestring '%s'", override_value);
|
|
return NON_FATAL(FDT_ERR_BADVALUE);
|
|
}
|
|
if (byte_count == sizeof(bytes_buf))
|
|
{
|
|
dtoverlay_error("bytestring '%s' too long", override_value);
|
|
return NON_FATAL(FDT_ERR_BADVALUE);
|
|
}
|
|
bytes_buf[byte_count++] = (nib1 << 4) | nib2;
|
|
}
|
|
|
|
err = fdt_setprop(dtb->fdt, node_off, prop_name, bytes_buf, byte_count);
|
|
}
|
|
else if (override_type != DTOVERRIDE_END)
|
|
{
|
|
const char *p;
|
|
char *end;
|
|
char *prop_val;
|
|
void *prop_buf = NULL;
|
|
int prop_len;
|
|
int new_prop_len;
|
|
uint64_t override_int;
|
|
uint32_t frag_num;
|
|
|
|
/* Parse as an integer */
|
|
override_int = strtoull(override_value, &end, 0);
|
|
if (end[0] != '\0')
|
|
{
|
|
if ((strcmp(override_value, "y") == 0) ||
|
|
(strcmp(override_value, "yes") == 0) ||
|
|
(strcmp(override_value, "on") == 0) ||
|
|
(strcmp(override_value, "true") == 0) ||
|
|
(strcmp(override_value, "down") == 0))
|
|
override_int = 1;
|
|
else if ((strcmp(override_value, "n") == 0) ||
|
|
(strcmp(override_value, "no") == 0) ||
|
|
(strcmp(override_value, "off") == 0) ||
|
|
(strcmp(override_value, "false") == 0))
|
|
override_int = 0;
|
|
else if (strcmp(override_value, "up") == 0)
|
|
override_int = 2;
|
|
else
|
|
{
|
|
dtoverlay_error("invalid override value '%s' - ignored",
|
|
override_value);
|
|
return NON_FATAL(FDT_ERR_INTERNAL);
|
|
}
|
|
}
|
|
|
|
switch (override_type)
|
|
{
|
|
case DTOVERRIDE_INTEGER:
|
|
/* Patch a word within the property */
|
|
prop_val = (void *)fdt_getprop(dtb->fdt, node_off, prop_name,
|
|
&prop_len);
|
|
new_prop_len = target_off + target_size;
|
|
if (prop_len < new_prop_len)
|
|
{
|
|
/* This property either doesn't exist or isn't long enough.
|
|
Create a buffer containing any existing property data
|
|
with zero padding, which will later be patched and written
|
|
back. */
|
|
prop_buf = calloc(1, new_prop_len);
|
|
if (!prop_buf)
|
|
{
|
|
dtoverlay_error(" out of memory");
|
|
return NON_FATAL(FDT_ERR_NOSPACE);
|
|
}
|
|
if (prop_len > 0)
|
|
memcpy(prop_buf, prop_val, prop_len);
|
|
prop_val = prop_buf;
|
|
}
|
|
|
|
switch (target_size)
|
|
{
|
|
case 1:
|
|
dtoverlay_write_u8(prop_val, target_off, (uint32_t)override_int);
|
|
break;
|
|
case 2:
|
|
dtoverlay_write_u16(prop_val, target_off, (uint32_t)override_int);
|
|
break;
|
|
case 4:
|
|
dtoverlay_write_u32(prop_val, target_off, (uint32_t)override_int);
|
|
break;
|
|
case 8:
|
|
dtoverlay_write_u64(prop_val, target_off, override_int);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (prop_buf)
|
|
{
|
|
/* Add/extend the property by setting it */
|
|
if (strcmp(prop_name, "reg") != 0) // Don't create or extend "reg" - it must be a pseudo-property
|
|
err = fdt_setprop(dtb->fdt, node_off, prop_name, prop_buf, new_prop_len);
|
|
free(prop_buf);
|
|
}
|
|
|
|
if (strcmp(prop_name, "reg") == 0 && target_off == 0)
|
|
{
|
|
const char *old_name = fdt_get_name(dtb->fdt, node_off, NULL);
|
|
const char *atpos = strchr(old_name, '@');
|
|
if (atpos)
|
|
{
|
|
int name_len = (atpos - old_name);
|
|
char *new_name = malloc(name_len + 1 + 16 + 1);
|
|
if (!new_name)
|
|
return -FDT_ERR_NOSPACE;
|
|
sprintf(new_name, "%.*s@%x", name_len, old_name, (uint32_t)override_int);
|
|
err = dtoverlay_set_node_name(dtb, node_off, new_name);
|
|
free(new_name);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DTOVERRIDE_BOOLEAN:
|
|
case DTOVERRIDE_BOOLEAN_INV:
|
|
/* This is a boolean property (present->true, absent->false) */
|
|
if (override_int ^ (override_type == DTOVERRIDE_BOOLEAN_INV))
|
|
err = fdt_setprop(dtb->fdt, node_off, prop_name, NULL, 0);
|
|
else
|
|
{
|
|
err = fdt_delprop(dtb->fdt, node_off, prop_name);
|
|
if (err == -FDT_ERR_NOTFOUND)
|
|
err = 0;
|
|
}
|
|
break;
|
|
|
|
case DTOVERRIDE_OVERLAY:
|
|
/* Apply the overlay-wide override. The supported options are (<frag> is a fragment number):
|
|
+<frag> Enable a fragment
|
|
-<frag> Disable a fragment
|
|
=<frag> Enable/disable the fragment according to the override value
|
|
!<frag> Disable/enable the fragment according to the inverse of the override value */
|
|
p = prop_name;
|
|
while (*p && !err)
|
|
{
|
|
char type = *p;
|
|
int frag_off;
|
|
switch (type)
|
|
{
|
|
case '+':
|
|
case '-':
|
|
case '=':
|
|
case '!':
|
|
frag_num = strtoul(p + 1, &end, 0);
|
|
if (end != p)
|
|
{
|
|
/* Change fragment@<frag_num>/__overlay__<->__dormant__ as necessary */
|
|
const char *states[2] = { "__dormant__", "__overlay__" };
|
|
char node_name[24];
|
|
int active = (type == '+') ||
|
|
((type == '=') && (override_int != 0)) ||
|
|
((type == '!') && (override_int == 0));
|
|
snprintf(node_name, sizeof(node_name), "/fragment@%u", frag_num);
|
|
frag_off = fdt_path_offset(dtb->fdt, node_name);
|
|
if (frag_off < 0)
|
|
{
|
|
snprintf(node_name, sizeof(node_name), "/fragment-%u", frag_num);
|
|
frag_off = fdt_path_offset(dtb->fdt, node_name);
|
|
}
|
|
if (frag_off >= 0)
|
|
{
|
|
frag_off = fdt_subnode_offset(dtb->fdt, frag_off, states[!active]);
|
|
if (frag_off >= 0)
|
|
(void)dtoverlay_set_node_name(dtb, frag_off, states[active]);
|
|
}
|
|
else
|
|
{
|
|
dtoverlay_error(" fragment %u not found", frag_num);
|
|
err = NON_FATAL(frag_off);
|
|
}
|
|
p = end;
|
|
}
|
|
else
|
|
{
|
|
dtoverlay_error(" invalid overlay override '%s'", prop_name);
|
|
err = NON_FATAL(FDT_ERR_BADVALUE);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
The problem is the split between inline string values and inline
|
|
cell values passed to the callback. For strings properties the
|
|
returned data is strings; no conversion from cells is required. The
|
|
special handling for "status" is performed before the callback. For
|
|
all other property types the returned values are binary/opaque data.
|
|
Any string data should have been converted to binary data already in
|
|
the framework.
|
|
|
|
Translation:
|
|
1. The override value (the value assigned to the parameter) is always a string.
|
|
2. Strings are converted according to type of the parameter at the point of use.
|
|
3. A single override value can result in multiple different values being assigned
|
|
to properties as the result of type conversions and set lookups.
|
|
4. Cell literals have a binary value.
|
|
5. Lookups convert strings to either a string or a cell literal.
|
|
6. Cell literals are primarily (only?) useful for label references, which are
|
|
really just integers. There is nothing stopping them (or other integers) being
|
|
converted to strings.
|
|
7. Therefore dtoverlay_extract_override always returns a string value, either the
|
|
input override value, a literal, or the result of a lookup.
|
|
*/
|
|
|
|
// Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors
|
|
// After calling this, assume all node offsets are no longer valid
|
|
int dtoverlay_foreach_override_target(DTBLOB_T *dtb, const char *override_name,
|
|
const char *override_data, int data_len,
|
|
const char *override_value,
|
|
override_callback_t callback,
|
|
void *callback_state)
|
|
{
|
|
int err = 0;
|
|
int target_phandle = 0;
|
|
char *data_buf, *data, *data_end;
|
|
|
|
/* Short-circuit the degenerate case of an empty parameter, avoiding an
|
|
apparent memory allocation failure. */
|
|
if (!data_len)
|
|
return 0;
|
|
|
|
/* Copy the override data in case it moves */
|
|
data_buf = malloc(data_len);
|
|
if (!data_buf)
|
|
{
|
|
dtoverlay_error(" out of memory");
|
|
return NON_FATAL(FDT_ERR_NOSPACE);
|
|
}
|
|
|
|
memcpy(data_buf, override_data, data_len);
|
|
data = data_buf;
|
|
data_end = data + data_len;
|
|
|
|
while (err == 0)
|
|
{
|
|
const char *target_prop = NULL;
|
|
static char prop_name[256];
|
|
static char target_value[256];
|
|
int name_len = 0;
|
|
int target_off = 0;
|
|
int target_size = 0;
|
|
int override_type;
|
|
int node_off = 0;
|
|
|
|
strcpy(target_value, override_value);
|
|
override_type = dtoverlay_extract_override(override_name,
|
|
target_value, sizeof(target_value),
|
|
&target_phandle,
|
|
(const char **)&data, data_end,
|
|
&target_prop, &name_len,
|
|
&target_off, &target_size);
|
|
|
|
if (override_type < 0)
|
|
{
|
|
err = override_type;
|
|
break;
|
|
}
|
|
/* Pass DTOVERRIDE_END to the callback, in case it is interested */
|
|
|
|
if (target_phandle != 0)
|
|
{
|
|
node_off = fdt_node_offset_by_phandle(dtb->fdt, target_phandle);
|
|
if (node_off < 0)
|
|
{
|
|
dtoverlay_error(" phandle %d not found", target_phandle);
|
|
err = NON_FATAL(node_off);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Sadly there are no '_namelen' setprop variants, so copies are required */
|
|
if (target_prop)
|
|
{
|
|
memcpy(prop_name, target_prop, name_len);
|
|
prop_name[name_len] = '\0';
|
|
}
|
|
|
|
err = callback(override_type, target_value, dtb, node_off, prop_name,
|
|
target_phandle, target_off, target_size, callback_state);
|
|
|
|
if (override_type == DTOVERRIDE_END)
|
|
break;
|
|
}
|
|
|
|
free(data_buf);
|
|
|
|
return err;
|
|
}
|
|
|
|
// Returns 0 on success, -ve for fatal errors and +ve for non-fatal errors
|
|
int dtoverlay_apply_override(DTBLOB_T *dtb, const char *override_name,
|
|
const char *override_data, int data_len,
|
|
const char *override_value)
|
|
{
|
|
return dtoverlay_foreach_override_target(dtb, override_name,
|
|
override_data, data_len,
|
|
override_value,
|
|
dtoverlay_override_one_target,
|
|
NULL);
|
|
}
|
|
|
|
/* Returns an override type (DTOVERRIDE_INTEGER, DTOVERRIDE_BOOLEAN, DTOVERRIDE_STRING, DTOVERRIDE_OVERLAY),
|
|
DTOVERRIDE_END (0) at the end, or an error code (< 0) */
|
|
static int dtoverlay_extract_override(const char *override_name,
|
|
char *override_value, int value_size,
|
|
int *phandle_ptr,
|
|
const char **datap, const char *data_end,
|
|
const char **namep, int *namelenp,
|
|
int *offp, int *sizep)
|
|
{
|
|
const char *data;
|
|
const char *prop_name, *override_end;
|
|
int len, override_len, name_len, target_len, phandle;
|
|
const char *offset_seps = ".;:#?![{=";
|
|
const char *literal_value = NULL;
|
|
char literal_type = '?';
|
|
int type;
|
|
|
|
data = *datap;
|
|
len = data_end - data;
|
|
if (len <= 0)
|
|
{
|
|
if (len < 0)
|
|
return len;
|
|
*phandle_ptr = 0;
|
|
*namep = NULL;
|
|
return DTOVERRIDE_END;
|
|
}
|
|
|
|
// Check for space for a phandle, a terminating NUL and at least one char
|
|
if (len < (sizeof(fdt32_t) + 1 + 1))
|
|
{
|
|
dtoverlay_error(" override %s: data is truncated or mangled",
|
|
override_name);
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
}
|
|
|
|
phandle = dtoverlay_read_u32(data, 0);
|
|
*phandle_ptr = phandle;
|
|
|
|
data += sizeof(fdt32_t);
|
|
len -= sizeof(fdt32_t);
|
|
|
|
override_end = memchr(data, 0, len);
|
|
if (!override_end)
|
|
{
|
|
dtoverlay_error(" override %s: string is not NUL-terminated",
|
|
override_name);
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
}
|
|
|
|
prop_name = data;
|
|
|
|
override_len = override_end - prop_name;
|
|
data += (override_len + 1);
|
|
*datap = data;
|
|
|
|
if (phandle <= 0)
|
|
{
|
|
if (phandle < 0)
|
|
return -FDT_ERR_BADPHANDLE;
|
|
/* This is an "overlay" override, signalled using <0> as the phandle. */
|
|
*namep = prop_name;
|
|
*namelenp = override_len;
|
|
return DTOVERRIDE_OVERLAY;
|
|
}
|
|
|
|
target_len = strcspn(prop_name, "={");
|
|
name_len = strcspn(prop_name, offset_seps);
|
|
|
|
*namep = prop_name;
|
|
*namelenp = name_len;
|
|
|
|
if (target_len < override_len)
|
|
{
|
|
/* Literal assignment or lookup table
|
|
* Can't have '=' and '{' (or at least, don't need to support it.
|
|
* = is an override value replacement
|
|
* { is an override value transformation
|
|
*/
|
|
literal_type = prop_name[target_len];
|
|
literal_value = prop_name + target_len + 1;
|
|
}
|
|
|
|
if (name_len < target_len)
|
|
{
|
|
/* There is a separator specified */
|
|
char sep = prop_name[name_len];
|
|
if (sep == '?')
|
|
{
|
|
/* The target is a boolean parameter (present->true, absent->false) */
|
|
*offp = 0;
|
|
*sizep = 0;
|
|
type = DTOVERRIDE_BOOLEAN;
|
|
dtoverlay_debug(" override %s: boolean target %.*s",
|
|
override_name, name_len, prop_name);
|
|
}
|
|
else if (sep == '!')
|
|
{
|
|
/* The target is a boolean parameter (present->true, absent->false),
|
|
* but the sense of the value is inverted */
|
|
*offp = 0;
|
|
*sizep = 0;
|
|
type = DTOVERRIDE_BOOLEAN_INV;
|
|
dtoverlay_debug(" override %s: inverted boolean target %.*s",
|
|
override_name, name_len, prop_name);
|
|
}
|
|
else if (sep == '[')
|
|
{
|
|
/* The target is a byte-string */
|
|
*offp = -1;
|
|
*sizep = 0;
|
|
type = DTOVERRIDE_BYTE_STRING;
|
|
dtoverlay_debug(" override %s: byte-string target %.*s",
|
|
override_name, name_len, prop_name);
|
|
}
|
|
else
|
|
{
|
|
/* The target is a cell/integer */
|
|
*offp = atoi(prop_name + name_len + 1);
|
|
*sizep = 1 << (strchr(offset_seps, sep) - offset_seps);
|
|
type = DTOVERRIDE_INTEGER;
|
|
dtoverlay_debug(" override %s: cell target %.*s @ offset %d (size %d)",
|
|
override_name, name_len, prop_name, *offp, *sizep);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*offp = -1;
|
|
*sizep = 0;
|
|
type = DTOVERRIDE_STRING;
|
|
dtoverlay_debug(" override %s: string target '%.*s'",
|
|
override_name, name_len, prop_name);
|
|
}
|
|
|
|
if (literal_value)
|
|
{
|
|
if (literal_type == '=')
|
|
{
|
|
/* Immediate value */
|
|
if (type == DTOVERRIDE_STRING ||
|
|
type == DTOVERRIDE_BYTE_STRING ||
|
|
literal_value[0])
|
|
{
|
|
/* String */
|
|
strcpy(override_value, literal_value);
|
|
}
|
|
else
|
|
{
|
|
/* Cell */
|
|
sprintf(override_value, "%d", dtoverlay_read_u32(data, 0));
|
|
*datap = data + 4;
|
|
}
|
|
}
|
|
else if (literal_type == '{')
|
|
{
|
|
/* Lookup */
|
|
data = dtoverlay_lookup_key(literal_value, data_end,
|
|
override_value, override_value, value_size);
|
|
*datap = data;
|
|
if (!data)
|
|
return -FDT_ERR_BADSTRUCTURE;
|
|
}
|
|
else
|
|
{
|
|
return -FDT_ERR_INTERNAL;
|
|
}
|
|
}
|
|
|
|
if ((type == DTOVERRIDE_STRING) &&
|
|
(strmemcmp(prop_name, name_len, "status") == 0))
|
|
{
|
|
/* Convert booleans to okay/disabled */
|
|
if ((strcmp(override_value, "y") == 0) ||
|
|
(strcmp(override_value, "yes") == 0) ||
|
|
(strcmp(override_value, "on") == 0) ||
|
|
(strcmp(override_value, "true") == 0) ||
|
|
(strcmp(override_value, "enable") == 0) ||
|
|
(strcmp(override_value, "1") == 0))
|
|
strcpy(override_value, "okay");
|
|
else if ((strcmp(override_value, "n") == 0) ||
|
|
(strcmp(override_value, "no") == 0) ||
|
|
(strcmp(override_value, "off") == 0) ||
|
|
(strcmp(override_value, "false") == 0) ||
|
|
(strcmp(override_value, "0") == 0))
|
|
strcpy(override_value, "disabled");
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
/* Read the string or (if permitted) cell value, storing the result in buf. Returns a pointer
|
|
to the first byte after the successfully parsed immediate, or NULL on error. */
|
|
static const char *dtoverlay_extract_immediate(const char *data, const char *data_end,
|
|
char *buf, int buf_len)
|
|
{
|
|
if ((data + 1) < data_end && !data[0])
|
|
{
|
|
uint32_t val;
|
|
data++;
|
|
if (data + 4 > data_end)
|
|
{
|
|
dtoverlay_error(" truncated cell immediate");
|
|
return NULL;
|
|
}
|
|
val = dtoverlay_read_u32(data, 0);
|
|
if (buf)
|
|
snprintf(buf, buf_len, "%d", val);
|
|
data += 4;
|
|
}
|
|
else if (data[0] == '\'')
|
|
{
|
|
// Continue to closing "'", error on end-of-string
|
|
int len;
|
|
data++;
|
|
len = strcspn(data, "'");
|
|
if (!data[len])
|
|
{
|
|
dtoverlay_error(" unterminated quoted string: '%s", data);
|
|
return NULL;
|
|
}
|
|
if (len >= buf_len)
|
|
{
|
|
dtoverlay_error(" immediate string too long: '%s", data);
|
|
return NULL;
|
|
}
|
|
if (buf)
|
|
{
|
|
memcpy(buf, data, len);
|
|
buf[len] = '\0';
|
|
}
|
|
data += len + 1;
|
|
if (*data == ',') // Skip a comma, preserve a brace
|
|
data++;
|
|
}
|
|
else
|
|
{
|
|
// Continue to a comma, right brace or end-of-string NUL
|
|
int len = strcspn(data, ",}");
|
|
if (len >= buf_len)
|
|
{
|
|
dtoverlay_error(" immediate string too long: '%s", data);
|
|
return NULL;
|
|
}
|
|
if (buf)
|
|
{
|
|
memcpy(buf, data, len);
|
|
buf[len] = '\0';
|
|
}
|
|
data += len;
|
|
if (*data == ',') // Skip a comma, preserve a brace
|
|
data++;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
static const char *dtoverlay_lookup_key(const char *lookup_string, const char *data_end,
|
|
const char *key, char *buf, int buf_len)
|
|
{
|
|
const char *p = lookup_string;
|
|
int found = 0;
|
|
|
|
while (p < data_end && *p && *p != '}')
|
|
{
|
|
int key_len = strcspn(p, "=,}");
|
|
char *q = NULL;
|
|
char sep = p[key_len];
|
|
|
|
if (!key_len)
|
|
{
|
|
if (sep) // default value
|
|
{
|
|
if (!found)
|
|
{
|
|
q = buf;
|
|
found = 2;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (found != 1 && strmemcmp(p, key_len, key) == 0)
|
|
{
|
|
q = buf;
|
|
found = 1;
|
|
}
|
|
}
|
|
|
|
p += key_len;
|
|
|
|
if (sep == '=')
|
|
{
|
|
p = dtoverlay_extract_immediate(p + 1, data_end, q, buf_len);
|
|
}
|
|
else
|
|
{
|
|
if (q && q != key)
|
|
{
|
|
strncpy(q, key, buf_len);
|
|
q[buf_len - 1] = 0;
|
|
}
|
|
if (sep == ',')
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
{
|
|
dtoverlay_error("lookup -> no match for '%s'", key);
|
|
return NULL;
|
|
}
|
|
|
|
if (p == data_end)
|
|
return p;
|
|
|
|
if (!*p)
|
|
{
|
|
dtoverlay_error(" malformed lookup");
|
|
return NULL;
|
|
}
|
|
|
|
assert(p[0] != 0 && p[1] == 0);
|
|
return p + 2;
|
|
}
|
|
|
|
int dtoverlay_set_synonym(DTBLOB_T *dtb, const char *dst, const char *src)
|
|
{
|
|
/* Add/update all aliases, symbols and overrides named dst
|
|
to be equivalent to those named src.
|
|
An absent src is ignored.
|
|
*/
|
|
int err;
|
|
|
|
err = dtoverlay_dup_property(dtb, "/aliases", dst, src);
|
|
if (err == 0)
|
|
err = dtoverlay_dup_property(dtb, "/__symbols__", dst, src);
|
|
if (err == 0)
|
|
dtoverlay_dup_property(dtb, "/__overrides__", dst, src);
|
|
return err;
|
|
}
|
|
|
|
int dtoverlay_dup_property(DTBLOB_T *dtb, const char *node_name,
|
|
const char *dst, const char *src)
|
|
{
|
|
/* Find the node and src property */
|
|
const DTBLOB_T *src_prop;
|
|
int node_off;
|
|
int prop_len = 0;
|
|
int err = 0;
|
|
|
|
node_off = fdt_path_offset(dtb->fdt, node_name);
|
|
if (node_off < 0)
|
|
return 0;
|
|
|
|
src_prop = fdt_getprop(dtb->fdt, node_off, src, &prop_len);
|
|
if (!src_prop)
|
|
return 0;
|
|
|
|
err = fdt_setprop_inplace(dtb->fdt, node_off, dst, src_prop, prop_len);
|
|
if (err != 0)
|
|
{
|
|
void *prop_data;
|
|
/* Copy the src property, just in case things move */
|
|
prop_data = malloc(prop_len);
|
|
memcpy(prop_data, src_prop, prop_len);
|
|
|
|
err = fdt_setprop(dtb->fdt, node_off, dst, prop_data, prop_len);
|
|
|
|
free(prop_data);
|
|
}
|
|
|
|
if (err == 0)
|
|
dtoverlay_debug("%s:%s=%s", node_name, dst, src);
|
|
return err;
|
|
}
|
|
|
|
int dtoverlay_find_pins_for_device(DTBLOB_T *dtb, const char *symbol,
|
|
PIN_ITER_T *iter)
|
|
{
|
|
int pos = dtoverlay_find_symbol(dtb, symbol);
|
|
|
|
memset(iter, 0, sizeof(*iter));
|
|
|
|
if (pos < 0)
|
|
return pos;
|
|
|
|
iter->dtb = dtb;
|
|
|
|
if (dtoverlay_node_is_enabled(dtb, pos))
|
|
iter->pinctrl = dtoverlay_get_property(dtb, pos, "pinctrl-0", &iter->pinctrl_len);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dtoverlay_next_pin(PIN_ITER_T *iter, int *pin, int *func, int *pull)
|
|
{
|
|
if (pin)
|
|
*pin = -1;
|
|
if (func)
|
|
*func = -1;
|
|
if (pull)
|
|
*pull = -1;
|
|
|
|
while (1)
|
|
{
|
|
int phandle, pos;
|
|
|
|
if ((iter->pin_off) + 4 <= iter->pins_len)
|
|
{
|
|
int off = iter->pin_off;
|
|
*pin = GETBE4(iter->pins, off);
|
|
if (func && iter->funcs_len)
|
|
*func = GETBE4(iter->funcs, (iter->funcs_len > 4) ? off : 0);
|
|
if (pull && iter->pulls_len)
|
|
*pull = GETBE4(iter->pulls, (iter->pulls_len > 4) ? off : 0);
|
|
iter->pin_off = off + 4;
|
|
return 1;
|
|
}
|
|
|
|
if ((iter->pinctrl_off + 4) > iter->pinctrl_len)
|
|
break;
|
|
|
|
phandle = GETBE4(iter->pinctrl, iter->pinctrl_off);
|
|
iter->pinctrl_off += 4;
|
|
pos = dtoverlay_find_phandle(iter->dtb, phandle);
|
|
iter->pins = dtoverlay_get_property(iter->dtb, pos, "brcm,pins", &iter->pins_len);
|
|
iter->funcs = dtoverlay_get_property(iter->dtb, pos, "brcm,function", &iter->funcs_len);
|
|
iter->pulls = dtoverlay_get_property(iter->dtb, pos, "brcm,pull", &iter->pulls_len);
|
|
iter->pin_off = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
DTBLOB_T *dtoverlay_create_dtb(int max_size)
|
|
{
|
|
DTBLOB_T *dtb = NULL;
|
|
void *fdt = NULL;
|
|
|
|
fdt = malloc(max_size);
|
|
if (!fdt)
|
|
{
|
|
dtoverlay_error("out of memory");
|
|
goto error_exit;
|
|
}
|
|
|
|
if (fdt_create_empty_tree(fdt, max_size) != 0)
|
|
{
|
|
dtoverlay_error("failed to create empty dtb");
|
|
goto error_exit;
|
|
}
|
|
|
|
dtb = calloc(1, sizeof(DTBLOB_T));
|
|
if (!dtb)
|
|
{
|
|
dtoverlay_error("out of memory");
|
|
goto error_exit;
|
|
}
|
|
|
|
dtb->fdt = fdt;
|
|
dtb->max_phandle = 0; // Not a valid phandle
|
|
|
|
return dtb;
|
|
|
|
error_exit:
|
|
free(fdt);
|
|
if (dtb)
|
|
free(dtb->trailer);
|
|
free(dtb);
|
|
return NULL;
|
|
|
|
}
|
|
|
|
DTBLOB_T *dtoverlay_load_dtb_from_fp(FILE *fp, int max_size)
|
|
{
|
|
DTBLOB_T *dtb = NULL;
|
|
void *fdt = NULL;
|
|
|
|
if (fp)
|
|
{
|
|
long len;
|
|
long bytes_read;
|
|
int dtb_len;
|
|
|
|
fseek(fp, 0, SEEK_END);
|
|
len = ftell(fp);
|
|
fseek(fp, 0, SEEK_SET);
|
|
if (max_size > 0)
|
|
{
|
|
if (max_size < len)
|
|
{
|
|
dtoverlay_error("file too large (%d bytes) for max_size", len);
|
|
goto error_exit;
|
|
}
|
|
}
|
|
else if (max_size < 0)
|
|
{
|
|
max_size = len - max_size;
|
|
}
|
|
else
|
|
{
|
|
max_size = len;
|
|
}
|
|
|
|
fdt = malloc(max_size);
|
|
if (!fdt)
|
|
{
|
|
dtoverlay_error("out of memory");
|
|
goto error_exit;
|
|
}
|
|
|
|
bytes_read = fread(fdt, 1, len, fp);
|
|
fclose(fp);
|
|
|
|
if (bytes_read != len)
|
|
{
|
|
dtoverlay_error("fread failed");
|
|
goto error_exit;
|
|
}
|
|
|
|
// Record the total size before any expansion
|
|
dtb_len = fdt_totalsize(fdt);
|
|
|
|
dtb = dtoverlay_import_fdt(fdt, max_size);
|
|
if (!dtb)
|
|
goto error_exit;
|
|
|
|
dtb->fdt_is_malloced = 1;
|
|
|
|
if (len > dtb_len)
|
|
{
|
|
/* Load the trailer */
|
|
dtb->trailer_len = len - dtb_len;
|
|
dtb->trailer = malloc(dtb->trailer_len);
|
|
if (!dtb->trailer)
|
|
{
|
|
dtoverlay_error("out of memory");
|
|
goto error_exit;
|
|
}
|
|
dtb->trailer_is_malloced = 1;
|
|
memcpy(dtb->trailer, (char *)fdt + dtb_len, dtb->trailer_len);
|
|
}
|
|
}
|
|
|
|
return dtb;
|
|
|
|
error_exit:
|
|
free(fdt);
|
|
if (dtb)
|
|
free(dtb->trailer);
|
|
free(dtb);
|
|
return NULL;
|
|
}
|
|
|
|
DTBLOB_T *dtoverlay_load_dtb(const char *filename, int max_size)
|
|
{
|
|
FILE *fp = fopen(filename, "rb");
|
|
if (fp)
|
|
return dtoverlay_load_dtb_from_fp(fp, max_size);
|
|
dtoverlay_error("failed to open '%s'", filename);
|
|
return NULL;
|
|
}
|
|
|
|
void dtoverlay_init_map_from_fp(FILE *fp, const char *compatible,
|
|
int compatible_len)
|
|
{
|
|
if (!compatible)
|
|
return;
|
|
|
|
while (compatible_len > 0)
|
|
{
|
|
const char *p;
|
|
int len;
|
|
|
|
// Look for a string containing a comma
|
|
p = memchr(compatible, ',', compatible_len);
|
|
|
|
if (p)
|
|
{
|
|
p++;
|
|
len = compatible + compatible_len - p;
|
|
}
|
|
else
|
|
{
|
|
// Otherwise treat it as a simple string
|
|
p = compatible;
|
|
len = compatible_len;
|
|
}
|
|
|
|
/* Group the members of the BCM2835 family */
|
|
if (strncmp(p, "bcm2708", len) == 0 ||
|
|
strncmp(p, "bcm2709", len) == 0 ||
|
|
strncmp(p, "bcm2710", len) == 0 ||
|
|
strncmp(p, "bcm2835", len) == 0 ||
|
|
strncmp(p, "bcm2836", len) == 0 ||
|
|
strncmp(p, "bcm2837", len) == 0)
|
|
{
|
|
platform_name = "bcm2835";
|
|
break;
|
|
}
|
|
else if (strncmp(p, "bcm2711", len) == 0)
|
|
{
|
|
platform_name = "bcm2711";
|
|
break;
|
|
}
|
|
|
|
compatible_len -= (p - compatible);
|
|
compatible = p;
|
|
|
|
len = strnlen(compatible, compatible_len) + 1;
|
|
compatible += len;
|
|
compatible_len -= len;
|
|
}
|
|
|
|
if (platform_name)
|
|
{
|
|
dtoverlay_debug("using platform '%s'", platform_name);
|
|
platform_name_len = strlen(platform_name);
|
|
if (fp)
|
|
overlay_map = dtoverlay_load_dtb_from_fp(fp, 0);
|
|
}
|
|
else
|
|
{
|
|
dtoverlay_warn("no matching platform found");
|
|
}
|
|
|
|
dtoverlay_debug("overlay map %sloaded", overlay_map ? "" : "not ");
|
|
}
|
|
|
|
void dtoverlay_init_map(const char *overlay_dir, const char *compatible,
|
|
int compatible_len)
|
|
{
|
|
char map_file[DTOVERLAY_MAX_PATH];
|
|
int dir_len = strlen(overlay_dir);
|
|
FILE *fp;
|
|
static int tried;
|
|
|
|
if (tried)
|
|
return;
|
|
|
|
tried = 1;
|
|
|
|
if (!compatible)
|
|
return;
|
|
|
|
/* Handle the possibility that the supplied directory may or may not end
|
|
with a slash */
|
|
sprintf(map_file, "%s%soverlay_map.dtb", overlay_dir,
|
|
(!dir_len || overlay_dir[dir_len - 1] != '/') ? "/" : "");
|
|
fp = fopen(map_file, "rb");
|
|
dtoverlay_init_map_from_fp(fp, compatible, compatible_len);
|
|
}
|
|
|
|
const char *dtoverlay_remap_overlay(const char *overlay)
|
|
{
|
|
while (overlay_map)
|
|
{
|
|
const char *deprecated_msg;
|
|
const char *new_name;
|
|
int root_off;
|
|
int overlay_off;
|
|
int prop_len;
|
|
|
|
root_off = fdt_path_offset(overlay_map->fdt, "/");
|
|
|
|
overlay_off = fdt_subnode_offset(overlay_map->fdt, root_off, overlay);
|
|
if (overlay_off < 0)
|
|
break;
|
|
|
|
new_name = fdt_getprop_namelen(overlay_map->fdt, overlay_off,
|
|
platform_name, platform_name_len,
|
|
&prop_len);
|
|
|
|
if (new_name)
|
|
{
|
|
if (new_name[0])
|
|
overlay = new_name;
|
|
break;
|
|
}
|
|
|
|
// Has it been renamed or deprecated?
|
|
new_name = fdt_getprop_namelen(overlay_map->fdt, overlay_off,
|
|
"renamed", 7, &prop_len);
|
|
if (new_name)
|
|
{
|
|
dtoverlay_warn("overlay '%s' has been renamed '%s'",
|
|
overlay, new_name);
|
|
overlay = new_name;
|
|
continue;
|
|
}
|
|
|
|
deprecated_msg = fdt_getprop_namelen(overlay_map->fdt, overlay_off,
|
|
"deprecated", 10, &prop_len);
|
|
if (deprecated_msg)
|
|
dtoverlay_error("overlay '%s' is deprecated: %s",
|
|
overlay, deprecated_msg);
|
|
else
|
|
dtoverlay_error("overlay '%s' is not supported on the '%s' platform", overlay, platform_name);
|
|
return NULL;
|
|
}
|
|
|
|
return overlay;
|
|
}
|
|
|
|
DTBLOB_T *dtoverlay_import_fdt(void *fdt, int buf_size)
|
|
{
|
|
DTBLOB_T *dtb = NULL;
|
|
int node_off;
|
|
int dtb_len;
|
|
int err;
|
|
|
|
err = fdt_check_header(fdt);
|
|
if (err != 0)
|
|
{
|
|
dtoverlay_error("not a valid FDT - err %d", err);
|
|
goto error_exit;
|
|
}
|
|
|
|
dtb_len = fdt_totalsize(fdt);
|
|
|
|
if (buf_size < dtb_len)
|
|
{
|
|
dtoverlay_error("fdt is too large");
|
|
err = -FDT_ERR_NOSPACE;
|
|
goto error_exit;
|
|
}
|
|
|
|
if (buf_size > dtb_len)
|
|
fdt_set_totalsize(fdt, buf_size);
|
|
|
|
dtb = calloc(1, sizeof(DTBLOB_T));
|
|
if (!dtb)
|
|
{
|
|
dtoverlay_error("out of memory");
|
|
goto error_exit;
|
|
}
|
|
|
|
dtb->fdt = fdt;
|
|
dtb->max_phandle = 0; // Not a valid phandle
|
|
|
|
// Find the minimum and maximum phandles, in case it is necessary to
|
|
// relocate existing ones or create new ones.
|
|
|
|
for (node_off = 0;
|
|
node_off >= 0;
|
|
node_off = fdt_next_node(fdt, node_off, NULL))
|
|
{
|
|
uint32_t phandle = fdt_get_phandle(fdt, node_off);
|
|
if (phandle > dtb->max_phandle)
|
|
dtb->max_phandle = phandle;
|
|
}
|
|
|
|
error_exit:
|
|
return dtb;
|
|
}
|
|
|
|
int dtoverlay_save_dtb(const DTBLOB_T *dtb, const char *filename)
|
|
{
|
|
FILE *fp = fopen(filename, "wb");
|
|
int err = 0;
|
|
|
|
if (fp)
|
|
{
|
|
long len = fdt_totalsize(dtb->fdt);
|
|
if (len != fwrite(dtb->fdt, 1, len, fp))
|
|
{
|
|
dtoverlay_error("fwrite failed");
|
|
err = -2;
|
|
goto error_exit;
|
|
}
|
|
if (dtb->trailer_len &&
|
|
(fwrite(dtb->trailer, 1, dtb->trailer_len, fp) != dtb->trailer_len))
|
|
{
|
|
dtoverlay_error("fwrite failed");
|
|
err = -2;
|
|
goto error_exit;
|
|
}
|
|
|
|
dtoverlay_debug("wrote %ld bytes to '%s'", len, filename);
|
|
fclose(fp);
|
|
}
|
|
else
|
|
{
|
|
dtoverlay_debug("failed to create '%s'", filename);
|
|
err = -1;
|
|
}
|
|
|
|
error_exit:
|
|
return err;
|
|
}
|
|
|
|
int dtoverlay_extend_dtb(DTBLOB_T *dtb, int new_size)
|
|
{
|
|
int size = fdt_totalsize(dtb->fdt);
|
|
int err = 0;
|
|
|
|
if (new_size < 0)
|
|
new_size = size - new_size;
|
|
|
|
if (new_size > size)
|
|
{
|
|
void *fdt;
|
|
fdt = malloc(new_size);
|
|
if (fdt)
|
|
{
|
|
memcpy(fdt, dtb->fdt, size);
|
|
fdt_set_totalsize(fdt, new_size);
|
|
|
|
if (dtb->fdt_is_malloced)
|
|
free(dtb->fdt);
|
|
|
|
dtb->fdt = fdt;
|
|
dtb->fdt_is_malloced = 1;
|
|
}
|
|
else
|
|
{
|
|
err = -FDT_ERR_NOSPACE;
|
|
}
|
|
}
|
|
else if (new_size < size)
|
|
{
|
|
/* Can't shrink it */
|
|
err = -FDT_ERR_NOSPACE;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
int dtoverlay_dtb_totalsize(DTBLOB_T *dtb)
|
|
{
|
|
return fdt_totalsize(dtb->fdt);
|
|
}
|
|
|
|
void dtoverlay_pack_dtb(DTBLOB_T *dtb)
|
|
{
|
|
fdt_pack(dtb->fdt);
|
|
}
|
|
|
|
void dtoverlay_free_dtb(DTBLOB_T *dtb)
|
|
{
|
|
if (dtb)
|
|
{
|
|
if (dtb->fdt_is_malloced)
|
|
free(dtb->fdt);
|
|
if (dtb->trailer_is_malloced)
|
|
free(dtb->trailer);
|
|
free(dtb);
|
|
}
|
|
}
|
|
|
|
int dtoverlay_find_phandle(DTBLOB_T *dtb, int phandle)
|
|
{
|
|
return fdt_node_offset_by_phandle(dtb->fdt, phandle);
|
|
}
|
|
|
|
int dtoverlay_find_symbol(DTBLOB_T *dtb, const char *symbol_name)
|
|
{
|
|
int symbols_off, path_len;
|
|
const char *node_path;
|
|
|
|
node_path = dtoverlay_get_alias(dtb, symbol_name);
|
|
|
|
if (node_path)
|
|
{
|
|
path_len = strlen(node_path);
|
|
}
|
|
else
|
|
{
|
|
symbols_off = fdt_path_offset(dtb->fdt, "/__symbols__");
|
|
|
|
if (symbols_off < 0)
|
|
{
|
|
dtoverlay_error("no symbols found");
|
|
return -FDT_ERR_NOTFOUND;
|
|
}
|
|
|
|
node_path = fdt_getprop(dtb->fdt, symbols_off, symbol_name, &path_len);
|
|
if (path_len < 0)
|
|
return -FDT_ERR_NOTFOUND;
|
|
|
|
//Ensure we don't have trailing NULLs
|
|
if (path_len > strnlen(node_path, path_len))
|
|
path_len = strnlen(node_path, path_len);
|
|
}
|
|
|
|
return fdt_path_offset_namelen(dtb->fdt, node_path, path_len);
|
|
}
|
|
|
|
int dtoverlay_find_matching_node(DTBLOB_T *dtb, const char **node_names,
|
|
int pos)
|
|
{
|
|
while (1)
|
|
{
|
|
const char *node_name;
|
|
pos = fdt_next_node(dtb->fdt, pos, NULL);
|
|
if (pos < 0)
|
|
break;
|
|
node_name = fdt_get_name(dtb->fdt, pos, NULL);
|
|
if (node_name)
|
|
{
|
|
int i;
|
|
for (i = 0; node_names[i]; i++)
|
|
{
|
|
const char *node = node_names[i];
|
|
int matchlen = strlen(node);
|
|
if ((strncmp(node_name, node, matchlen) == 0) &&
|
|
((node[matchlen] == '\0') ||
|
|
(node[matchlen] == '@')))
|
|
return pos;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int dtoverlay_node_is_enabled(DTBLOB_T *dtb, int pos)
|
|
{
|
|
if (pos >= 0)
|
|
{
|
|
const void *prop = dtoverlay_get_property(dtb, pos, "status", NULL);
|
|
if (prop &&
|
|
((strcmp((const char *)prop, "okay") == 0) ||
|
|
(strcmp((const char *)prop, "ok") == 0)))
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const void *dtoverlay_get_property(DTBLOB_T *dtb, int pos, const char *prop_name, int *prop_len)
|
|
{
|
|
return fdt_getprop(dtb->fdt, pos, prop_name, prop_len);
|
|
}
|
|
|
|
int dtoverlay_set_property(DTBLOB_T *dtb, int pos,
|
|
const char *prop_name, const void *prop, int prop_len)
|
|
{
|
|
int err = fdt_setprop(dtb->fdt, pos, prop_name, prop, prop_len);
|
|
if (err < 0)
|
|
dtoverlay_error("failed to set property '%s'", prop_name);
|
|
return err;
|
|
}
|
|
|
|
const char *dtoverlay_get_alias(DTBLOB_T *dtb, const char *alias_name)
|
|
{
|
|
int node_off;
|
|
int prop_len;
|
|
const char *alias;
|
|
|
|
node_off = fdt_path_offset(dtb->fdt, "/aliases");
|
|
|
|
alias = fdt_getprop(dtb->fdt, node_off, alias_name, &prop_len);
|
|
if (alias && !prop_len)
|
|
alias = "";
|
|
return alias;
|
|
}
|
|
|
|
int dtoverlay_set_alias(DTBLOB_T *dtb, const char *alias_name, const char *value)
|
|
{
|
|
int node_off;
|
|
|
|
node_off = fdt_path_offset(dtb->fdt, "/aliases");
|
|
if (node_off < 0)
|
|
node_off = fdt_add_subnode(dtb->fdt, 0, "aliases");
|
|
|
|
return fdt_setprop_string(dtb->fdt, node_off, alias_name, value);
|
|
}
|
|
|
|
void dtoverlay_set_logging_func(DTOVERLAY_LOGGING_FUNC *func)
|
|
{
|
|
dtoverlay_logging_func = func;
|
|
}
|
|
|
|
void dtoverlay_enable_debug(int enable)
|
|
{
|
|
dtoverlay_debug_enabled = enable;
|
|
}
|
|
|
|
void dtoverlay_error(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
(*dtoverlay_logging_func)(DTOVERLAY_ERROR, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void dtoverlay_warn(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
(*dtoverlay_logging_func)(DTOVERLAY_WARN, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
void dtoverlay_debug(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
if (dtoverlay_debug_enabled)
|
|
{
|
|
va_start(args, fmt);
|
|
(*dtoverlay_logging_func)(DTOVERLAY_DEBUG, fmt, args);
|
|
va_end(args);
|
|
}
|
|
}
|
|
|
|
static void dtoverlay_stdio_logging(dtoverlay_logging_type_t type,
|
|
const char *fmt, va_list args)
|
|
{
|
|
const char *type_str;
|
|
|
|
switch (type)
|
|
{
|
|
case DTOVERLAY_ERROR:
|
|
type_str = "error";
|
|
break;
|
|
|
|
case DTOVERLAY_WARN:
|
|
type_str = "warn";
|
|
break;
|
|
|
|
case DTOVERLAY_DEBUG:
|
|
type_str = "debug";
|
|
break;
|
|
|
|
default:
|
|
type_str = "?";
|
|
}
|
|
|
|
fprintf(stderr, "DTOVERLAY[%s]: ", type_str);
|
|
vfprintf(stderr, fmt, args);
|
|
fprintf(stderr, "\n");
|
|
}
|