331 lines
8.3 KiB
C
331 lines
8.3 KiB
C
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
/*
|
|
* NVRAM Format as specified in PAPR
|
|
*
|
|
* Copyright 2013-2019 IBM Corp.
|
|
*/
|
|
|
|
#include <skiboot.h>
|
|
#include <nvram.h>
|
|
|
|
struct chrp_nvram_hdr {
|
|
uint8_t sig;
|
|
uint8_t cksum;
|
|
be16 len;
|
|
char name[12];
|
|
};
|
|
|
|
static struct chrp_nvram_hdr *skiboot_part_hdr;
|
|
|
|
#define NVRAM_SIG_FW_PRIV 0x51
|
|
#define NVRAM_SIG_SYSTEM 0x70
|
|
#define NVRAM_SIG_FREE 0x7f
|
|
|
|
#define NVRAM_NAME_COMMON "common"
|
|
#define NVRAM_NAME_FW_PRIV "ibm,skiboot"
|
|
#define NVRAM_NAME_FREE "wwwwwwwwwwww"
|
|
|
|
/* 64k should be enough, famous last words... */
|
|
#define NVRAM_SIZE_COMMON 0x10000
|
|
|
|
/* 4k should be enough, famous last words... */
|
|
#define NVRAM_SIZE_FW_PRIV 0x1000
|
|
|
|
static uint8_t chrp_nv_cksum(struct chrp_nvram_hdr *hdr)
|
|
{
|
|
struct chrp_nvram_hdr h_copy = *hdr;
|
|
uint8_t b_data, i_sum, c_sum;
|
|
uint8_t *p = (uint8_t *)&h_copy;
|
|
unsigned int nbytes = sizeof(h_copy);
|
|
|
|
h_copy.cksum = 0;
|
|
for (c_sum = 0; nbytes; nbytes--) {
|
|
b_data = *(p++);
|
|
i_sum = c_sum + b_data;
|
|
if (i_sum < c_sum)
|
|
i_sum++;
|
|
c_sum = i_sum;
|
|
}
|
|
return c_sum;
|
|
}
|
|
|
|
int nvram_format(void *nvram_image, uint32_t nvram_size)
|
|
{
|
|
struct chrp_nvram_hdr *h;
|
|
unsigned int offset = 0;
|
|
|
|
prerror("NVRAM: Re-initializing (size: 0x%08x)\n", nvram_size);
|
|
memset(nvram_image, 0, nvram_size);
|
|
|
|
/* Create private partition */
|
|
if (nvram_size - offset < NVRAM_SIZE_FW_PRIV)
|
|
return -1;
|
|
h = nvram_image + offset;
|
|
h->sig = NVRAM_SIG_FW_PRIV;
|
|
h->len = cpu_to_be16(NVRAM_SIZE_FW_PRIV >> 4);
|
|
strcpy(h->name, NVRAM_NAME_FW_PRIV);
|
|
h->cksum = chrp_nv_cksum(h);
|
|
prlog(PR_DEBUG, "NVRAM: Created '%s' partition at 0x%08x"
|
|
" for size 0x%08x with cksum 0x%02x\n",
|
|
NVRAM_NAME_FW_PRIV, offset,
|
|
be16_to_cpu(h->len), h->cksum);
|
|
offset += NVRAM_SIZE_FW_PRIV;
|
|
|
|
/* Create common partition */
|
|
if (nvram_size - offset < NVRAM_SIZE_COMMON)
|
|
return -1;
|
|
h = nvram_image + offset;
|
|
h->sig = NVRAM_SIG_SYSTEM;
|
|
h->len = cpu_to_be16(NVRAM_SIZE_COMMON >> 4);
|
|
strcpy(h->name, NVRAM_NAME_COMMON);
|
|
h->cksum = chrp_nv_cksum(h);
|
|
prlog(PR_DEBUG, "NVRAM: Created '%s' partition at 0x%08x"
|
|
" for size 0x%08x with cksum 0x%02x\n",
|
|
NVRAM_NAME_COMMON, offset,
|
|
be16_to_cpu(h->len), h->cksum);
|
|
offset += NVRAM_SIZE_COMMON;
|
|
|
|
/* Create free space partition */
|
|
if (nvram_size - offset < sizeof(struct chrp_nvram_hdr))
|
|
return -1;
|
|
h = nvram_image + offset;
|
|
h->sig = NVRAM_SIG_FREE;
|
|
h->len = cpu_to_be16((nvram_size - offset) >> 4);
|
|
/* We have the full 12 bytes here */
|
|
memcpy(h->name, NVRAM_NAME_FREE, 12);
|
|
h->cksum = chrp_nv_cksum(h);
|
|
prlog(PR_DEBUG, "NVRAM: Created '%s' partition at 0x%08x"
|
|
" for size 0x%08x with cksum 0x%02x\n",
|
|
NVRAM_NAME_FREE, offset, be16_to_cpu(h->len), h->cksum);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Check that the nvram partition layout is sane and that it
|
|
* contains our required partitions. If not, we re-format the
|
|
* lot of it
|
|
*/
|
|
int nvram_check(void *nvram_image, const uint32_t nvram_size)
|
|
{
|
|
unsigned int offset = 0;
|
|
bool found_common = false;
|
|
|
|
skiboot_part_hdr = NULL;
|
|
|
|
while (offset + sizeof(struct chrp_nvram_hdr) < nvram_size) {
|
|
struct chrp_nvram_hdr *h = nvram_image + offset;
|
|
|
|
if (chrp_nv_cksum(h) != h->cksum) {
|
|
prerror("NVRAM: Partition at offset 0x%x"
|
|
" has bad checksum: 0x%02x vs 0x%02x\n",
|
|
offset, h->cksum, chrp_nv_cksum(h));
|
|
goto failed;
|
|
}
|
|
if (be16_to_cpu(h->len) < 1) {
|
|
prerror("NVRAM: Partition at offset 0x%x"
|
|
" has incorrect 0 length\n", offset);
|
|
goto failed;
|
|
}
|
|
|
|
if (h->sig == NVRAM_SIG_SYSTEM &&
|
|
strcmp(h->name, NVRAM_NAME_COMMON) == 0)
|
|
found_common = true;
|
|
|
|
if (h->sig == NVRAM_SIG_FW_PRIV &&
|
|
strcmp(h->name, NVRAM_NAME_FW_PRIV) == 0)
|
|
skiboot_part_hdr = h;
|
|
|
|
offset += be16_to_cpu(h->len) << 4;
|
|
if (offset > nvram_size) {
|
|
prerror("NVRAM: Partition at offset 0x%x"
|
|
" extends beyond end of nvram !\n", offset);
|
|
goto failed;
|
|
}
|
|
}
|
|
if (!found_common) {
|
|
prlog_once(PR_ERR, "NVRAM: Common partition not found !\n");
|
|
goto failed;
|
|
}
|
|
|
|
if (!skiboot_part_hdr) {
|
|
prlog_once(PR_ERR, "NVRAM: Skiboot private partition not found !\n");
|
|
goto failed;
|
|
} else {
|
|
/*
|
|
* The OF NVRAM format requires config strings to be NUL
|
|
* terminated and unused memory to be set to zero. Well behaved
|
|
* software should ensure this is done for us, but we should
|
|
* always check.
|
|
*/
|
|
const char *last_byte = (const char *) skiboot_part_hdr +
|
|
be16_to_cpu(skiboot_part_hdr->len) * 16 - 1;
|
|
|
|
if (*last_byte != 0) {
|
|
prerror("NVRAM: Skiboot private partition is not NUL terminated");
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
prlog(PR_INFO, "NVRAM: Layout appears sane\n");
|
|
assert(skiboot_part_hdr);
|
|
return 0;
|
|
failed:
|
|
return -1;
|
|
}
|
|
|
|
static const char *find_next_key(const char *start, const char *end)
|
|
{
|
|
/*
|
|
* Unused parts of the partition are set to NUL. If we hit two
|
|
* NULs in a row then we assume that we have hit the end of the
|
|
* partition.
|
|
*/
|
|
if (*start == 0)
|
|
return NULL;
|
|
|
|
while (start < end) {
|
|
if (*start == 0)
|
|
return start + 1;
|
|
|
|
start++;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void nvram_dangerous(const char *key)
|
|
{
|
|
prlog(PR_ERR, " ___________________________________________________________\n");
|
|
prlog(PR_ERR, "< Dangerous NVRAM option: %s\n", key);
|
|
prlog(PR_ERR, " -----------------------------------------------------------\n");
|
|
prlog(PR_ERR, " \\ \n");
|
|
prlog(PR_ERR, " \\ WW \n");
|
|
prlog(PR_ERR, " <^ \\___/| \n");
|
|
prlog(PR_ERR, " \\ / \n");
|
|
prlog(PR_ERR, " \\_ _/ \n");
|
|
prlog(PR_ERR, " }{ \n");
|
|
}
|
|
|
|
|
|
/*
|
|
* nvram_query_safe/dangerous() - Searches skiboot NVRAM partition
|
|
* for a key=value pair.
|
|
*
|
|
* Dangerous means it should only be used for testing as it may
|
|
* mask issues. Safe is ok for long term use.
|
|
*
|
|
* Returns a pointer to a NUL terminated string that contains the value
|
|
* associated with the given key.
|
|
*/
|
|
static const char *__nvram_query(const char *key, bool dangerous)
|
|
{
|
|
const char *part_end, *start;
|
|
int key_len = strlen(key);
|
|
|
|
assert(key);
|
|
|
|
if (!nvram_has_loaded()) {
|
|
prlog(PR_DEBUG,
|
|
"NVRAM: Query for '%s' must wait for NVRAM to load\n",
|
|
key);
|
|
if (!nvram_wait_for_load()) {
|
|
prlog(PR_CRIT, "NVRAM: Failed to load\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The running OS can modify the NVRAM as it pleases so we need to be
|
|
* a little paranoid and check that it's ok before we try parse it.
|
|
*
|
|
* NB: nvram_validate() can update skiboot_part_hdr
|
|
*/
|
|
if (!nvram_validate())
|
|
return NULL;
|
|
|
|
assert(skiboot_part_hdr);
|
|
|
|
part_end = (const char *) skiboot_part_hdr
|
|
+ be16_to_cpu(skiboot_part_hdr->len) * 16 - 1;
|
|
|
|
start = (const char *) skiboot_part_hdr
|
|
+ sizeof(*skiboot_part_hdr);
|
|
|
|
if (!key_len) {
|
|
prlog(PR_WARNING, "NVRAM: search key is empty!\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (key_len > 32)
|
|
prlog(PR_WARNING, "NVRAM: search key '%s' is longer than 32 chars\n", key);
|
|
|
|
while (start) {
|
|
int remaining = part_end - start;
|
|
|
|
prlog(PR_TRACE, "NVRAM: '%s' (%lu)\n",
|
|
start, strlen(start));
|
|
|
|
if (key_len + 1 > remaining)
|
|
return NULL;
|
|
|
|
if (!strncmp(key, start, key_len) && start[key_len] == '=') {
|
|
const char *value = &start[key_len + 1];
|
|
|
|
prlog(PR_DEBUG, "NVRAM: Searched for '%s' found '%s'\n",
|
|
key, value);
|
|
|
|
if (dangerous)
|
|
nvram_dangerous(start);
|
|
return value;
|
|
}
|
|
|
|
start = find_next_key(start, part_end);
|
|
}
|
|
|
|
prlog(PR_DEBUG, "NVRAM: '%s' not found\n", key);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *nvram_query_safe(const char *key)
|
|
{
|
|
return __nvram_query(key, false);
|
|
}
|
|
|
|
const char *nvram_query_dangerous(const char *key)
|
|
{
|
|
return __nvram_query(key, true);
|
|
}
|
|
|
|
/*
|
|
* nvram_query_eq_safe/dangerous() - Check if the given 'key' exists
|
|
* and is set to 'value'.
|
|
*
|
|
* Dangerous means it should only be used for testing as it may
|
|
* mask issues. Safe is ok for long term use.
|
|
*
|
|
* Note: Its an error to check for non-existence of a key
|
|
* by passing 'value == NULL' as a key's value can never be
|
|
* NULL in nvram.
|
|
*/
|
|
static bool __nvram_query_eq(const char *key, const char *value, bool dangerous)
|
|
{
|
|
const char *s = __nvram_query(key, dangerous);
|
|
|
|
if (!s)
|
|
return false;
|
|
|
|
assert(value != NULL);
|
|
return !strcmp(s, value);
|
|
}
|
|
|
|
bool nvram_query_eq_safe(const char *key, const char *value)
|
|
{
|
|
return __nvram_query_eq(key, value, false);
|
|
}
|
|
|
|
bool nvram_query_eq_dangerous(const char *key, const char *value)
|
|
{
|
|
return __nvram_query_eq(key, value, true);
|
|
}
|
|
|