diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/core/nvram-format.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/core/nvram-format.c')
-rw-r--r-- | roms/skiboot/core/nvram-format.c | 331 |
1 files changed, 331 insertions, 0 deletions
diff --git a/roms/skiboot/core/nvram-format.c b/roms/skiboot/core/nvram-format.c new file mode 100644 index 000000000..8aa5abf22 --- /dev/null +++ b/roms/skiboot/core/nvram-format.c @@ -0,0 +1,331 @@ +// 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); +} + |