mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2833 lines
80 KiB
2833 lines
80 KiB
/* |
|
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"); |
|
}
|
|
|