1728aef23d
- Added a generic bhnd_nvram_parser API, with support for the TLV format used on WGT634U devices, the standard BCM NVRAM format used on most modern devices, and the "board text file" format used on some hardware to supply external NVRAM data at runtime (e.g. via an EFI variable). - Extended the bhnd_bus_if and bhnd_nvram_if interfaces to support both string-based and primitive data type variable access, required for common behavior across both SPROM and NVRAM data sources. - Extended the existing SPROM implementation to support the new string-based NVRAM APIs. - Added an abstract bhnd_nvram driver, implementing the bhnd_nvram_if atop the bhnd_nvram_parser API. - Added a CFE-based bhnd_nvram driver to provide read-only access to NVRAM data on MIPS SoCs, pending implementation of a flash-aware bhnd_nvram driver. Approved by: adrian (mentor) Differential Revision: https://reviews.freebsd.org/D7489
1579 lines
38 KiB
C
1579 lines
38 KiB
C
/*-
|
|
* Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer,
|
|
* without modification.
|
|
* 2. Redistributions in binary form must reproduce at minimum a disclaimer
|
|
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
|
|
* redistribution must be conditioned upon including a substantially
|
|
* similar Disclaimer requirement for further binary redistribution.
|
|
*
|
|
* NO WARRANTY
|
|
* 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 NONINFRINGEMENT, MERCHANTIBILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR 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 DAMAGES.
|
|
*/
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/bus.h>
|
|
#include <sys/ctype.h>
|
|
#include <sys/endian.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/queue.h>
|
|
#include <sys/rman.h>
|
|
#include <sys/systm.h>
|
|
|
|
#include <machine/bus.h>
|
|
#include <machine/resource.h>
|
|
|
|
#include "bhnd_nvram_parserreg.h"
|
|
#include "bhnd_nvram_parservar.h"
|
|
|
|
/*
|
|
* BHND NVRAM Parser
|
|
*
|
|
* Provides identification, decoding, and encoding of BHND NVRAM data.
|
|
*/
|
|
|
|
static const struct bhnd_nvram_ops *bhnd_nvram_find_ops(bhnd_nvram_format fmt);
|
|
|
|
static int bhnd_nvram_find_var(struct bhnd_nvram *sc, const char *name,
|
|
const char **value, size_t *value_len);
|
|
|
|
static int bhnd_nvram_keycmp(const char *lhs, size_t lhs_len,
|
|
const char *rhs, size_t rhs_len);
|
|
static int bhnd_nvram_sort_idx(void *ctx, const void *lhs,
|
|
const void *rhs);
|
|
static int bhnd_nvram_generate_index(struct bhnd_nvram *sc);
|
|
|
|
static int bhnd_nvram_index_lookup(struct bhnd_nvram *sc,
|
|
struct bhnd_nvram_idx *idx, const char *name,
|
|
const char **env, size_t *len, const char **value,
|
|
size_t *value_len);
|
|
static int bhnd_nvram_buffer_lookup(struct bhnd_nvram *sc,
|
|
const char *name, const char **env, size_t *env_len,
|
|
const char **value, size_t *value_len);
|
|
|
|
static bool bhnd_nvram_bufptr_valid(struct bhnd_nvram *sc, const void *ptr,
|
|
size_t nbytes, bool log_error);
|
|
|
|
static int bhnd_nvram_parse_env(struct bhnd_nvram *sc, const char *env,
|
|
size_t len, const char **key, size_t *key_len,
|
|
const char **val, size_t *val_len);
|
|
|
|
/**
|
|
* Calculate the size of the NVRAM data in @p data.
|
|
*
|
|
* @param data Pointer to NVRAM data to be parsed.
|
|
* @param[in,out] size On input, the total size of @p data. On
|
|
* successful parsing of @p data, will be set to
|
|
* the parsed size (which may be larger).
|
|
*/
|
|
typedef int (*bhnd_nvram_op_getsize)(const void *data, size_t *size);
|
|
|
|
/** Perform format-specific initialization. */
|
|
typedef int (*bhnd_nvram_op_init)(struct bhnd_nvram *sc);
|
|
|
|
/** Initialize any format-specific default values. */
|
|
typedef int (*bhnd_nvram_op_init_defaults)(struct bhnd_nvram *sc);
|
|
typedef int (*bhnd_nvram_op_enum_buf)(struct bhnd_nvram *sc,
|
|
const char **env, size_t *len, const uint8_t *p,
|
|
uint8_t const **next);
|
|
|
|
/* FMT_BCM ops */
|
|
static int bhnd_nvram_bcm_getsize(const void *data, size_t *size);
|
|
static int bhnd_nvram_bcm_init(struct bhnd_nvram *sc);
|
|
static int bhnd_nvram_bcm_init_defaults(struct bhnd_nvram *sc);
|
|
static int bhnd_nvram_bcm_enum_buf(struct bhnd_nvram *sc, const char **env,
|
|
size_t *len, const uint8_t *p, uint8_t const **next);
|
|
|
|
/* FMT_TLV ops */
|
|
static int bhnd_nvram_tlv_getsize(const void *data, size_t *size);
|
|
static int bhnd_nvram_tlv_init(struct bhnd_nvram *sc);
|
|
static int bhnd_nvram_tlv_enum_buf(struct bhnd_nvram *sc, const char **env,
|
|
size_t *len, const uint8_t *p, uint8_t const **next);
|
|
/* FMT_TXT ops */
|
|
static int bhnd_nvram_txt_getsize(const void *data, size_t *size);
|
|
static int bhnd_nvram_txt_init(struct bhnd_nvram *sc);
|
|
static int bhnd_nvram_txt_enum_buf(struct bhnd_nvram *sc, const char **env,
|
|
size_t *len, const uint8_t *p, uint8_t const **next);
|
|
|
|
/**
|
|
* Format-specific operations.
|
|
*/
|
|
struct bhnd_nvram_ops {
|
|
bhnd_nvram_format fmt; /**< nvram format */
|
|
bhnd_nvram_op_getsize getsize; /**< determine actual NVRAM size */
|
|
bhnd_nvram_op_init init; /**< format-specific initialization */
|
|
bhnd_nvram_op_enum_buf enum_buf; /**< enumerate backing buffer */
|
|
bhnd_nvram_op_init_defaults init_defaults; /**< populate any default values */
|
|
};
|
|
|
|
static const struct bhnd_nvram_ops bhnd_nvram_ops_table[] = {
|
|
{
|
|
BHND_NVRAM_FMT_BCM,
|
|
bhnd_nvram_bcm_getsize,
|
|
bhnd_nvram_bcm_init,
|
|
bhnd_nvram_bcm_enum_buf,
|
|
bhnd_nvram_bcm_init_defaults
|
|
},
|
|
{
|
|
BHND_NVRAM_FMT_TLV,
|
|
bhnd_nvram_tlv_getsize,
|
|
bhnd_nvram_tlv_init,
|
|
bhnd_nvram_tlv_enum_buf,
|
|
NULL
|
|
},
|
|
{
|
|
BHND_NVRAM_FMT_BTXT,
|
|
bhnd_nvram_txt_getsize,
|
|
bhnd_nvram_txt_init,
|
|
bhnd_nvram_txt_enum_buf,
|
|
NULL
|
|
},
|
|
};
|
|
|
|
#define NVRAM_LOG(sc, fmt, ...) do { \
|
|
if (sc->dev != NULL) \
|
|
device_printf(sc->dev, fmt, ##__VA_ARGS__); \
|
|
else \
|
|
printf("bhnd_nvram: " fmt, ##__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
/* Limit a size_t value to a suitable range for use as a printf string field
|
|
* width */
|
|
#define NVRAM_PRINT_WIDTH(_len) \
|
|
((_len) > NVRAM_VAL_MAX ? NVRAM_VAL_MAX : (int)(_len))
|
|
|
|
/* Is _c a field terminating/delimiting character? */
|
|
#define nvram_is_ftermc(_c) ((_c) == '\0' || nvram_is_fdelim(_c))
|
|
|
|
/* Is _c a field delimiting character? */
|
|
#define nvram_is_fdelim(_c) ((_c) == ',')
|
|
|
|
/**
|
|
* Identify @p ident.
|
|
*
|
|
* @param ident Initial header data to be used for identification.
|
|
* @param expected Expected format against which @p ident will be tested.
|
|
*
|
|
* @retval 0 If @p ident has the @p expected format.
|
|
* @retval ENODEV If @p ident does not match @p expected.
|
|
*/
|
|
int
|
|
bhnd_nvram_parser_identify(const union bhnd_nvram_ident *ident,
|
|
bhnd_nvram_format expected)
|
|
{
|
|
uint32_t bcm_magic = le32toh(ident->bcm.magic);
|
|
|
|
switch (expected) {
|
|
case BHND_NVRAM_FMT_BCM:
|
|
if (bcm_magic == NVRAM_MAGIC)
|
|
return (0);
|
|
|
|
return (ENODEV);
|
|
case BHND_NVRAM_FMT_TLV:
|
|
if (bcm_magic == NVRAM_MAGIC)
|
|
return (ENODEV);
|
|
|
|
if (ident->tlv.tag != NVRAM_TLV_TYPE_ENV)
|
|
return (ENODEV);
|
|
|
|
return (0);
|
|
case BHND_NVRAM_FMT_BTXT:
|
|
for (size_t i = 0; i < nitems(ident->btxt); i++) {
|
|
char c = ident->btxt[i];
|
|
if (!isprint(c) && !isspace(c))
|
|
return (ENODEV);
|
|
}
|
|
return (0);
|
|
break;
|
|
default:
|
|
printf("%s: unknown format: %d\n", __FUNCTION__, expected);
|
|
return (ENODEV);
|
|
}
|
|
}
|
|
|
|
/** Return the set of operations for @p fmt, if any */
|
|
static const struct bhnd_nvram_ops *
|
|
bhnd_nvram_find_ops(bhnd_nvram_format fmt)
|
|
{
|
|
const struct bhnd_nvram_ops *ops;
|
|
|
|
/* Fetch format-specific operation callbacks */
|
|
for (size_t i = 0; i < nitems(bhnd_nvram_ops_table); i++) {
|
|
ops = &bhnd_nvram_ops_table[i];
|
|
|
|
if (ops->fmt != fmt)
|
|
continue;
|
|
|
|
/* found */
|
|
return (ops);
|
|
}
|
|
|
|
return (NULL);
|
|
}
|
|
|
|
/**
|
|
* Identify the NVRAM format at @p offset within @p r, verify the
|
|
* CRC (if applicable), and allocate a local shadow copy of the NVRAM data.
|
|
*
|
|
* After initialization, no reference to @p input will be held by the
|
|
* NVRAM parser, and @p input may be safely deallocated.
|
|
*
|
|
* @param[out] sc The NVRAM parser state to be initialized.
|
|
* @param dev The parser's parent device, or NULL if none.
|
|
* @param data NVRAM data to be parsed.
|
|
* @param size Size of @p data.
|
|
* @param fmt Required format of @p input.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOMEM If internal allocation of NVRAM state fails.
|
|
* @retval EINVAL If @p input parsing fails.
|
|
*/
|
|
int
|
|
bhnd_nvram_parser_init(struct bhnd_nvram *sc, device_t dev, const void *data,
|
|
size_t size, bhnd_nvram_format fmt)
|
|
{
|
|
int error;
|
|
|
|
/* Initialize NVRAM state */
|
|
memset(sc, 0, sizeof(*sc));
|
|
|
|
sc->dev = dev;
|
|
LIST_INIT(&sc->devpaths);
|
|
|
|
/* Verify data format and init operation callbacks */
|
|
if (size < sizeof(union bhnd_nvram_ident))
|
|
return (EINVAL);
|
|
|
|
error = bhnd_nvram_parser_identify(
|
|
(const union bhnd_nvram_ident *)data, fmt);
|
|
if (error)
|
|
return (error);
|
|
|
|
if ((sc->ops = bhnd_nvram_find_ops(fmt)) == NULL) {
|
|
NVRAM_LOG(sc, "unsupported format: %d\n", fmt);
|
|
return (error);
|
|
}
|
|
|
|
/* Determine appropriate size for backing buffer */
|
|
sc->buf_size = size;
|
|
if ((error = sc->ops->getsize(data, &sc->buf_size)))
|
|
return (error);
|
|
|
|
if (sc->buf_size > size) {
|
|
NVRAM_LOG(sc, "cannot parse %zu NVRAM bytes, would overrun "
|
|
"%zu byte input buffer\n", sc->buf_size, size);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Allocate and populate backing buffer */
|
|
sc->buf = malloc(sc->buf_size, M_BHND_NVRAM, M_NOWAIT);
|
|
if (sc->buf == NULL)
|
|
return (ENOMEM);
|
|
memcpy(sc->buf, data, sc->buf_size);
|
|
|
|
/* Allocate default/pending variable hash tables */
|
|
error = bhnd_nvram_varmap_init(&sc->defaults, NVRAM_SMALL_HASH_SIZE,
|
|
M_NOWAIT);
|
|
if (error)
|
|
goto cleanup;
|
|
|
|
error = bhnd_nvram_varmap_init(&sc->pending, NVRAM_SMALL_HASH_SIZE,
|
|
M_NOWAIT);
|
|
if (error)
|
|
goto cleanup;
|
|
|
|
/* Perform format-specific initialization */
|
|
if ((error = sc->ops->init(sc)))
|
|
goto cleanup;
|
|
|
|
/* Generate all indices */
|
|
if ((error = bhnd_nvram_generate_index(sc)))
|
|
goto cleanup;
|
|
|
|
/* Add any format-specific default values */
|
|
if (sc->ops->init_defaults != NULL) {
|
|
if ((error = sc->ops->init_defaults(sc)))
|
|
goto cleanup;
|
|
}
|
|
|
|
return (0);
|
|
|
|
cleanup:
|
|
bhnd_nvram_parser_fini(sc);
|
|
return (error);
|
|
}
|
|
|
|
|
|
/**
|
|
* Release all resources held by @p sc.
|
|
*
|
|
* @param sc A NVRAM instance previously initialized via
|
|
* bhnd_nvram_parser_init().
|
|
*/
|
|
void
|
|
bhnd_nvram_parser_fini(struct bhnd_nvram *sc)
|
|
{
|
|
struct bhnd_nvram_devpath *dpath, *dnext;
|
|
|
|
LIST_FOREACH_SAFE(dpath, &sc->devpaths, dp_link, dnext) {
|
|
free(dpath->path, M_BHND_NVRAM);
|
|
free(dpath, M_BHND_NVRAM);
|
|
}
|
|
|
|
if (sc->defaults.table != NULL)
|
|
bhnd_nvram_varmap_free(&sc->defaults);
|
|
|
|
if (sc->pending.table != NULL)
|
|
bhnd_nvram_varmap_free(&sc->pending);
|
|
|
|
if (sc->idx != NULL)
|
|
free(sc->idx, M_BHND_NVRAM);
|
|
|
|
if (sc->buf != NULL)
|
|
free(sc->buf, M_BHND_NVRAM);
|
|
|
|
}
|
|
|
|
/**
|
|
* Identify the integer format of @p field.
|
|
*
|
|
* @param field Field to be identified.
|
|
* @param field_len Length of @p field.
|
|
* @param[out] base Integer base, or 0 if integer format unrecognized.
|
|
* @param[out] negated True if integer is prefixed with negation sign.
|
|
*
|
|
* @retval true if parsed successfully
|
|
* @retval false if the format of @p field cannot be determined.
|
|
*/
|
|
static bool
|
|
bhnd_nvram_identify_intfmt(const char *field, size_t field_len, int *base,
|
|
bool *negated)
|
|
{
|
|
const char *p;
|
|
|
|
/* Hex? */
|
|
p = field;
|
|
if (field_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
|
|
bool valid;
|
|
|
|
/* Check all field characters */
|
|
valid = true;
|
|
for (p = field + 2; p - field < field_len; p++) {
|
|
if (isxdigit(*p))
|
|
continue;
|
|
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
if (valid) {
|
|
*base = 16;
|
|
*negated = false;
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
/* Decimal? */
|
|
p = field;
|
|
if (field_len >= 1 && (*p == '-' || isdigit(*p))) {
|
|
bool valid;
|
|
|
|
valid = true;
|
|
*negated = false;
|
|
for (p = field; p - field < field_len; p++) {
|
|
if (p == field && *p == '-') {
|
|
*negated = true;
|
|
continue;
|
|
}
|
|
|
|
if (isdigit(*p))
|
|
continue;
|
|
|
|
valid = false;
|
|
break;
|
|
}
|
|
|
|
if (valid) {
|
|
*base = 10;
|
|
return (true);
|
|
}
|
|
}
|
|
|
|
/* No match */
|
|
*base = 0;
|
|
*negated = false;
|
|
return (false);
|
|
}
|
|
|
|
/**
|
|
* Search for a field delimiter or '\0' in @p value, returning the
|
|
* size of the first field (not including its terminating character).
|
|
*
|
|
* If no terminating character is found, @p value_len is returned.
|
|
*
|
|
* @param value The value to be searched.
|
|
* @param value_size The size of @p value.
|
|
*/
|
|
static size_t
|
|
bhnd_nvram_parse_field_len(const char *value, size_t value_size)
|
|
{
|
|
for (const char *p = value; p - value < value_size; p++) {
|
|
if (nvram_is_ftermc(*p))
|
|
return (p - value);
|
|
}
|
|
|
|
return (value_size);
|
|
}
|
|
|
|
/* Parse a string NVRAM variable, writing the NUL-terminated result
|
|
* to buf (if non-NULL). */
|
|
static int
|
|
bhnd_nvram_parse_strvar(const char *value, size_t value_len, char *buf,
|
|
size_t *size)
|
|
{
|
|
size_t str_len;
|
|
size_t req_size;
|
|
size_t max_size;
|
|
|
|
if (buf != NULL)
|
|
max_size = *size;
|
|
else
|
|
max_size = 0;
|
|
|
|
|
|
/* Determine input and output sizes, including whether additional space
|
|
* is required for a trailing NUL */
|
|
str_len = strnlen(value, value_len);
|
|
if (str_len == value_len)
|
|
req_size = str_len + 1;
|
|
else
|
|
req_size = value_len;
|
|
|
|
/* Provide actual size to caller */
|
|
*size = req_size;
|
|
if (max_size < req_size) {
|
|
if (buf != NULL)
|
|
return (ENOMEM);
|
|
else
|
|
return (0);
|
|
}
|
|
|
|
/* Copy and NUL terminate output */
|
|
memcpy(buf, value, str_len);
|
|
buf[str_len] = '\0';
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Read an NVRAM variable.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
* @param name The NVRAM variable name.
|
|
* @param[out] buf On success, the requested value will be written
|
|
* to this buffer. This argment may be NULL if
|
|
* the value is not desired.
|
|
* @param[in,out] len The capacity of @p buf. On success, will be set
|
|
* to the actual size of the requested value.
|
|
* @param type The requested data type to be written to @p buf.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT The requested variable was not found.
|
|
* @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too
|
|
* small to hold the requested value.
|
|
* @retval non-zero If reading @p name otherwise fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
int
|
|
bhnd_nvram_parser_getvar(struct bhnd_nvram *sc, const char *name, void *buf,
|
|
size_t *len, bhnd_nvram_type type)
|
|
{
|
|
char *cstr, cstr_buf[NVRAM_VAL_MAX+1];
|
|
const char *val;
|
|
size_t cstr_size;
|
|
size_t limit, nbytes;
|
|
size_t field_len, val_len;
|
|
int error;
|
|
|
|
/* Verify name validity */
|
|
if (!bhnd_nvram_validate_name(name, strlen(name)))
|
|
return (EINVAL);
|
|
|
|
/* Fetch variable's string value */
|
|
if ((error = bhnd_nvram_find_var(sc, name, &val, &val_len)))
|
|
return (error);
|
|
|
|
nbytes = 0;
|
|
if (buf != NULL)
|
|
limit = *len;
|
|
else
|
|
limit = 0;
|
|
|
|
/* Populate C string requests directly from the fetched value */
|
|
if (type == BHND_NVRAM_TYPE_CSTR)
|
|
return (bhnd_nvram_parse_strvar(val, val_len, buf, len));
|
|
|
|
/* Determine actual string length. */
|
|
val_len = strnlen(val, val_len);
|
|
|
|
/* Try parsing as an octet string value (e.g. a MAC address) */
|
|
if (bhnd_nvram_parse_octet_string(val, val_len, buf, len, type) == 0)
|
|
return (0);
|
|
|
|
/* Otherwise, we need a NUL-terminated copy of the string value
|
|
* for parsing */
|
|
cstr_size = val_len + 1;
|
|
if (cstr_size <= sizeof(cstr)) {
|
|
/* prefer stack allocated buffer */
|
|
cstr = cstr_buf;
|
|
} else {
|
|
cstr = malloc(cstr_size, M_BHND_NVRAM, M_NOWAIT);
|
|
if (cstr == NULL)
|
|
return (EFBIG);
|
|
}
|
|
|
|
/* Copy and NUL terminate */
|
|
strncpy(cstr, val, val_len);
|
|
cstr[val_len] = '\0';
|
|
|
|
/* Parse */
|
|
for (char *p = cstr; *p != '\0';) {
|
|
char *endp;
|
|
int base;
|
|
bool is_int, is_negated;
|
|
union {
|
|
unsigned long u32;
|
|
long s32;
|
|
} intv;
|
|
|
|
/* Determine the field length */
|
|
field_len = val_len - (p - cstr);
|
|
field_len = bhnd_nvram_parse_field_len(p, field_len);
|
|
|
|
/* Skip any leading whitespace */
|
|
while (field_len > 0 && isspace(*p)) {
|
|
p++;
|
|
field_len--;
|
|
}
|
|
|
|
/* Empty field values cannot be parsed as a fixed
|
|
* data type */
|
|
if (field_len == 0) {
|
|
NVRAM_LOG(sc, "error: cannot parse empty string in "
|
|
"'%s'\n", cstr);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
/* Attempt to identify the integer format */
|
|
is_int = bhnd_nvram_identify_intfmt(p, field_len, &base,
|
|
&is_negated);
|
|
|
|
/* Extract the field data */
|
|
#define NV_READ_INT(_ctype, _max, _min, _dest, _strto) do { \
|
|
if (!is_int) { \
|
|
error = EFTYPE; \
|
|
goto finished; \
|
|
} \
|
|
\
|
|
if (is_negated && _min == 0) { \
|
|
error = ERANGE; \
|
|
goto finished; \
|
|
} \
|
|
\
|
|
_dest = _strto(p, &endp, base); \
|
|
if (endp == p || !nvram_is_ftermc(*endp)) { \
|
|
error = ERANGE; \
|
|
goto finished; \
|
|
} \
|
|
\
|
|
if (_dest > _max || _dest < _min) { \
|
|
error = ERANGE; \
|
|
goto finished; \
|
|
} \
|
|
\
|
|
if (limit > nbytes && limit - nbytes >= sizeof(_ctype)) \
|
|
*((_ctype *)((uint8_t *)buf + nbytes)) = _dest; \
|
|
\
|
|
nbytes += sizeof(_ctype); \
|
|
} while(0)
|
|
|
|
switch (type) {
|
|
case BHND_NVRAM_TYPE_CHAR:
|
|
/* Copy out the characters directly */
|
|
for (size_t i = 0; i < field_len; i++) {
|
|
if (limit > nbytes)
|
|
*((char *)buf + nbytes) = p[i];
|
|
nbytes++;
|
|
}
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_UINT8:
|
|
NV_READ_INT(uint8_t, UINT8_MAX, 0, intv.u32, strtoul);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_UINT16:
|
|
NV_READ_INT(uint16_t, UINT16_MAX, 0, intv.u32, strtoul);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_UINT32:
|
|
NV_READ_INT(uint32_t, UINT32_MAX, 0, intv.u32, strtoul);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_INT8:
|
|
NV_READ_INT(int8_t, INT8_MAX, INT8_MIN, intv.s32,
|
|
strtol);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_INT16:
|
|
NV_READ_INT(int16_t, INT16_MAX, INT16_MIN, intv.s32,
|
|
strtol);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_INT32:
|
|
NV_READ_INT(int32_t, INT32_MAX, INT32_MIN, intv.s32,
|
|
strtol);
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_CSTR: /* Must be handled above */
|
|
/* fallthrough */
|
|
default:
|
|
NVRAM_LOG(sc, "unhandled NVRAM type: %d\n", type);
|
|
error = ENXIO;
|
|
goto finished;
|
|
}
|
|
|
|
/* Advance to next field, skip any trailing delimiter */
|
|
p += field_len;
|
|
if (nvram_is_fdelim(*p))
|
|
p++;
|
|
}
|
|
|
|
error = 0;
|
|
|
|
finished:
|
|
if (cstr != cstr_buf)
|
|
free(cstr, M_BHND_NVRAM);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Set an NVRAM variable.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
* @param name The NVRAM variable name.
|
|
* @param[out] buf The new value.
|
|
* @param[in,out] len The size of @p buf.
|
|
* @param type The data type of @p buf.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT The requested variable was not found.
|
|
* @retval EINVAL If @p len does not match the expected variable size.
|
|
*/
|
|
int
|
|
bhnd_nvram_parser_setvar(struct bhnd_nvram *sc, const char *name,
|
|
const void *buf, size_t len, bhnd_nvram_type type)
|
|
{
|
|
/* Verify name validity */
|
|
if (!bhnd_nvram_validate_name(name, strlen(name)))
|
|
return (EINVAL);
|
|
|
|
/* Verify buffer size alignment for the given type. If this is a
|
|
* variable width type, a width of 0 will always pass this check */
|
|
if (len % bhnd_nvram_type_width(type) != 0)
|
|
return (EINVAL);
|
|
|
|
/* Determine string format (or directly add variable, if a C string) */
|
|
switch (type) {
|
|
case BHND_NVRAM_TYPE_UINT8:
|
|
case BHND_NVRAM_TYPE_UINT16:
|
|
case BHND_NVRAM_TYPE_UINT32:
|
|
case BHND_NVRAM_TYPE_INT8:
|
|
case BHND_NVRAM_TYPE_INT16:
|
|
case BHND_NVRAM_TYPE_INT32:
|
|
// TODO: primitive type value support
|
|
return (EOPNOTSUPP);
|
|
|
|
case BHND_NVRAM_TYPE_CHAR:
|
|
case BHND_NVRAM_TYPE_CSTR:
|
|
return (bhnd_nvram_varmap_add(&sc->pending, name, buf, len));
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Return true if @p ptr + nbytes falls within our backing buffer, false
|
|
* otherwise.
|
|
*/
|
|
static bool
|
|
bhnd_nvram_bufptr_valid(struct bhnd_nvram *sc, const void *ptr, size_t nbytes,
|
|
bool log_error)
|
|
{
|
|
const uint8_t *p = ptr;
|
|
|
|
if (p < sc->buf)
|
|
goto failed;
|
|
|
|
if (nbytes > sc->buf_size)
|
|
goto failed;
|
|
|
|
if (p - sc->buf > sc->buf_size - nbytes)
|
|
goto failed;
|
|
|
|
return (true);
|
|
|
|
failed:
|
|
if (log_error)
|
|
NVRAM_LOG(sc, "NVRAM record not readable at %p+%#zx (base=%p, "
|
|
"len=%zu)\n", p, nbytes, sc->buf, sc->buf_size);
|
|
return (false);
|
|
}
|
|
|
|
/**
|
|
* Parse a 'key=value' env string.
|
|
*/
|
|
static int
|
|
bhnd_nvram_parse_env(struct bhnd_nvram *sc, const char *env, size_t len,
|
|
const char **key, size_t *key_len, const char **val, size_t *val_len)
|
|
{
|
|
const char *p;
|
|
|
|
/* Key */
|
|
if ((p = memchr(env, '=', len)) == NULL) {
|
|
NVRAM_LOG(sc, "missing delim in '%.*s'\n",
|
|
NVRAM_PRINT_WIDTH(len), env);
|
|
return (EINVAL);
|
|
}
|
|
|
|
*key = env;
|
|
*key_len = p - env;
|
|
|
|
/* Skip '=' */
|
|
p++;
|
|
|
|
/* Vaue */
|
|
*val = p;
|
|
*val_len = len - (p - env);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Fetch a string pointer to @p name's value, if any.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
* @param name The NVRAM variable name.
|
|
* @param[out] value On success, a pointer to the variable's value
|
|
* string. The string may not be NUL terminated.
|
|
* @param[out] value_len On success, the length of @p value, not
|
|
* including a terminating NUL (if any exists).
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT The requested variable was not found.
|
|
* @retval non-zero If reading @p name otherwise fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
static int
|
|
bhnd_nvram_find_var(struct bhnd_nvram *sc, const char *name, const char **value,
|
|
size_t *value_len)
|
|
{
|
|
struct bhnd_nvram_tuple *t;
|
|
bhnd_nvram_op_enum_buf enum_fn;
|
|
const char *env;
|
|
size_t env_len;
|
|
size_t name_len;
|
|
int error;
|
|
|
|
enum_fn = sc->ops->enum_buf;
|
|
name_len = strlen(name);
|
|
|
|
/*
|
|
* Search path:
|
|
*
|
|
* - uncommitted changes
|
|
* - index lookup OR buffer scan
|
|
* - registered defaults
|
|
*/
|
|
|
|
/* Search uncommitted changes */
|
|
t = bhnd_nvram_varmap_find(&sc->pending, name, name_len);
|
|
if (t != NULL) {
|
|
if (t->value != NULL) {
|
|
/* Uncommited value exists, is not a deletion */
|
|
*value = t->value;
|
|
*value_len = t->value_len;
|
|
return (0);
|
|
} else {
|
|
/* Value is marked for deletion. */
|
|
error = ENOENT;
|
|
goto failed;
|
|
}
|
|
}
|
|
|
|
/* Search backing buffer. We the index if available; otherwise,
|
|
* perform a buffer scan */
|
|
if (sc->idx != NULL) {
|
|
error = bhnd_nvram_index_lookup(sc, sc->idx, name, &env,
|
|
&env_len, value, value_len);
|
|
} else {
|
|
error = bhnd_nvram_buffer_lookup(sc, name, &env, &env_len,
|
|
value, value_len);
|
|
}
|
|
|
|
failed:
|
|
/* If a parse error occured, we don't want to hide the issue by
|
|
* returning a default NVRAM value. Otherwise, look for a matching
|
|
* default. */
|
|
if (error != ENOENT)
|
|
return (error);
|
|
|
|
t = bhnd_nvram_varmap_find(&sc->defaults, name, name_len);
|
|
if (t != NULL) {
|
|
*value = t->value;
|
|
*value_len = t->value_len;
|
|
return (0);
|
|
}
|
|
|
|
/* Not found, and no default value available */
|
|
return (ENOENT);
|
|
}
|
|
|
|
/*
|
|
* An strcmp()-compatible lexical comparison implementation that
|
|
* handles non-NUL-terminated strings.
|
|
*/
|
|
static int
|
|
bhnd_nvram_keycmp(const char *lhs, size_t lhs_len, const char *rhs,
|
|
size_t rhs_len)
|
|
{
|
|
int order;
|
|
|
|
order = strncmp(lhs, rhs, ulmin(lhs_len, rhs_len));
|
|
if (order == 0) {
|
|
if (lhs_len < rhs_len)
|
|
order = -1;
|
|
else if (lhs_len > rhs_len)
|
|
order = 1;
|
|
}
|
|
|
|
return (order);
|
|
}
|
|
|
|
/* sort function for bhnd_nvram_idx_entry values */
|
|
static int
|
|
bhnd_nvram_sort_idx(void *ctx, const void *lhs, const void *rhs)
|
|
{
|
|
struct bhnd_nvram *sc;
|
|
const struct bhnd_nvram_idx_entry *l_idx, *r_idx;
|
|
const char *l_str, *r_str;
|
|
|
|
sc = ctx;
|
|
l_idx = lhs;
|
|
r_idx = rhs;
|
|
|
|
/* Fetch string pointers */
|
|
l_str = (char *)(sc->buf + l_idx->env_offset);
|
|
r_str = (char *)(sc->buf + r_idx->env_offset);
|
|
|
|
/* Perform comparison */
|
|
return (bhnd_nvram_keycmp(l_str, l_idx->key_len, r_str,
|
|
r_idx->key_len));
|
|
}
|
|
|
|
|
|
/**
|
|
* Generate all indices for the NVRAM data backing @p nvram.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If indexing @p nvram fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
static int
|
|
bhnd_nvram_generate_index(struct bhnd_nvram *sc)
|
|
{
|
|
bhnd_nvram_op_enum_buf enum_fn;
|
|
const char *key, *val;
|
|
const char *env;
|
|
const uint8_t *p;
|
|
size_t env_len;
|
|
size_t idx_bytes;
|
|
size_t key_len, val_len;
|
|
size_t num_records;
|
|
int error;
|
|
|
|
enum_fn = sc->ops->enum_buf;
|
|
num_records = 0;
|
|
|
|
/* Parse and register all device path aliases */
|
|
p = NULL;
|
|
while ((error = enum_fn(sc, &env, &env_len, p, &p)) == 0) {
|
|
struct bhnd_nvram_devpath *devpath;
|
|
char *eptr;
|
|
char suffix[NVRAM_KEY_MAX+1];
|
|
size_t suffix_len;
|
|
u_long index;
|
|
|
|
/* Hit EOF */
|
|
if (env == NULL)
|
|
break;
|
|
|
|
num_records++;
|
|
|
|
/* Skip string comparison if env_len < strlen(devpath) */
|
|
if (env_len < NVRAM_DEVPATH_LEN)
|
|
continue;
|
|
|
|
/* Check for devpath prefix */
|
|
if (strncmp(env, NVRAM_DEVPATH_STR, NVRAM_DEVPATH_LEN) != 0)
|
|
continue;
|
|
|
|
/* Split key and value */
|
|
error = bhnd_nvram_parse_env(sc, env, env_len, &key,
|
|
&key_len, &val, &val_len);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* NUL terminate the devpath's suffix */
|
|
if (key_len >= sizeof(suffix)) {
|
|
NVRAM_LOG(sc, "variable '%.*s' exceeds NVRAM_KEY_MAX, "
|
|
"skipping devpath parsing\n",
|
|
NVRAM_PRINT_WIDTH(key_len), key);
|
|
continue;
|
|
} else {
|
|
suffix_len = key_len - NVRAM_DEVPATH_LEN;
|
|
if (suffix_len == 0)
|
|
continue;
|
|
|
|
strcpy(suffix, key+NVRAM_DEVPATH_LEN);
|
|
suffix[suffix_len] = '\0';
|
|
}
|
|
|
|
/* Parse the index value */
|
|
index = strtoul(suffix, &eptr, 10);
|
|
if (eptr == suffix || *eptr != '\0') {
|
|
NVRAM_LOG(sc, "invalid devpath variable '%.*s'\n",
|
|
NVRAM_PRINT_WIDTH(key_len), key);
|
|
continue;
|
|
}
|
|
|
|
/* Register path alias */
|
|
devpath = malloc(sizeof(*devpath), M_BHND_NVRAM, M_NOWAIT);
|
|
if (devpath == NULL)
|
|
return (ENOMEM);
|
|
|
|
devpath->index = index;
|
|
devpath->path = strndup(val, val_len, M_BHND_NVRAM);
|
|
LIST_INSERT_HEAD(&sc->devpaths, devpath, dp_link);
|
|
}
|
|
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Save record count */
|
|
sc->num_buf_vars = num_records;
|
|
|
|
/* Skip generating variable index if threshold is not met */
|
|
if (sc->num_buf_vars < NVRAM_IDX_VAR_THRESH)
|
|
return (0);
|
|
|
|
/* Allocate and populate variable index */
|
|
idx_bytes = sizeof(struct bhnd_nvram_idx) +
|
|
(sizeof(struct bhnd_nvram_idx_entry) * sc->num_buf_vars);
|
|
sc->idx = malloc(idx_bytes, M_BHND_NVRAM, M_NOWAIT);
|
|
if (sc->idx == NULL) {
|
|
NVRAM_LOG(sc, "error allocating %zu byte index\n", idx_bytes);
|
|
goto bad_index;
|
|
}
|
|
|
|
sc->idx->num_entries = sc->num_buf_vars;
|
|
|
|
if (bootverbose) {
|
|
NVRAM_LOG(sc, "allocated %zu byte index for %zu variables "
|
|
"in %zu bytes\n", idx_bytes, sc->num_buf_vars,
|
|
sc->buf_size);
|
|
}
|
|
|
|
p = NULL;
|
|
for (size_t i = 0; i < sc->idx->num_entries; i++) {
|
|
struct bhnd_nvram_idx_entry *idx;
|
|
size_t env_offset;
|
|
size_t key_len, val_len;
|
|
|
|
/* Fetch next record */
|
|
if ((error = enum_fn(sc, &env, &env_len, p, &p)))
|
|
return (error);
|
|
|
|
/* Early EOF */
|
|
if (env == NULL) {
|
|
NVRAM_LOG(sc, "indexing failed, expected %zu records "
|
|
"(got %zu)\n", sc->idx->num_entries, i+1);
|
|
goto bad_index;
|
|
}
|
|
|
|
/* Calculate env offset */
|
|
env_offset = (const uint8_t *)env - (const uint8_t *)sc->buf;
|
|
if (env_offset > NVRAM_IDX_OFFSET_MAX) {
|
|
NVRAM_LOG(sc, "'%.*s' offset %#zx exceeds maximum "
|
|
"indexable value\n", NVRAM_PRINT_WIDTH(env_len),
|
|
env, env_offset);
|
|
goto bad_index;
|
|
}
|
|
|
|
/* Split key and value */
|
|
error = bhnd_nvram_parse_env(sc, env, env_len, &key, &key_len,
|
|
&val, &val_len);
|
|
if (error)
|
|
return (error);
|
|
|
|
if (key_len > NVRAM_IDX_LEN_MAX) {
|
|
NVRAM_LOG(sc, "key length %#zx at %#zx exceeds maximum "
|
|
"indexable value\n", key_len, env_offset);
|
|
goto bad_index;
|
|
}
|
|
|
|
if (val_len > NVRAM_IDX_LEN_MAX) {
|
|
NVRAM_LOG(sc, "value length %#zx for key '%.*s' "
|
|
"exceeds maximum indexable value\n", val_len,
|
|
NVRAM_PRINT_WIDTH(key_len), key);
|
|
goto bad_index;
|
|
}
|
|
|
|
idx = &sc->idx->entries[i];
|
|
idx->env_offset = env_offset;
|
|
idx->key_len = key_len;
|
|
idx->val_len = val_len;
|
|
}
|
|
|
|
/* Sort the index table */
|
|
qsort_r(sc->idx->entries, sc->idx->num_entries,
|
|
sizeof(sc->idx->entries[0]), sc, bhnd_nvram_sort_idx);
|
|
|
|
return (0);
|
|
|
|
bad_index:
|
|
/* Fall back on non-indexed access */
|
|
NVRAM_LOG(sc, "reverting to non-indexed variable lookup\n");
|
|
if (sc->idx != NULL) {
|
|
free(sc->idx, M_BHND_NVRAM);
|
|
sc->idx = NULL;
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform an index lookup of @p name.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
* @param idx The index to search.
|
|
* @param name The variable to search for.
|
|
* @param[out] env On success, the pointer to @p name within the
|
|
* backing buffer.
|
|
* @param[out] env_len On success, the length of @p env.
|
|
* @param[out] value On success, the pointer to @p name's value
|
|
* within the backing buffer.
|
|
* @param[out] value_len On success, the length of @p value.
|
|
*
|
|
* @retval 0 If @p name was found in the index.
|
|
* @retval ENOENT If @p name was not found in the index.
|
|
* @retval ENODEV If no index has been generated.
|
|
*/
|
|
static int
|
|
bhnd_nvram_index_lookup(struct bhnd_nvram *sc, struct bhnd_nvram_idx *idx,
|
|
const char *name, const char **env, size_t *env_len, const char **value,
|
|
size_t *value_len)
|
|
{
|
|
struct bhnd_nvram_idx_entry *idxe;
|
|
const char *idxe_key;
|
|
size_t min, mid, max;
|
|
size_t name_len;
|
|
int order;
|
|
|
|
if (idx->num_entries == 0)
|
|
return (ENOENT);
|
|
|
|
/*
|
|
* Locate the requested variable using a binary search.
|
|
*/
|
|
min = 0;
|
|
mid = 0;
|
|
max = idx->num_entries - 1;
|
|
name_len = strlen(name);
|
|
|
|
while (max >= min) {
|
|
/* Select midpoint */
|
|
mid = (min + max) / 2;
|
|
idxe = &idx->entries[mid];
|
|
|
|
/* Determine which side of the partition to search */
|
|
idxe_key = (const char *) (sc->buf + idxe->env_offset);
|
|
order = bhnd_nvram_keycmp(idxe_key, idxe->key_len, name,
|
|
name_len);
|
|
|
|
if (order < 0) {
|
|
/* Search upper partition */
|
|
min = mid + 1;
|
|
} else if (order > 0) {
|
|
/* Search lower partition */
|
|
max = mid - 1;
|
|
} else if (order == 0) {
|
|
/* Match found */
|
|
*env = sc->buf + idxe->env_offset;
|
|
*env_len = idxe->key_len + idxe->val_len + 1 /* '=' */;
|
|
|
|
*value = *env + idxe->key_len + 1 /* '=' */;
|
|
*value_len = idxe->val_len;
|
|
|
|
return (0);
|
|
}
|
|
}
|
|
|
|
/* Not found */
|
|
return (ENOENT);
|
|
}
|
|
|
|
|
|
/**
|
|
* Perform a unindexed search for an entry matching @p name in the backing
|
|
* NVRAM data buffer.
|
|
*
|
|
* @param sc The NVRAM parser state.
|
|
* @param name The variable to search for.
|
|
* @param[out] env On success, the pointer to @p name within the
|
|
* backing buffer.
|
|
* @param[out] env_len On success, the length of @p env.
|
|
* @param[out] value On success, the pointer to @p name's value
|
|
* within the backing buffer.
|
|
* @param[out] value_len On success, the length of @p value.
|
|
*
|
|
* @retval 0 If @p name was found in the index.
|
|
* @retval ENOENT If @p name was not found in the index.
|
|
* @retval ENODEV If no index has been generated.
|
|
*/
|
|
static int
|
|
bhnd_nvram_buffer_lookup(struct bhnd_nvram *sc, const char *name,
|
|
const char **env, size_t *env_len, const char **value, size_t *value_len)
|
|
{
|
|
bhnd_nvram_op_enum_buf enum_fn;
|
|
const uint8_t *p;
|
|
size_t name_len;
|
|
int error;
|
|
|
|
enum_fn = sc->ops->enum_buf;
|
|
name_len = strlen(name);
|
|
|
|
/* Iterate over all records in the backing buffer */
|
|
p = NULL;
|
|
while ((error = enum_fn(sc, env, env_len, p, &p)) == 0) {
|
|
/* Hit EOF, not found */
|
|
if (*env == NULL)
|
|
return (ENOENT);
|
|
|
|
/* Skip string comparison if env_len < strlen('key=') */
|
|
if (*env_len < name_len + 1)
|
|
continue;
|
|
|
|
/* Skip string comparison if delimiter isn't found at
|
|
* expected position */
|
|
if (*(*env + name_len) != '=')
|
|
continue;
|
|
|
|
/* Check for match */
|
|
if (strncmp(*env, name, name_len) == 0) {
|
|
/* Found */
|
|
*value = *env + name_len + 1;
|
|
*value_len = *env_len - name_len - 1;
|
|
return (0);
|
|
};
|
|
}
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* FMT_BCM NVRAM data size calculation */
|
|
static int
|
|
bhnd_nvram_bcm_getsize(const void *data, size_t *size)
|
|
{
|
|
const struct bhnd_nvram_header *hdr;
|
|
|
|
if (*size < sizeof(*hdr))
|
|
return (EINVAL);
|
|
|
|
hdr = (const struct bhnd_nvram_header *) data;
|
|
*size = le32toh(hdr->size);
|
|
return (0);
|
|
}
|
|
|
|
/* FMT_BCM-specific parser initialization */
|
|
static int
|
|
bhnd_nvram_bcm_init(struct bhnd_nvram *sc)
|
|
{
|
|
const uint8_t *p;
|
|
uint32_t cfg0;
|
|
uint8_t crc, valid;
|
|
|
|
/* Validate CRC */
|
|
if (sc->buf_size < NVRAM_CRC_SKIP)
|
|
return (EINVAL);
|
|
|
|
if (sc->buf_size < sizeof(struct bhnd_nvram_header))
|
|
return (EINVAL);
|
|
|
|
cfg0 = ((struct bhnd_nvram_header *)sc->buf)->cfg0;
|
|
valid = (cfg0 & NVRAM_CFG0_CRC_MASK) >> NVRAM_CFG0_CRC_SHIFT;
|
|
|
|
p = sc->buf;
|
|
crc = bhnd_nvram_crc8(p + NVRAM_CRC_SKIP, sc->buf_size-NVRAM_CRC_SKIP,
|
|
BHND_NVRAM_CRC8_INITIAL);
|
|
|
|
if (crc != valid) {
|
|
NVRAM_LOG(sc, "warning: NVRAM CRC error (crc=%#hhx, "
|
|
"expected=%hhx)\n", crc, valid);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Populate FMT_BCM-specific default values */
|
|
static int
|
|
bhnd_nvram_bcm_init_defaults(struct bhnd_nvram *sc)
|
|
{
|
|
struct bhnd_nvram_header *header;
|
|
char vbuf[NVRAM_VAL_MAX];
|
|
uint32_t value;
|
|
int error;
|
|
int nwrite;
|
|
|
|
/* Verify that our header is readable */
|
|
header = (struct bhnd_nvram_header *) sc->buf;
|
|
if (!bhnd_nvram_bufptr_valid(sc, header, sizeof(*header), true))
|
|
return (EINVAL);
|
|
|
|
/* Extract a value value from the NVRAM header, format it, and
|
|
* register a new default variable tuple */
|
|
#define NVRAM_BCM_HEADER_DEFAULT(_field, _name) do { \
|
|
value = NVRAM_GET_BITS(le32toh(header->_field), _name); \
|
|
nwrite = snprintf(vbuf, sizeof(vbuf), _name ##_FMT, value); \
|
|
if (nwrite < 0 || nwrite >= sizeof(vbuf)) { \
|
|
NVRAM_LOG(sc, "%s: formatting '%s' failed: %d\n", \
|
|
__FUNCTION__, _name ## _VAR, nwrite); \
|
|
return (ENXIO); \
|
|
} \
|
|
error = bhnd_nvram_varmap_add(&sc->defaults, \
|
|
_name ##_VAR, vbuf, strlen(vbuf)); \
|
|
\
|
|
if (error) \
|
|
return (error); \
|
|
} while(0)
|
|
|
|
NVRAM_BCM_HEADER_DEFAULT(cfg0, NVRAM_CFG0_SDRAM_INIT);
|
|
NVRAM_BCM_HEADER_DEFAULT(cfg1, NVRAM_CFG1_SDRAM_CFG);
|
|
NVRAM_BCM_HEADER_DEFAULT(cfg1, NVRAM_CFG1_SDRAM_REFRESH);
|
|
NVRAM_BCM_HEADER_DEFAULT(sdram_ncdl, NVRAM_SDRAM_NCDL);
|
|
|
|
#undef NVRAM_BCM_HEADER_DEFAULT
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/* FMT_BCM record parsing */
|
|
static int
|
|
bhnd_nvram_bcm_enum_buf(struct bhnd_nvram *sc, const char **env, size_t *len,
|
|
const uint8_t *p, uint8_t const **next)
|
|
{
|
|
/* First record is found following the NVRAM header */
|
|
if (p == NULL)
|
|
p = sc->buf + sizeof(struct bhnd_nvram_header);
|
|
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 1, true))
|
|
return (EINVAL);
|
|
|
|
/* EOF */
|
|
if (*p == '\0') {
|
|
*env = NULL;
|
|
*len = 0;
|
|
*next = p;
|
|
return (0);
|
|
}
|
|
|
|
/* Provide pointer to env data */
|
|
*env = p;
|
|
*len = strnlen(p, sc->buf_size - (p - sc->buf));
|
|
|
|
/* Advance to next entry and skip terminating NUL */
|
|
p += *len;
|
|
if (bhnd_nvram_bufptr_valid(sc, p, 1, false)) {
|
|
p++;
|
|
} else {
|
|
NVRAM_LOG(sc, "warning: missing NVRAM termination record");
|
|
}
|
|
|
|
*next = p;
|
|
return (0);
|
|
}
|
|
|
|
/* FMT_TLV NVRAM data size calculation */
|
|
static int
|
|
bhnd_nvram_tlv_getsize(const void *data, size_t *size)
|
|
{
|
|
const uint8_t *const start = data;
|
|
size_t offset;
|
|
uint16_t rlen;
|
|
|
|
offset = 0;
|
|
while (offset < *size) {
|
|
uint8_t type;
|
|
|
|
/* Fetch type */
|
|
type = *(start+offset);
|
|
|
|
/* EOF */
|
|
if (type == NVRAM_TLV_TYPE_END) {
|
|
*size = offset + 1;
|
|
return (0);
|
|
}
|
|
|
|
if ((offset++) == *size)
|
|
return (EINVAL);
|
|
|
|
/* Determine record length */
|
|
if (type & NVRAM_TLV_TF_U8_LEN) {
|
|
rlen = *(start+offset);
|
|
} else {
|
|
rlen = *(start+offset) << 8;
|
|
if ((offset++) == *size)
|
|
return (EINVAL);
|
|
rlen |= *(start+offset);
|
|
}
|
|
|
|
if ((offset++) >= *size)
|
|
return (EINVAL);
|
|
|
|
/* Advance to next entry */
|
|
if (rlen > *size || *size - rlen < offset)
|
|
return (EINVAL);
|
|
|
|
offset += rlen;
|
|
}
|
|
|
|
/* EOF not found */
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* FMT_TLV-specific parser initialization */
|
|
static int
|
|
bhnd_nvram_tlv_init(struct bhnd_nvram *sc)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/* FMT_TLV record parsing */
|
|
static int
|
|
bhnd_nvram_tlv_enum_buf(struct bhnd_nvram *sc, const char **env, size_t *len,
|
|
const uint8_t *p, uint8_t const **next)
|
|
{
|
|
size_t rlen;
|
|
uint8_t type;
|
|
|
|
if (p == NULL)
|
|
p = sc->buf;
|
|
|
|
/* Fetch type */
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 1, true))
|
|
return (EINVAL);
|
|
|
|
type = *p;
|
|
|
|
/* EOF */
|
|
if (type == NVRAM_TLV_TYPE_END) {
|
|
*env = NULL;
|
|
*len = 0;
|
|
*next = p;
|
|
return (0);
|
|
}
|
|
|
|
/* Determine record length */
|
|
p++;
|
|
if (type & NVRAM_TLV_TF_U8_LEN) {
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 1, true))
|
|
return (EINVAL);
|
|
|
|
rlen = *p;
|
|
p += 1;
|
|
} else {
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 2, true))
|
|
return (EINVAL);
|
|
rlen = (p[0] << 8) | (p[1]);
|
|
p += 2;
|
|
}
|
|
|
|
/* Verify record readability */
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, rlen, true))
|
|
return (EINVAL);
|
|
|
|
/* Error on non-env records */
|
|
if (type != NVRAM_TLV_TYPE_ENV) {
|
|
NVRAM_LOG(sc, "unsupported NVRAM TLV tag: %#hhx\n", type);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Skip flag field */
|
|
if (rlen < 1)
|
|
return (EINVAL);
|
|
p++;
|
|
rlen--;
|
|
|
|
/* Provide pointer to env data */
|
|
*env = p;
|
|
*len = strnlen(*env, rlen);
|
|
|
|
/* Advance to next entry */
|
|
*next = p + rlen;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* FMT_BTXT NVRAM data size calculation */
|
|
static int
|
|
bhnd_nvram_txt_getsize(const void *data, size_t *size)
|
|
{
|
|
*size = (strnlen(data, *size));
|
|
return (0);
|
|
}
|
|
|
|
/* FMT_BTXT-specific parser initialization */
|
|
static int
|
|
bhnd_nvram_txt_init(struct bhnd_nvram *sc)
|
|
{
|
|
return (0);
|
|
}
|
|
|
|
/* Seek past the next line ending (\r, \r\n, or \n) */
|
|
static const uint8_t *
|
|
bhnd_nvram_txt_seek_eol(struct bhnd_nvram *sc, const uint8_t *p)
|
|
{
|
|
while (p < sc->buf + sc->buf_size) {
|
|
switch (*p) {
|
|
case '\r':
|
|
/* \r\n */
|
|
if (bhnd_nvram_bufptr_valid(sc, p, 1, false)) {
|
|
if (*(p+1) == '\n')
|
|
p++;
|
|
}
|
|
|
|
return (p+1);
|
|
case '\n':
|
|
return (p+1);
|
|
default:
|
|
p++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return (p);
|
|
}
|
|
|
|
/* Seek to the next valid key=value entry (or EOF) */
|
|
static const uint8_t *
|
|
bhnd_nvram_txt_seek_nextline(struct bhnd_nvram *sc, const uint8_t *p)
|
|
{
|
|
/* Skip leading whitespace and comments */
|
|
while (p < sc->buf + sc->buf_size) {
|
|
if (isspace(*p)) {
|
|
p++;
|
|
continue;
|
|
}
|
|
|
|
if (*p == '#') {
|
|
p = bhnd_nvram_txt_seek_eol(sc, p);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return (p);
|
|
}
|
|
|
|
/* FMT_BTXT record parsing */
|
|
static int
|
|
bhnd_nvram_txt_enum_buf(struct bhnd_nvram *sc, const char **env, size_t *len,
|
|
const uint8_t *p, uint8_t const **next)
|
|
{
|
|
const uint8_t *startp;
|
|
size_t line_len;
|
|
|
|
if (p == NULL)
|
|
p = sc->buf;
|
|
|
|
/* Skip any leading whitespace and comments */
|
|
p = bhnd_nvram_txt_seek_nextline(sc, p);
|
|
|
|
/* EOF? */
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 1, false)) {
|
|
*env = NULL;
|
|
*len = 0;
|
|
*next = p;
|
|
return (0);
|
|
}
|
|
|
|
/* Find record termination (EOL, or '#') */
|
|
startp = p;
|
|
while (p < sc->buf + sc->buf_size) {
|
|
if (*p == '#' || *p == '\n' || *p == '\r')
|
|
break;
|
|
|
|
p++;
|
|
}
|
|
|
|
/* Calculate line length, check for EOF */
|
|
line_len = p - startp;
|
|
if (!bhnd_nvram_bufptr_valid(sc, p, 1, false)) {
|
|
*env = NULL;
|
|
*len = 0;
|
|
*next = p;
|
|
return (0);
|
|
}
|
|
|
|
/* Got env data; trim any tailing whitespace */
|
|
*env = startp;
|
|
*len = line_len;
|
|
|
|
for (size_t i = 0; i < line_len && line_len > 0; i++) {
|
|
char c = startp[line_len - i - 1];
|
|
if (!isspace(c))
|
|
break;
|
|
|
|
*len -= 1;
|
|
}
|
|
|
|
/* Advance to next entry */
|
|
p = bhnd_nvram_txt_seek_nextline(sc, p);
|
|
|
|
*next = p;
|
|
return (0);
|
|
}
|