blob: da44b7bc67aaa259392e492b521d46f7bf55706e [file] [log] [blame]
/*
* Copyright 2017, Google Inc.
* 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 Google Inc. 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
* OWNER 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.
*/
#define _GNU_SOURCE
#include <errno.h>
#include <libfdt.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "mosys/alloc.h"
#include "mosys/log.h"
#include "mosys/platform.h"
#include "lib/cros_config.h"
#include "lib/cros_config_struct.h"
#include "lib/sku.h"
#include "lib/smbios.h"
#include "lib/string.h"
static int cros_config_fdt_err(const char *where, int err)
{
lprintf(LOG_ERR, "%s: %s: %s\n", __func__, where, fdt_strerror(err));
return -1;
}
/**
* check_sku_map() - Check a single sku-map node for a match
*
* This searches the given sku-map node to see if it is a match for the given
* SKU ID.
*
* @fdt: Device tree blob
* @node: 'sku-map' node to examine
* @find_sku_id: SKU ID to search for. This is not required (can be -1) for
* single-sku matching
* @find_smbios_name: SMBIOS name to search for. Can be NULL if the name does
* not need to be checked (no smbios-name-match property). This only works
* on x86 devices.
* @platform_namep: Returns platform name for this SKU, if found
* @return phandle found (> 0), if any, 0 if not found, negative on error
*/
static int check_sku_map(const char *fdt, int node,
const char *find_smbios_name, int find_sku_id,
const char **platform_namep)
{
const fdt32_t *data, *end, *ptr;
const char *smbios_name;
int found_phandle = 0;
int len;
lprintf(LOG_DEBUG, "%s: Trying %s\n", __func__,
fdt_get_name(fdt, node, NULL));
smbios_name = fdt_getprop(fdt, node, "smbios-name-match", NULL);
if (smbios_name &&
(!find_smbios_name || strcmp(smbios_name, find_smbios_name))) {
lprintf(LOG_DEBUG, "%s: SMBIOS name '%s' does not match '%s'\n",
__func__, smbios_name,
find_smbios_name ? find_smbios_name : "(null)");
return 0;
}
/* If we have a single SKU, deal with that first */
data = fdt_getprop(fdt, node, "single-sku", &len);
if (data) {
if (len != sizeof(fdt32_t)) {
lprintf(LOG_ERR, "%s: single-sku: Invalid length %d\n",
__func__, len);
return -1;
}
found_phandle = fdt32_to_cpu(*data);
lprintf(LOG_DEBUG, "%s: Single SKU match\n", __func__);
goto found;
}
/*
* Locate the map and make sure it is a multiple of 2 cells (first is
* SKU ID, second is phandle).
*/
data = fdt_getprop(fdt, node, "simple-sku-map", &len);
if (!data)
return cros_config_fdt_err("find simple-sku-map", len);
if (len % (sizeof(fdt32_t) * 2)) {
/*
* Validation of configuration should catch this, so this
* should never happen. But we don't want to crash.
*/
lprintf(LOG_ERR, "%s: %s: simple-sku-map: Invalid length %d\n",
__func__, fdt_get_name(fdt, node, NULL), len);
return -1;
}
/* Search for the SKU ID in the list */
for (end = (fdt32_t *)((char *)data + len), 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) {
lprintf(LOG_DEBUG, "%s: SKU ID %d not found in mapping\n",
__func__, find_sku_id);
return 0;
}
lprintf(LOG_DEBUG, "%s: Simple SKU map match\n", __func__);
found:
*platform_namep = fdt_getprop(fdt, node, "platform-name", NULL);
if (!*platform_namep)
*platform_namep = "unknown";
lprintf(LOG_DEBUG, "%s: Platform name '%s'\n", __func__,
*platform_namep);
return found_phandle;
}
/**
* check_sku_map() - Check all sku-map nodes for a match
*
* This searches all the sku-map subnodes to see if it is a match for the given
* SKU ID.
*
* @fdt: Device tree blob
* @mapping_node: 'mapping' node to examine
* @find_smbios_name: SMBIOS name to search for
* @find_sku_id: SKU ID to search for
* @platform_namep: Returns platform name for this SKU, if found
* @return phandle found (> 0), if any, 0 if not found, negative on error
*/
static int check_sku_maps(const char *fdt, int mapping_node,
const char *find_smbios_name, int find_sku_id,
const char **platform_namep)
{
int subnode, phandle;
fdt_for_each_subnode(subnode, fdt, mapping_node)
{
phandle = check_sku_map(fdt, subnode, find_smbios_name,
find_sku_id, platform_namep);
if (phandle < 0)
return -1;
else if (phandle > 0) {
break;
}
}
return phandle;
}
/**
* follow_phandle() - Find the model node pointed to by a phandle
*
* @fdt: Device tree blob
* @phandle: Phandle to look up
* @targetp: Returns the target node of the phandle
* @return model node for this phandle, or negative on error
*/
static int follow_phandle(const char *fdt, int phandle, int *targetp)
{
const char *parent_name;
int node, parent;
int model_node;
/* Follow the phandle to the target */
node = fdt_node_offset_by_phandle(fdt, phandle);
if (node < 0)
return cros_config_fdt_err("find phandle for sku ID", node);
/* Figure out whether the target is a model or a sub-model */
parent = fdt_parent_offset(fdt, node);
if (parent < 0) {
return cros_config_fdt_err("find parent of phandle target",
node);
}
parent_name = fdt_get_name(fdt, parent, NULL);
if (!strcmp(parent_name, "submodels")) {
model_node = fdt_parent_offset(fdt, parent);
if (model_node < 0) {
return cros_config_fdt_err("find sub-model parent",
node);
}
} else if (!strcmp(parent_name, "models")) {
model_node = node;
} else {
lprintf(LOG_ERR, "%s: phandle target parent '%s' invalid\n",
__func__, parent_name);
return -1;
}
*targetp = node;
return model_node;
}
/**
* cros_config_lookup_whitelabel() - Look up whitelabel information
*
* This checks whether the model is a zero-touch whitelabel and if so, checks
* the VPD for the correct whitelabel name.
*
* @fdt: Device tree blob
* @model_nodep: On entry this is the model node to be checked.
* @find_wl_name: Whitelabel name to search for. If NULL then the value is read
* from the VPD using sku_get_whitelabel_from_vpd()
* @return -1 on error, or the whitelabel tag node offset (>0) if this is a
* whitelabel, or 0 on lookup failure.
*/
static int cros_config_lookup_whitelabel(const char *fdt, int model_nodep,
const char *find_wl_name)
{
int firmware_node;
int wl_tags_node;
int wl_tag = 0;
firmware_node = fdt_subnode_offset(fdt, model_nodep, "firmware");
wl_tags_node = fdt_subnode_offset(fdt, model_nodep, "whitelabels");
if (wl_tags_node >= 0) {
if (!find_wl_name)
find_wl_name = sku_get_whitelabel_from_vpd();
wl_tag = fdt_subnode_offset(fdt, wl_tags_node, find_wl_name);
if (wl_tag < 0) {
lprintf(LOG_ERR,
"Cannot find whitelabel tag '%s' for model "
"'%s': %s (check VPD customization ID)\n",
find_wl_name,
fdt_get_name(fdt, model_nodep, NULL),
fdt_strerror(wl_tag));
return 0;
}
}
return wl_tag;
}
/**
* string_in_list() - Check if a name is in a comma-separated list
*
* @name: Name to search for
* @list: List of names to search in, separated by a comma
* @return true if found, false if not found
*/
static bool string_in_list(const char *name, const char *list)
{
const char *p, *end;
for (p = list; *p; p = end + (*end == ',')) {
end = strchrnul(p, ',');
if (!strncmp(name, p, end - p))
return true;
}
return false;
}
int cros_config_setup_sku(const char *fdt, struct sku_info *sku_info,
const char *find_smbios_name, int find_sku_id,
const char *find_wl_name, const char **platform_namep)
{
int mapping_node, model_node;
int wl_tag_node;
int phandle;
int target;
char *customization;
mapping_node = fdt_path_offset(fdt, "/chromeos/family/mapping");
if (mapping_node < 0)
return cros_config_fdt_err("find mapping", mapping_node);
phandle = check_sku_maps(fdt, mapping_node, find_smbios_name,
find_sku_id, platform_namep);
if (phandle <= 0)
goto err;
model_node = follow_phandle(fdt, phandle, &target);
if (model_node < 0)
goto err;
/* We found the model node, so pull out the data */
memset(sku_info, '\0', sizeof(*sku_info));
/*
* If this is a whitelabel model, select the correct model or
* whitelabel tag.
*/
wl_tag_node =
cros_config_lookup_whitelabel(fdt, model_node, find_wl_name);
if (wl_tag_node < 0) {
goto err;
} else if (wl_tag_node) {
/* Whitelabel info is in whitelabels table. */
char *sig_id_gen = (char *)mosys_malloc(128);
if (!sig_id_gen) {
lprintf(LOG_ERR,
"Could not allocate sig_id_gen string\n");
return -ENOMEM;
}
snprintf(sig_id_gen, 128, "%s-%s",
fdt_get_name(fdt, model_node, NULL),
fdt_get_name(fdt, wl_tag_node, NULL));
sku_info->signature_id = sig_id_gen;
sku_info->brand =
fdt_getprop(fdt, wl_tag_node, "brand-code", NULL);
} else {
/* Not a whitelabel */
sku_info->signature_id = fdt_get_name(fdt, model_node, NULL);
sku_info->brand = fdt_getprop(fdt, target, "brand-code", NULL);
if (!sku_info->brand)
sku_info->brand =
fdt_getprop(fdt, model_node, "brand-code", NULL);
}
sku_info->model = fdt_get_name(fdt, model_node, NULL);
/* Default customization should be model, or model-wltag. */
customization = mosys_strdup(sku_info->signature_id);
if (!customization) {
lprintf(LOG_ERR, "Could not allocate customization string\n");
return -ENOMEM;
}
sku_info->customization = strupper(customization);
/* we don't report the sub-model in mosys */
lprintf(LOG_DEBUG, "%s: Found model '%s'\n", __func__, sku_info->model);
return 0;
err:
lprintf(LOG_ERR, "%s: Could not locate SKU in mapping\n", __func__);
return -1;
}
#ifdef CONFIG_PLATFORM_ARCH_X86
/** internal function with common code to read sku info */
int internal_cros_config_read_sku_info(struct platform_intf *intf,
const int sku_number,
const char *smbios_name,
struct sku_info *sku_info)
{
const char *platform_name;
extern char __dtb_config_begin[];
char *fdt = __dtb_config_begin;
int ret;
lprintf(LOG_DEBUG, "%s: Yaml lookup SMBIOS name '%s', SKU ID %d\n",
__func__, smbios_name ? smbios_name : "(null)", sku_number);
ret = cros_config_read_sku_info_struct(intf, smbios_name, sku_number,
sku_info);
if (!ret)
return 0;
/* Fall back to using device tree if yaml-based config is not present */
lprintf(LOG_DEBUG, "%s: Yaml lookup failed, trying device tree: "
"SMBIOS name '%s', SKU ID %d\n",
__func__, smbios_name ? smbios_name : "(null)", sku_number);
ret = cros_config_setup_sku(fdt, sku_info, smbios_name, sku_number,
NULL, &platform_name);
if (ret) {
if (ret != -ENOENT)
lprintf(LOG_ERR,
"%s: Failed to read master configuration\n",
__func__);
return -1;
}
intf->name = platform_name;
return 0;
}
#endif // CONFIG_PLATFORM_ARCH_X86
int cros_config_read_sku_info(struct platform_intf *intf,
const char *find_platform_names,
struct sku_info *sku_info)
{
#ifdef CONFIG_PLATFORM_ARCH_ARMEL
return cros_config_read_sku_info_struct(intf, sku_info);
#endif // CONFIG_PLATFORM_ARCH_ARMEL
#ifdef CONFIG_PLATFORM_ARCH_X86
const char *smbios_name;
int sku_id;
smbios_name = smbios_sysinfo_get_name(intf);
if (!smbios_name)
lprintf(LOG_DEBUG, "%s: Unknown SMBIOS name\n", __func__);
sku_id = smbios_sysinfo_get_sku_number(intf);
if (sku_id == -1)
lprintf(LOG_DEBUG, "%s: Unknown SKU ID\n", __func__);
if (smbios_name &&
!string_in_list(smbios_name, find_platform_names)) {
lprintf(LOG_DEBUG, "%s: Could not locate name '%s' in '%s'\n",
__func__, smbios_name, find_platform_names);
return -ENOENT;
}
return internal_cros_config_read_sku_info(intf, sku_id, smbios_name,
sku_info);
#endif // CONFIG_PLATFORM_ARCH_X86
}
int cros_config_read_forced_sku_info(struct platform_intf *intf,
const char *find_platform_names,
const int forced_sku_number,
struct sku_info *sku_info)
{
#ifdef CONFIG_PLATFORM_ARCH_ARMEL
return cros_config_read_sku_info_struct(intf, sku_info);
#endif // CONFIG_PLATFORM_ARCH_ARMEL
#ifdef CONFIG_PLATFORM_ARCH_X86
const char *smbios_name;
smbios_name = smbios_sysinfo_get_name(intf);
if (!smbios_name)
lprintf(LOG_DEBUG, "%s: Unknown SMBIOS name\n", __func__);
if (smbios_name &&
!string_in_list(smbios_name, find_platform_names)) {
lprintf(LOG_DEBUG, "%s: Could not locate name '%s' in '%s'\n",
__func__, smbios_name, find_platform_names);
return -ENOENT;
}
return internal_cros_config_read_sku_info(intf, forced_sku_number,
smbios_name, sku_info);
#endif // CONFIG_PLATFORM_ARCH_X86
}
int cros_config_smbios_platform_name_match(struct platform_intf *intf,
const char *find_platform_names)
{
#ifdef CONFIG_PLATFORM_ARCH_X86
const char *smbios_name;
smbios_name = smbios_sysinfo_get_name(intf);
if (!smbios_name) {
lprintf(LOG_DEBUG, "%s: Unknown SMBIOS name\n", __func__);
return -ENOENT;
}
if (!string_in_list(smbios_name, find_platform_names)) {
lprintf(LOG_DEBUG, "%s: Could not locate name '%s' in '%s'\n",
__func__, smbios_name, find_platform_names);
return -ENOENT;
}
return 0;
#else // CONFIG_PLATFORM_ARCH_X86
return -ENOENT;
#endif
}