blob: acc5e40d5c40a2ee973a02823d4d21bd7c53aa55 [file] [log] [blame]
/*
* Copyright 2019, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <stdio.h>
#include <stdbool.h>
#include <libfdt.h>
#include <utils/list.h>
#include <utils/util.h>
#include <utils/uthash.h>
#include <fdtgen.h>
#define MAX_FULL_PATH_LENGTH (4096)
enum device_flag {
DEVICE_KEEP = 1,
DEVICE_KEEP_AND_DISABLE = 2,
};
typedef struct {
char *name;
int offset;
int cnt;
enum device_flag flag;
UT_hash_handle hh;
} path_node_t;
static const char *props_with_dep[] = {"phy-handle", "next-level-cache", "interrupt-parent", "interrupts-extended", "clocks", "power-domains"};
static const int num_props_with_dep = sizeof(props_with_dep) / sizeof(char *);
typedef struct {
char *to_path;
uint32_t to_phandle;
} d_list_node_t;
static int dnode_cmp(void *_a, void *_b)
{
d_list_node_t *a = _a, *b = _b;
return strcmp(a->to_path, b->to_path);
}
typedef struct {
char *from_path;
list_t *to_list;
UT_hash_handle hh;
} dependency_t;
struct fdtgen_context {
path_node_t *nodes_table;
dependency_t *dep_table;
int root_offset;
void *buffer;
int bufsize;
char *string_buf;
};
typedef struct fdtgen_context fdtgen_context_t;
static void init_keep_node(fdtgen_context_t *handle, const char **nodes, int num_nodes, enum device_flag flag)
{
for (int i = 0; i < num_nodes; ++i) {
path_node_t *this = NULL;
HASH_FIND_STR(handle->nodes_table, nodes[i], this);
if (this == NULL) {
path_node_t *new = malloc(sizeof(path_node_t));
new->name = strdup(nodes[i]);
new->flag = flag;
new->cnt = 0;
HASH_ADD_STR(handle->nodes_table, name, new);
} else {
this->flag = flag;
}
}
}
static int is_to_keep(fdtgen_context_t *handle, UNUSED int child)
{
void *dtb = handle->buffer;
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
return this != NULL;
}
static int retrive_to_phandle(const void *prop_data, int lenp)
{
uint32_t handle = fdt32_ld(prop_data);
return handle;
}
static void register_node_dependencies(fdtgen_context_t *handle, int offset);
static int keep_node_and_parents(fdtgen_context_t *handle, int offset, int target)
{
void *dtb = handle->buffer;
if (target == handle->root_offset) {
return 0;
}
if (target == offset) {
return 1;
}
int child;
fdt_for_each_subnode(child, dtb, offset) {
int new_len = strlen(handle->string_buf);
strcat(handle->string_buf, "/");
const char *n = fdt_get_name(dtb, child, NULL);
strcat(handle->string_buf, n);
char *curr = strdup(handle->string_buf);
int keep = keep_node_and_parents(handle, child, target);
if (keep) {
path_node_t *target_node = NULL;
HASH_FIND_STR(handle->nodes_table, curr, target_node);
if (target_node == NULL) {
target_node = malloc(sizeof(path_node_t));
target_node->name = strdup(curr);
target_node->offset = offset;
target_node->cnt = 0;
target_node->flag = DEVICE_KEEP;
HASH_ADD_STR(handle->nodes_table, name, target_node);
}
dependency_t *dep;
HASH_FIND_STR(handle->dep_table, curr, dep);
if (dep == NULL) {
register_node_dependencies(handle, child);
}
handle->string_buf[new_len] = '\0';
return 1;
}
handle->string_buf[new_len] = '\0';
free(curr);
}
return 0;
}
static void register_single_dependency(fdtgen_context_t *handle, int offset, int lenp, const void *data,
dependency_t *this)
{
void *dtb = handle->buffer;
d_list_node_t *new_node = malloc(sizeof(d_list_node_t));
uint32_t to_phandle = retrive_to_phandle(data, lenp);
int off = fdt_node_offset_by_phandle(dtb, to_phandle);
fdt_get_path(dtb, off, handle->string_buf, MAX_FULL_PATH_LENGTH);
new_node->to_path = strdup(handle->string_buf);
new_node->to_phandle = to_phandle;
// it is the same node when it refers to itself
if (offset == off || list_exists(this->to_list, new_node, dnode_cmp)) {
free(new_node->to_path);
free(new_node);
} else {
list_append(this->to_list, new_node);
handle->string_buf[0] = '\0';
keep_node_and_parents(handle, handle->root_offset, off);
register_node_dependencies(handle, off);
}
}
static void register_clocks_dependency(fdtgen_context_t *handle, int offset, int lenp, const void *data_,
dependency_t *this)
{
void *dtb = handle->buffer;
const void *data = data_;
int done = 0;
while (lenp > done) {
data = (data_ + done);
int phandle = fdt32_ld(data);
int refers_to = fdt_node_offset_by_phandle(dtb, phandle);
int len;
const void *clock_cells = fdt_getprop(dtb, refers_to, "#clock-cells", &len);
int cells = fdt32_ld(clock_cells);
register_single_dependency(handle, offset, lenp, data, this);
done += 4 + cells * 4;
}
}
static void register_power_domains_dependency(fdtgen_context_t *handle, int offset, int lenp, const void *data_,
dependency_t *this)
{
void *dtb = handle->buffer;
const void *data = data_;
int done = 0;
while (lenp > done) {
data = (data_ + done);
register_single_dependency(handle, offset, lenp, data, this);
done += 4;
}
}
static void register_node_dependency(fdtgen_context_t *handle, int offset, const char *type, int p_offset)
{
void *dtb = handle->buffer;
int lenp = 0;
const void *data = fdt_getprop_by_offset(dtb, p_offset, NULL, &lenp);
fdt_get_path(dtb, offset, handle->string_buf, MAX_FULL_PATH_LENGTH);
dependency_t *this = NULL;
HASH_FIND_STR(handle->dep_table, handle->string_buf, this);
if (this == NULL) {
dependency_t *new = malloc(sizeof(dependency_t));
new->from_path = strdup(handle->string_buf);
new->to_list = malloc(sizeof(list_t));
list_init(new->to_list);
this = new;
HASH_ADD_STR(handle->dep_table, from_path, this);
}
if (strcmp(type, "clocks") == 0) {
register_clocks_dependency(handle, offset, lenp, data, this);
} else if (strcmp(type, "power-domains") == 0) {
register_power_domains_dependency(handle, offset, lenp, data, this);
} else {
register_single_dependency(handle, offset, lenp, data, this);
}
}
static void register_node_dependencies(fdtgen_context_t *handle, int offset)
{
if (offset == handle->root_offset) {
return;
}
int prop_off, lenp;
void *dtb = handle->buffer;
fdt_for_each_property_offset(prop_off, dtb, offset) {
const struct fdt_property *prop = fdt_get_property_by_offset(dtb, prop_off, NULL);
const char *name = fdt_get_string(dtb, fdt32_ld(&prop->nameoff), &lenp);
for (int i = 0; i < num_props_with_dep; i++) {
if (strcmp(name, props_with_dep[i]) == 0) {
register_node_dependency(handle, offset, name, prop_off);
}
}
}
}
static void resolve_all_dependencies(fdtgen_context_t *handle)
{
path_node_t *tmp, *el;
HASH_ITER(hh, handle->nodes_table, el, tmp) {
register_node_dependencies(handle, el->offset);
}
}
/*
* prefix traverse the device tree
* keep the parent if the child is kept
*/
static int find_nodes_to_keep(fdtgen_context_t *handle, int offset)
{
void *dtb = handle->buffer;
int child;
int find = 0;
fdt_for_each_subnode(child, dtb, offset) {
int len_ori = strlen(handle->string_buf);
strcat(handle->string_buf, "/");
const char *n = fdt_get_name(dtb, child, NULL);
strcat(handle->string_buf, n);
int child_is_kept = find_nodes_to_keep(handle, child);
int in_keep_list = 0;
if (child_is_kept == 0) {
in_keep_list = is_to_keep(handle, child);
}
if (in_keep_list || child_is_kept) {
find = 1;
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
if (this == NULL) { /* this is not in the keep list */
path_node_t *new = malloc(sizeof(path_node_t));
new->offset = child;
new->name = strdup(handle->string_buf);
new->cnt = 0;
new->flag = DEVICE_KEEP;
HASH_ADD_STR(handle->nodes_table, name, new);
} else {
this->offset = child;
}
}
handle->string_buf[len_ori] = '\0';
}
return find;
}
static void trim_tree(fdtgen_context_t *handle, int offset)
{
int child;
void *dtb = handle->buffer;
fdt_for_each_subnode(child, dtb, offset) {
int len_ori = strlen(handle->string_buf);
const char *n = fdt_get_name(dtb, child, NULL);
strcat(handle->string_buf, "/");
strcat(handle->string_buf, n);
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
if (this == NULL) {
int err = fdt_del_node(dtb, child);
ZF_LOGF_IF(err != 0, "Failed to delete a node from device tree");
/* NOTE: after deleting a node, all the offsets are invalidated,
* we need to repeat this triming process for the same node if
* we don't want to miss anything */
handle->string_buf[len_ori] = '\0';
trim_tree(handle, offset);
return;
} else {
this->cnt++;
if (this->flag == DEVICE_KEEP_AND_DISABLE && this->cnt == 1) {
int err = fdt_setprop_string(dtb, child, "status", "disabled");
ZF_LOGF_IF(err, "failed, %d", err);
handle->string_buf[len_ori] = '\0';
trim_tree(handle, offset);
return;
} else {
trim_tree(handle, child);
}
}
handle->string_buf[len_ori] = '\0';
}
}
static void free_list(list_t *l)
{
struct list_node *a = l->head;
while (a != NULL) {
struct list_node *next = a->next;
d_list_node_t *node = a->data;
free(node->to_path);
free(node);
a = next;
}
list_remove_all(l);
}
static void clean_up(fdtgen_context_t *handle)
{
dependency_t *tmp, *el;
HASH_ITER(hh, handle->dep_table, el, tmp) {
HASH_DEL(handle->dep_table, el);
free_list(el->to_list);
free(el->to_list);
free(el->from_path);
free(el);
}
path_node_t *tmp1, *el1;
HASH_ITER(hh, handle->nodes_table, el1, tmp1) {
if (el1->cnt == 0) {
ZF_LOGE("Non-existing node %s specified to be kept", el1->name);
}
HASH_DEL(handle->nodes_table, el1);
free(el1->name);
free(el1);
}
free(handle->string_buf);
}
void fdtgen_keep_nodes(fdtgen_context_t *handle, const char **nodes_to_keep, int num_nodes)
{
init_keep_node(handle, nodes_to_keep, num_nodes, DEVICE_KEEP);
}
void fdtgen_keep_nodes_and_disable(fdtgen_context_t *handle, const char **nodes_to_keep, int num_nodes)
{
init_keep_node(handle, nodes_to_keep, num_nodes, DEVICE_KEEP_AND_DISABLE);
}
static void keep_node_and_children(fdtgen_context_t *handle, const void *ori_fdt, int offset, enum device_flag flag)
{
int child;
fdt_for_each_subnode(child, ori_fdt, offset) {
int len_ori = strlen(handle->string_buf);
strcat(handle->string_buf, "/");
const char *n = fdt_get_name(ori_fdt, child, NULL);
strcat(handle->string_buf, n);
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
if (this == NULL) {
path_node_t *new = malloc(sizeof(path_node_t));
new->name = strdup(handle->string_buf);
new->cnt = 0;
new->flag = flag;
HASH_ADD_STR(handle->nodes_table, name, new);
} else {
this->flag = flag;
}
keep_node_and_children(handle, ori_fdt, child, flag);
handle->string_buf[len_ori] = '\0';
}
}
void fdtgen_keep_node_subtree_disable(fdtgen_context_t *handle, const void *ori_fdt, const char *node)
{
int child = fdt_path_offset(ori_fdt, node);
if (child < 0) {
ZF_LOGE("Non-existing root node %s", node);
} else {
fdt_get_path(ori_fdt, child, handle->string_buf, MAX_FULL_PATH_LENGTH);
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
if (this == NULL) {
path_node_t *new = malloc(sizeof(path_node_t));
new->name = strdup(handle->string_buf);
new->cnt = 0;
new->flag = DEVICE_KEEP_AND_DISABLE;
HASH_ADD_STR(handle->nodes_table, name, new);
} else {
this->flag = DEVICE_KEEP_AND_DISABLE;
}
keep_node_and_children(handle, ori_fdt, child, DEVICE_KEEP_AND_DISABLE);
}
}
void fdtgen_keep_node_subtree(fdtgen_context_t *handle, const void *ori_fdt, const char *node)
{
int child = fdt_path_offset(ori_fdt, node);
if (child < 0) {
ZF_LOGE("Non-existing root node %s", node);
} else {
fdt_get_path(ori_fdt, child, handle->string_buf, MAX_FULL_PATH_LENGTH);
path_node_t *this;
HASH_FIND_STR(handle->nodes_table, handle->string_buf, this);
if (this == NULL) {
path_node_t *new = malloc(sizeof(path_node_t));
new->name = strdup(handle->string_buf);
new->cnt = 0;
new->flag = DEVICE_KEEP;
HASH_ADD_STR(handle->nodes_table, name, new);
} else {
this->flag = DEVICE_KEEP;
}
keep_node_and_children(handle, ori_fdt, child, DEVICE_KEEP);
}
}
int fdtgen_generate(fdtgen_context_t *handle, const void *fdt_ori)
{
if (handle == NULL) {
return -1;
}
void *fdt_gen = handle->buffer;
fdt_open_into(fdt_ori, fdt_gen, handle->bufsize);
/* just make sure the device tree is valid */
int rst = fdt_check_full(fdt_gen, handle->bufsize);
if (rst != 0) {
ZF_LOGE("The original fdt is illegal : %d", rst);
return -1;
}
/* in case the root node is not at 0 offset.
* is that possible? */
handle->root_offset = fdt_path_offset(fdt_gen, "/");
handle->string_buf[0] = '\0';
find_nodes_to_keep(handle, handle->root_offset);
resolve_all_dependencies(handle);
// always keep the root node
path_node_t *root = malloc(sizeof(path_node_t));
root->name = strdup("/");
root->offset = handle->root_offset;
root->cnt = 1;
root->flag = DEVICE_KEEP;
HASH_ADD_STR(handle->nodes_table, name, root);
handle->string_buf[0] = '\0';
trim_tree(handle, handle->root_offset);
rst = fdt_check_full(fdt_gen, handle->bufsize);
if (rst != 0) {
ZF_LOGE("The generated fdt is illegal");
return -1;
}
return 0;
}
fdtgen_context_t *fdtgen_new_context(void *buf, size_t bufsize)
{
fdtgen_context_t *to_return = malloc(sizeof(fdtgen_context_t));
if (to_return == NULL) {
return NULL;
}
to_return->buffer = buf;
to_return->bufsize = bufsize;
to_return->nodes_table = NULL;
to_return->dep_table = NULL;
to_return->root_offset = 0;
to_return->string_buf = malloc(MAX_FULL_PATH_LENGTH);
return to_return;
}
void fdtgen_free_context(fdtgen_context_t *h)
{
if (h) {
clean_up(h);
free(h);
}
}