blob: c0956e8ac0424c5d0ecbca77d8f4ad0197a80a0d [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Provide access to the Chrome OS master configuration from device tree
#include "chromeos-config/libcros_config/cros_config_fdt.h"
// TODO(sjg@chromium.org): See if this can be accepted upstream.
extern "C" {
#include <libfdt.h>
};
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/strings/string_split.h>
#include <base/strings/string_util.h>
namespace {
const char kTargetDirsPath[] = "/chromeos/schema/target-dirs";
const char kSchemaPath[] = "/chromeos/schema";
const char kPhandleProperties[] = "phandle-properties";
} // namespace
namespace brillo {
ConfigNode::ConfigNode() : valid_(false), node_offset_(-1) {}
ConfigNode::ConfigNode(int offset) {
valid_ = true;
node_offset_ = offset;
}
int ConfigNode::GetOffset() const {
if (!valid_) {
return -1;
}
return node_offset_;
}
bool ConfigNode::operator==(const ConfigNode& other) const {
if (valid_ != other.valid_) {
return false;
}
return node_offset_ == other.node_offset_;
}
CrosConfigFdt::CrosConfigFdt() {}
CrosConfigFdt::~CrosConfigFdt() {}
std::string CrosConfigFdt::GetFullPath(const ConfigNode& node) {
const void* blob = blob_.c_str();
char buf[256];
int err;
err = fdt_get_path(blob, node.GetOffset(), buf, sizeof(buf));
if (err) {
CROS_CONFIG_LOG(WARNING) << "Cannot get full path: " << fdt_strerror(err);
return "unknown";
}
return std::string(buf);
}
ConfigNode CrosConfigFdt::GetPathNode(const ConfigNode& base_node,
const std::string& path) {
const void* blob = blob_.c_str();
auto parts = base::SplitString(path.substr(1), "/", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
int node = base_node.GetOffset();
for (auto part : parts) {
node = fdt_subnode_offset(blob, node, part.c_str());
if (node < 0) {
break;
}
}
return ConfigNode(node);
}
bool CrosConfigFdt::LookupPhandle(const ConfigNode& node,
const std::string& prop_name,
ConfigNode* node_out) {
const void* blob = blob_.c_str();
int len;
const fdt32_t* ptr = static_cast<const fdt32_t*>(
fdt_getprop(blob, node.GetOffset(), prop_name.c_str(), &len));
// We probably don't need all these checks since validation will ensure that
// the config is correct. But this is a critical tool and we want to avoid
// crashes in any situation.
*node_out = ConfigNode();
if (!ptr) {
return false;
}
if (len != sizeof(fdt32_t)) {
CROS_CONFIG_LOG(ERROR) << prop_name << " phandle for model " << model_name_
<< " is of size " << len << " but should be "
<< sizeof(fdt32_t);
return false;
}
int phandle = fdt32_to_cpu(*ptr);
int target_node = fdt_node_offset_by_phandle(blob, phandle);
if (target_node < 0) {
CROS_CONFIG_LOG(ERROR) << prop_name << "lookup for model " << model_name_
<< " failed: " << fdt_strerror(target_node);
return false;
}
*node_out = ConfigNode(target_node);
return true;
}
int CrosConfigFdt::FindIDsInMap(int node,
const std::string& find_name,
int find_sku_id,
std::string* platform_name_out) {
const void* blob = blob_.c_str();
VLOG(1) << "Trying " << fdt_get_name(blob, node, NULL);
const char* smbios_name = static_cast<const char*>(
fdt_getprop(blob, node, "smbios-name-match", NULL));
if (smbios_name &&
(find_name.empty() || strcmp(smbios_name, find_name.c_str()))) {
CROS_CONFIG_LOG(INFO) << "SMBIOS name " << smbios_name << " does not match "
<< find_name;
return 0;
}
// If we have a single SKU, deal with that first
int len = 0;
const fdt32_t* data =
(const fdt32_t*)fdt_getprop(blob, node, "single-sku", &len);
int found_phandle = 0;
if (data) {
if (len != sizeof(fdt32_t)) {
CROS_CONFIG_LOG(ERROR) << "single-sku: Invalid length " << len;
return -1;
}
found_phandle = fdt32_to_cpu(*data);
CROS_CONFIG_LOG(INFO) << "Single SKU match";
} else {
// Locate the map and make sure it is a multiple of 2 cells (first is SKU
// ID, second is phandle).
const fdt32_t *data, *end, *ptr;
data = static_cast<const fdt32_t*>(
fdt_getprop(blob, node, "simple-sku-map", &len));
if (!data) {
CROS_CONFIG_LOG(ERROR)
<< "Cannot find simple-sku-map: " << fdt_strerror(node);
return -1;
}
if (len % (sizeof(fdt32_t) * 2)) {
// Validation of configuration should catch this, so this should never
// happen. But we don't want to crash.
CROS_CONFIG_LOG(ERROR)
<< "single-sku-map: " << fdt_get_name(blob, node, NULL)
<< " invalid length " << len;
return -1;
}
// Search for the SKU ID in the list.
for (end = data + len / sizeof(fdt32_t), ptr = data; ptr < end; ptr += 2) {
int sku_id = fdt32_to_cpu(ptr[0]);
int phandle = fdt32_to_cpu(ptr[1]);
if (sku_id == find_sku_id) {
found_phandle = phandle;
break;
}
}
if (!found_phandle) {
VLOG(1) << "SKU ID " << find_sku_id << " not found in mapping";
return 0;
}
CROS_CONFIG_LOG(INFO) << "Simple SKU map match ";
}
const char* pname =
static_cast<const char*>(fdt_getprop(blob, node, "platform-name", NULL));
if (pname)
*platform_name_out = pname;
else
*platform_name_out = "unknown";
CROS_CONFIG_LOG(INFO) << "Platform name " << *platform_name_out;
return found_phandle;
}
int CrosConfigFdt::FindIDsInAllMaps(int mapping_node,
const std::string& find_name,
int find_sku_id,
std::string* platform_name_out) {
const void* blob = blob_.c_str();
int subnode;
fdt_for_each_subnode(subnode, blob, mapping_node) {
int phandle =
FindIDsInMap(subnode, find_name, find_sku_id, platform_name_out);
if (phandle < 0) {
return -1;
} else if (phandle > 0) {
return phandle;
}
}
return 0;
}
int CrosConfigFdt::FollowPhandle(int phandle, int* target_out) {
const void* blob = blob_.c_str();
// Follow the phandle to the target
int node = fdt_node_offset_by_phandle(blob, phandle);
if (node < 0) {
CROS_CONFIG_LOG(ERROR) << "Cannot find phandle for sku ID: "
<< fdt_strerror(node);
return -1;
}
// Figure out whether the target is a model or a sub-model
int parent = fdt_parent_offset(blob, node);
if (parent < 0) {
CROS_CONFIG_LOG(ERROR) << "Cannot find parent of phandle target: "
<< fdt_strerror(node);
return -1;
}
const char* parent_name = fdt_get_name(blob, parent, NULL);
int model_node;
if (!strcmp(parent_name, "submodels")) {
model_node = fdt_parent_offset(blob, parent);
if (model_node < 0) {
CROS_CONFIG_LOG(ERROR)
<< "Cannot find sub-model parent: " << fdt_strerror(node);
return -1;
}
} else if (!strcmp(parent_name, "models")) {
model_node = node;
} else {
CROS_CONFIG_LOG(ERROR) << "Phandle target parent " << parent_name
<< " is invalid";
return -1;
}
*target_out = node;
return model_node;
}
bool CrosConfigFdt::SelectConfigByIdentityArm(
const CrosConfigIdentityArm& identity) {
CROS_CONFIG_LOG(ERROR) << "ARM is not supported for the FDT impl.";
return false;
}
bool CrosConfigFdt::SelectConfigByIdentityX86(
const CrosConfigIdentityX86& identity) {
const std::string& find_name = identity.GetName();
int find_sku_id = identity.GetSkuId();
const std::string& find_whitelabel_name = identity.GetVpdId();
const void* blob = blob_.c_str();
CROS_CONFIG_LOG(INFO) << "Looking up name " << find_name << ", SKU ID "
<< find_sku_id;
int mapping_node = fdt_path_offset(blob, "/chromeos/family/mapping");
if (mapping_node < 0) {
CROS_CONFIG_LOG(ERROR) << "Cannot find mapping node: "
<< fdt_strerror(mapping_node);
return false;
}
std::string platform_name;
int phandle =
FindIDsInAllMaps(mapping_node, find_name, find_sku_id, &platform_name);
if (phandle <= 0) {
return false;
}
int target;
int model_offset = FollowPhandle(phandle, &target);
if (model_offset < 0) {
return false;
}
// We found the model node, so set up the data
platform_name_ = platform_name;
model_node_ = ConfigNode(model_offset);
model_name_ = fdt_get_name(blob, model_offset, NULL);
if (target != model_offset) {
submodel_node_ = ConfigNode(target);
submodel_name_ = fdt_get_name(blob, target, NULL);
} else {
submodel_node_ = ConfigNode();
submodel_name_ = "";
}
// If this is a whitelabel model, use the tag.
int firmware_node = fdt_subnode_offset(blob, model_offset, "firmware");
if (firmware_node >= 0) {
if (fdt_getprop(blob, firmware_node, "sig-id-in-customization-id", NULL)) {
int models_node = fdt_path_offset(blob, "/chromeos/models");
int wl_model =
fdt_subnode_offset(blob, models_node, find_whitelabel_name.c_str());
if (wl_model >= 0) {
whitelabel_node_ = model_node_;
model_node_ = ConfigNode(wl_model);
} else {
CROS_CONFIG_LOG(ERROR)
<< "Cannot find whitelabel model " << find_whitelabel_name
<< ": using " << model_name_ << ": " << fdt_strerror(wl_model);
}
}
}
int wl_tags_node = fdt_subnode_offset(blob, model_offset, "whitelabels");
if (wl_tags_node >= 0) {
int wl_tag =
fdt_subnode_offset(blob, wl_tags_node, find_whitelabel_name.c_str());
if (wl_tag >= 0) {
whitelabel_tag_node_ = ConfigNode(wl_tag);
} else {
CROS_CONFIG_LOG(ERROR)
<< "Cannot find whitelabel tag " << find_whitelabel_name << ": using "
<< model_name_ << ": " << fdt_strerror(wl_tag);
}
}
// See if there is a whitelabel config for this model.
if (!whitelabel_node_.IsValid()) {
LookupPhandle(model_node_, "whitelabel", &whitelabel_node_);
}
ConfigNode next_node;
default_nodes_.clear();
for (ConfigNode node = model_node_;
LookupPhandle(node, "default", &next_node); node = next_node) {
if (std::find(default_nodes_.begin(), default_nodes_.end(), next_node) !=
default_nodes_.end()) {
CROS_CONFIG_LOG(ERROR) << "Circular default at " << GetFullPath(node);
return false;
}
default_nodes_.push_back(next_node);
}
CROS_CONFIG_LOG(INFO) << "Using master configuration for model "
<< model_name_ << ", submodel "
<< (submodel_name_.empty() ? "(none)" : submodel_name_);
if (whitelabel_node_.IsValid()) {
CROS_CONFIG_LOG(INFO) << "Whitelabel of " << GetFullPath(whitelabel_node_);
} else if (whitelabel_tag_node_.IsValid()) {
CROS_CONFIG_LOG(INFO) << "Whitelabel tag "
<< GetFullPath(whitelabel_tag_node_);
}
inited_ = true;
return true;
}
int CrosConfigFdt::GetProp(const ConfigNode& node,
std::string name,
std::string* value_out) {
const void* blob = blob_.c_str();
int len;
const char* value = static_cast<const char*>(
fdt_getprop(blob, node.GetOffset(), name.c_str(), &len));
if (value) {
*value_out = value;
len--;
}
return len;
}
bool CrosConfigFdt::ReadConfigFile(const base::FilePath& filepath) {
if (!base::ReadFileToString(filepath, &blob_)) {
CROS_CONFIG_LOG(ERROR) << "Could not read file " << filepath.MaybeAsASCII();
return false;
}
const void* blob = blob_.c_str();
int ret = fdt_check_header(blob);
if (ret) {
CROS_CONFIG_LOG(ERROR) << "Config file " << filepath.MaybeAsASCII()
<< " is invalid: " << fdt_strerror(ret);
return false;
}
int target_dirs_offset = fdt_path_offset(blob, kTargetDirsPath);
if (target_dirs_offset >= 0) {
for (int poffset = fdt_first_property_offset(blob, target_dirs_offset);
poffset >= 0; poffset = fdt_next_property_offset(blob, poffset)) {
int len;
const struct fdt_property* prop =
fdt_get_property_by_offset(blob, poffset, &len);
const char* name = fdt_string(blob, fdt32_to_cpu(prop->nameoff));
target_dirs_[name] = prop->data;
}
} else if (target_dirs_offset < 0) {
CROS_CONFIG_LOG(WARNING) << "Cannot find " << kTargetDirsPath
<< " node: " << fdt_strerror(target_dirs_offset);
}
int schema_offset = fdt_path_offset(blob, kSchemaPath);
if (schema_offset >= 0) {
int len;
const char* prop = static_cast<const char*>(
fdt_getprop(blob, schema_offset, kPhandleProperties, &len));
if (prop) {
const char* end = prop + len;
for (const char* ptr = prop; ptr < end; ptr += strlen(ptr) + 1) {
phandle_props_.push_back(ptr);
}
} else {
CROS_CONFIG_LOG(WARNING) << "Cannot find property " << kPhandleProperties
<< " node: " << fdt_strerror(len);
}
} else if (schema_offset < 0) {
CROS_CONFIG_LOG(WARNING) << "Cannot find " << kSchemaPath
<< " node: " << fdt_strerror(schema_offset);
}
return true;
}
bool CrosConfigFdt::GetStringByNode(const ConfigNode& base_node,
const std::string& path,
const std::string& prop,
std::string* val_out,
std::vector<std::string>* log_msgs_out) {
ConfigNode subnode = GetPathNode(base_node, path);
ConfigNode wl_subnode;
if (whitelabel_node_.IsValid()) {
wl_subnode = GetPathNode(whitelabel_node_, path);
if (!subnode.IsValid() && wl_subnode.IsValid()) {
CROS_CONFIG_LOG(INFO)
<< "The path " << GetFullPath(base_node) << path
<< " does not exist. Falling back to whitelabel path";
subnode = wl_subnode;
}
}
if (!subnode.IsValid()) {
log_msgs_out->push_back("The path " + GetFullPath(base_node) + path +
" does not exist.");
return false;
}
std::string value;
int len = GetProp(subnode, prop, &value);
if (len < 0 && wl_subnode.IsValid()) {
len = GetProp(wl_subnode, prop, &value);
CROS_CONFIG_LOG(INFO) << "The property " << prop
<< " does not exist. Falling back to "
<< "whitelabel property";
}
if (len < 0) {
ConfigNode target_node;
for (int i = 0; i < phandle_props_.size(); i++) {
LookupPhandle(subnode, phandle_props_[i], &target_node);
if (target_node.IsValid()) {
len = GetProp(target_node, prop, &value);
if (len < 0) {
CROS_CONFIG_LOG(INFO)
<< "Followed " << phandle_props_[i] << " phandle";
break;
}
}
}
}
if (len < 0) {
log_msgs_out->push_back("Cannot get path " + path + " property " + prop +
": " + "full path " + GetFullPath(subnode) + ": " +
fdt_strerror(len));
return false;
}
// We must have a normally terminated string. This guards against a string
// list being used, or perhaps a property that does not contain a valid
// string at all.
if (!len || value.size() != len) {
log_msgs_out->push_back("String at path " + path + " property " + prop +
" is invalid");
return false;
}
val_out->assign(value);
return true;
}
bool CrosConfigFdt::GetString(const std::string& path,
const std::string& prop,
std::string* val_out,
std::vector<std::string>* log_msgs_out) {
if (!InitCheck()) {
return false;
}
if (!model_node_.IsValid()) {
log_msgs_out->push_back("Please specify the model to access.");
return false;
}
if (path.empty()) {
log_msgs_out->push_back("Path must be specified");
return false;
}
if (path[0] != '/') {
log_msgs_out->push_back("Path must start with / specifying the root node");
return false;
}
if (whitelabel_tag_node_.IsValid()) {
if (path == "/" && GetStringByNode(whitelabel_tag_node_, "/", prop, val_out,
log_msgs_out)) {
return true;
}
// TODO(sjg@chromium.org): We are considering moving the key-id to the root
// of the model schema. If we do, we can drop this special case.
if (path == "/firmware" && prop == "key-id" &&
GetStringByNode(whitelabel_tag_node_, "/", prop, val_out,
log_msgs_out)) {
return true;
}
}
if (!GetStringByNode(model_node_, path, prop, val_out, log_msgs_out)) {
if (submodel_node_.IsValid() &&
GetStringByNode(submodel_node_, path, prop, val_out, log_msgs_out)) {
return true;
}
for (ConfigNode node : default_nodes_) {
if (GetStringByNode(node, path, prop, val_out, log_msgs_out))
return true;
}
return false;
}
return true;
}
} // namespace brillo