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.
472 lines
16 KiB
472 lines
16 KiB
/*
|
|
* Copyright (C) 2018 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <string>
|
|
#include <unordered_map>
|
|
|
|
extern "C" {
|
|
|
|
#include "libufdt.h"
|
|
#include "ufdt_node_pool.h"
|
|
#include "ufdt_overlay.h"
|
|
#include "ufdt_overlay_internal.h"
|
|
|
|
}
|
|
|
|
#include "ufdt_test_overlay.h"
|
|
|
|
static bool ufdt_node_compare(struct ufdt_node *node_a, struct ufdt_node *node_b,
|
|
struct ufdt* tree_a, struct ufdt* tree_b);
|
|
|
|
/*
|
|
* Helper method to check if the tree rooted at node_b is a subset of the tree rooted
|
|
* at node_a.
|
|
*/
|
|
static bool compare_child_nodes(struct ufdt_node *node_a, struct ufdt_node *node_b,
|
|
struct ufdt * tree_a, struct ufdt * tree_b) {
|
|
bool result = true;
|
|
struct ufdt_node *it;
|
|
|
|
for (it = ((struct ufdt_node_fdt_node *)node_b)->child; it; it = it->sibling) {
|
|
struct ufdt_node *cur_node = it;
|
|
struct ufdt_node *target_node = NULL;
|
|
|
|
if (ufdt_node_tag(cur_node) == FDT_BEGIN_NODE) {
|
|
target_node =
|
|
ufdt_node_get_subnode_by_name(node_a, ufdt_node_name(cur_node));
|
|
} else {
|
|
target_node =
|
|
ufdt_node_get_property_by_name(node_a, ufdt_node_name(cur_node));
|
|
}
|
|
|
|
if (target_node == NULL) {
|
|
result = false;
|
|
} else {
|
|
result = ufdt_node_compare(target_node, cur_node, tree_a, tree_b);
|
|
}
|
|
|
|
if (!result) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Method to compare two nodes with tag FDT_PROP. Also accounts for the cases where
|
|
* the property type is phandle.
|
|
*/
|
|
static bool ufdt_compare_property(struct ufdt_node* node_final, struct ufdt_node* node_overlay,
|
|
struct ufdt* tree_final, struct ufdt* tree_overlay) {
|
|
if (ufdt_node_tag(node_final) == FDT_PROP) {
|
|
/* Return -1 if property names are differemt */
|
|
if (strcmp(ufdt_node_name(node_final), ufdt_node_name(node_overlay)) != 0)
|
|
return false;
|
|
|
|
int length_data_final = 0, length_data_overlay = 0;
|
|
char *prop_data_final = ufdt_node_get_fdt_prop_data(node_final, &length_data_final);
|
|
char *prop_data_overlay = ufdt_node_get_fdt_prop_data(node_overlay,
|
|
&length_data_overlay);
|
|
|
|
/* Confirm length for the property values are the same */
|
|
if (length_data_final != length_data_overlay) {
|
|
return false;
|
|
}
|
|
|
|
if (((length_data_final == 0) && (length_data_overlay ==0)) ||
|
|
(memcmp(prop_data_final, prop_data_overlay, length_data_final) == 0)) {
|
|
// Return if the properties have same value.
|
|
return true;
|
|
} else {
|
|
/* check for the presence of phandles */
|
|
for (int i = 0; i < length_data_final; i += sizeof(fdt32_t)) {
|
|
int offset_data_a = fdt32_to_cpu(
|
|
*reinterpret_cast<fdt32_t *>(prop_data_final + i));
|
|
int offset_data_b = fdt32_to_cpu(
|
|
*reinterpret_cast<fdt32_t *>(prop_data_overlay + i));
|
|
if (offset_data_a == offset_data_b) continue;
|
|
/* If the offsets have phandles, they would have valid target nodes */
|
|
struct ufdt_node * target_node_a = ufdt_get_node_by_phandle(tree_final,
|
|
offset_data_a);
|
|
struct ufdt_node * target_node_b = ufdt_get_node_by_phandle(tree_overlay,
|
|
offset_data_b);
|
|
|
|
/*
|
|
* verify that the target nodes are valid and point to the same node.
|
|
*/
|
|
if ((target_node_a == NULL) || (target_node_b == NULL) ||
|
|
strcmp(ufdt_node_name(target_node_a),
|
|
ufdt_node_name(target_node_b)) != 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Checks if the ufdt tree rooted at node_b is a subtree of the tree rooted at
|
|
* node_a.
|
|
*/
|
|
static bool ufdt_node_compare(struct ufdt_node *node_final, struct ufdt_node *node_overlay,
|
|
struct ufdt * tree_final, struct ufdt * tree_overlay) {
|
|
if (ufdt_node_tag(node_final) == FDT_PROP) {
|
|
return ufdt_compare_property(node_final, node_overlay, tree_final, tree_overlay);
|
|
}
|
|
|
|
return compare_child_nodes(node_final, node_overlay, tree_final, tree_overlay);
|
|
}
|
|
|
|
|
|
/*
|
|
* Multiple fragments may fixup to the same node on the base device tree.
|
|
* Combine these fragments for easier verification.
|
|
*/
|
|
void ufdt_combine_fixup(struct ufdt *tree, const char *fixup,
|
|
struct ufdt_node **prev_node, struct ufdt_node_pool *node_pool) {
|
|
char *path, *prop_ptr, *offset_ptr;
|
|
char path_buf[1024];
|
|
char *path_mem = NULL;
|
|
int result = 0;
|
|
|
|
size_t fixup_len = strlen(fixup) + 1;
|
|
if (fixup_len > sizeof(path_buf)) {
|
|
path_mem = static_cast<char *>(dto_malloc(fixup_len));
|
|
path = path_mem;
|
|
} else {
|
|
path = path_buf;
|
|
}
|
|
dto_memcpy(path, fixup, fixup_len);
|
|
|
|
prop_ptr = dto_strchr(path, ':');
|
|
if (prop_ptr == NULL) {
|
|
dto_error("Missing property part in '%s'\n", path);
|
|
goto fail;
|
|
}
|
|
|
|
*prop_ptr = '\0';
|
|
prop_ptr++;
|
|
|
|
offset_ptr = dto_strchr(prop_ptr, ':');
|
|
if (offset_ptr == NULL) {
|
|
dto_error("Missing offset part in '%s'\n", path);
|
|
goto fail;
|
|
}
|
|
|
|
*offset_ptr = '\0';
|
|
offset_ptr++;
|
|
|
|
result = dto_strcmp(prop_ptr, "target");
|
|
/* If the property being fixed up is not target, ignore and return */
|
|
if (result == 0) {
|
|
struct ufdt_node *target_node;
|
|
target_node = ufdt_get_node_by_path(tree, path);
|
|
if (target_node == NULL) {
|
|
dto_error("Path '%s' not found\n", path);
|
|
} else {
|
|
/* The goal is to combine fragments that have a common target */
|
|
if (*prev_node != NULL) {
|
|
ufdt_node_merge_into(*prev_node, target_node, node_pool);
|
|
} else {
|
|
*prev_node = target_node;
|
|
}
|
|
}
|
|
}
|
|
|
|
fail:
|
|
if (path_mem) {
|
|
dto_free(path_mem);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Creates a table of node paths to their corresponding phandles by walking
|
|
* through the 'symbols' node of the main device tree. The table would be
|
|
* used in combining overlay nodes that map to the same nodes in the
|
|
* main device tree.
|
|
*/
|
|
void create_path_phandle_map(std::unordered_map<uint32_t, std::string>* phandle_path_map,
|
|
struct ufdt* main_tree) {
|
|
int len = 0;
|
|
struct ufdt_node *main_symbols_node =
|
|
ufdt_get_node_by_path(main_tree, "/__symbols__");
|
|
if (!main_symbols_node) {
|
|
dto_error("No node __symbols__ in main dtb.\n");
|
|
return;
|
|
}
|
|
|
|
struct ufdt_node **it = nullptr;
|
|
for_each_prop(it, main_symbols_node) {
|
|
const char* symbol_path = ufdt_node_get_fdt_prop_data(*it, &len);
|
|
struct ufdt_node* symbol_node = ufdt_get_node_by_path(main_tree, symbol_path);
|
|
uint32_t phandle = ufdt_node_get_phandle(symbol_node);
|
|
(*phandle_path_map)[phandle] = std::string(symbol_path);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Recursively checks whether a node from another overlay fragment had overlaid the
|
|
* target node and if so merges the node into the previous node.
|
|
*/
|
|
static void combine_overlay_node(std::unordered_map<std::string,
|
|
struct ufdt_node*>* path_node_map,
|
|
std::string path,
|
|
struct ufdt_node* node,
|
|
struct ufdt_node_pool* pool) {
|
|
struct ufdt_node **it = nullptr;
|
|
for_each_node(it, node) {
|
|
//skips properties
|
|
if (ufdt_node_tag(*it) == FDT_BEGIN_NODE) {
|
|
combine_overlay_node(path_node_map, path + "/" + ufdt_node_name(*it), *it, pool);
|
|
}
|
|
}
|
|
|
|
if (path_node_map->find(path) != path_node_map->end()) {
|
|
ufdt_node_merge_into((*path_node_map)[path], node, pool);
|
|
} else {
|
|
//This is the first node overlaying the target node, add the same to the
|
|
//table.
|
|
(*path_node_map)[path] = node;
|
|
}
|
|
}
|
|
|
|
/* END of doing fixup in the overlay ufdt. */
|
|
|
|
static bool ufdt_verify_overlay_node(struct ufdt_node *target_node,
|
|
struct ufdt_node *overlay_node,
|
|
struct ufdt * target_tree,
|
|
struct ufdt * overlay_tree) {
|
|
return ufdt_node_compare(target_node, overlay_node, target_tree, overlay_tree);
|
|
}
|
|
|
|
/*
|
|
* verify one overlay fragment (subtree).
|
|
*/
|
|
static int ufdt_verify_fragment(struct ufdt *tree,
|
|
struct ufdt *overlay_tree,
|
|
struct ufdt_node *frag_node) {
|
|
struct ufdt_node *target_node = NULL;
|
|
struct ufdt_node *overlay_node = NULL;
|
|
enum overlay_result target_search_result = ufdt_overlay_get_target(tree, frag_node,
|
|
&target_node);
|
|
if (target_node == NULL) {
|
|
return target_search_result;
|
|
}
|
|
|
|
overlay_node = ufdt_node_get_node_by_path(frag_node, "__overlay__");
|
|
if (overlay_node == NULL) {
|
|
dto_error("missing __overlay__ sub-node\n");
|
|
return OVERLAY_RESULT_MISSING_OVERLAY;
|
|
}
|
|
|
|
bool result = ufdt_verify_overlay_node(target_node, overlay_node, tree, overlay_tree);
|
|
|
|
if (!result) {
|
|
dto_error("failed to verify overlay node %s to target %s\n",
|
|
ufdt_node_name(overlay_node), ufdt_node_name(target_node));
|
|
return OVERLAY_RESULT_VERIFY_FAIL;
|
|
}
|
|
|
|
return OVERLAY_RESULT_OK;
|
|
}
|
|
|
|
/*
|
|
* verify each fragment in overlay.
|
|
*/
|
|
static int ufdt_overlay_verify_fragments(struct ufdt *final_tree,
|
|
struct ufdt *overlay_tree) {
|
|
enum overlay_result err;
|
|
struct ufdt_node **it;
|
|
for_each_node(it, overlay_tree->root) {
|
|
err = static_cast<enum overlay_result>(ufdt_verify_fragment(final_tree, overlay_tree,
|
|
*it));
|
|
if (err == OVERLAY_RESULT_VERIFY_FAIL) {
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Examine target nodes for fragments in all overlays and combine ones with the
|
|
* same target.
|
|
*/
|
|
static void ufdt_overlay_combine_common_nodes(struct ufdt** overlay_trees,
|
|
size_t overlay_count,
|
|
struct ufdt* final_tree,
|
|
struct ufdt_node_pool* pool
|
|
) {
|
|
std::unordered_map<std::string, struct ufdt_node*> path_node_map;
|
|
std::unordered_map<uint32_t, std::string> phandle_path_map;
|
|
|
|
create_path_phandle_map(&phandle_path_map, final_tree);
|
|
|
|
struct ufdt_node **it = nullptr;
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
for_each_node(it, overlay_trees[i]->root) {
|
|
uint32_t target = 0;
|
|
const void* val = ufdt_node_get_fdt_prop_data_by_name(*it, "target", NULL);
|
|
if (val) {
|
|
dto_memcpy(&target, val, sizeof(target));
|
|
target = fdt32_to_cpu(target);
|
|
std::string path = phandle_path_map[target];
|
|
struct ufdt_node* overlay_node = ufdt_node_get_node_by_path(*it, "__overlay__");
|
|
if (overlay_node != nullptr) {
|
|
combine_overlay_node(&path_node_map, path, overlay_node, pool);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Makes sure that all phandles in the overlays are unique since they will be
|
|
* combined before verification.
|
|
*/
|
|
int ufdt_resolve_duplicate_phandles(ufdt** overlay_tree, size_t overlay_count) {
|
|
size_t phandle_offset = 0;
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
ufdt_try_increase_phandle(overlay_tree[i], phandle_offset);
|
|
if (ufdt_overlay_do_local_fixups(overlay_tree[i], phandle_offset) < 0) {
|
|
return -1;
|
|
}
|
|
phandle_offset = ufdt_get_max_phandle(overlay_tree[i]);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Combines all overlays into a single tree at overlay_trees[0]
|
|
*/
|
|
int ufdt_combine_all_overlays(struct ufdt** overlay_trees, size_t overlay_count,
|
|
struct ufdt* final_tree, struct ufdt_node_pool* pool) {
|
|
struct ufdt* combined_overlay_tree = nullptr;
|
|
|
|
if (!overlay_trees || !overlay_count || !final_tree || !pool) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* If there are duplicate phandles amongst the overlays, replace them with
|
|
* unique ones.
|
|
*/
|
|
if (ufdt_resolve_duplicate_phandles(overlay_trees, overlay_count) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* For each overlay, perform fixup for each fragment.
|
|
*/
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
if (ufdt_overlay_do_fixups(final_tree, overlay_trees[i]) < 0) {
|
|
dto_error("failed to perform fixups in overlay\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Iterate through each overlay and combine all nodes with the same target
|
|
* node.
|
|
*/
|
|
ufdt_overlay_combine_common_nodes(overlay_trees, overlay_count, final_tree, pool);
|
|
|
|
/*
|
|
* Combine all overlays into the tree at overlay_trees[0] for easy
|
|
* verification.
|
|
*/
|
|
combined_overlay_tree = overlay_trees[0];
|
|
struct ufdt_node* combined_root_node = combined_overlay_tree->root;
|
|
|
|
for (size_t i = 1; i < overlay_count; i++) {
|
|
struct ufdt_node** it = nullptr;
|
|
struct ufdt_node* root_node = overlay_trees[i]->root;
|
|
for_each_node(it, root_node) {
|
|
ufdt_node_add_child(combined_root_node, *it);
|
|
}
|
|
((struct ufdt_node_fdt_node *)root_node)->child = nullptr;
|
|
}
|
|
|
|
/*
|
|
* Rebuild the phandle_table for the combined tree.
|
|
*/
|
|
combined_overlay_tree->phandle_table = build_phandle_table(combined_overlay_tree);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ufdt_verify_dtbo(struct fdt_header* final_fdt_header,
|
|
size_t final_fdt_size, void** overlay_arr,
|
|
size_t overlay_count) {
|
|
const size_t min_fdt_size = 8;
|
|
struct ufdt_node_pool pool;
|
|
struct ufdt* final_tree = nullptr;
|
|
struct ufdt** overlay_trees = nullptr;
|
|
int result = 1;
|
|
|
|
if (final_fdt_header == NULL) {
|
|
goto fail;
|
|
}
|
|
|
|
if (final_fdt_size < min_fdt_size || final_fdt_size != fdt_totalsize(final_fdt_header)) {
|
|
dto_error("Bad fdt size!\n");
|
|
goto fail;
|
|
}
|
|
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
if ((fdt_magic(overlay_arr[i]) != FDT_MAGIC) || !fdt_totalsize(overlay_arr[i])) {
|
|
dto_error("Corrupted or empty overlay\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
ufdt_node_pool_construct(&pool);
|
|
final_tree = ufdt_from_fdt(final_fdt_header, final_fdt_size, &pool);
|
|
|
|
overlay_trees = new ufdt*[overlay_count];
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
size_t fdt_size = fdt_totalsize(overlay_arr[i]);
|
|
overlay_trees[i] = ufdt_from_fdt(overlay_arr[i], fdt_size, &pool);
|
|
}
|
|
|
|
if (ufdt_combine_all_overlays(overlay_trees, overlay_count, final_tree, &pool) < 0) {
|
|
dto_error("Unable to combine overlays\n");
|
|
goto fail;
|
|
}
|
|
if (ufdt_overlay_verify_fragments(final_tree, overlay_trees[0]) < 0) {
|
|
dto_error("Failed to verify overlay application\n");
|
|
goto fail;
|
|
} else {
|
|
result = 0;
|
|
}
|
|
|
|
fail:
|
|
if (overlay_trees) {
|
|
for (size_t i = 0; i < overlay_count; i++) {
|
|
ufdt_destruct(overlay_trees[i], &pool);
|
|
}
|
|
delete[] overlay_trees;
|
|
}
|
|
|
|
ufdt_destruct(final_tree, &pool);
|
|
ufdt_node_pool_destruct(&pool);
|
|
return result;
|
|
}
|