freebsd-dev/sys/dev/bhnd/nvram/bhnd_nvram_value_fmts.c
Landon J. Fuller 77cb4d3e50 bhnd(4): Unify NVRAM/SPROM parsing, implement compact SPROM layout encoding.
- Defined an abstract NVRAM I/O API (bhnd_nvram_io), decoupling NVRAM/SPROM
  parsing from the actual underlying NVRAM data provider (e.g. CFE firmware
  devices).
- Defined an abstract NVRAM data API (bhnd_nvram_data), decoupling
  higher-level NVRAM operations (indexed lookup, data conversion, etc) from
  the underlying NVRAM file format parsing/serialization.
- Implemented a new high-level bhnd_nvram_store API, providing indexed
  variable lookup, pending write tracking, etc on top of an arbitrary
  bhnd_nvram_data instance.
- Migrated all bhnd(4) NVRAM device drivers to the common bhnd_nvram_store
  API.
- Implemented a common bhnd_nvram_val API for parsing/encoding NVRAM
  variable values, including applying format-specific behavior when
  converting to/from the NVRAM string representations.
- Dropped the now unnecessary bhnd_nvram driver, and moved the
  broadcom/mips-specific CFE NVRAM driver out into sys/mips/broadcom.
- Implemented a new nvram_map file format:
        - Variable definitions are now defined separately from the SPROM
          layout. This will also allow us to define CIS tuple NVRAM
          mappings referencing the common NVRAM variable definitions.
        - Variables can now be defined within arbitrary named groups.
        - Textual descriptions and help information can be defined inline
          for both variables and variable groups.
        - Implemented a new, compact encoding of SPROM image layout
          offsets.
- Source-level (but not build system) support for building the NVRAM file
  format APIs (bhnd_nvram_io, bhnd_nvram_data, bhnd_nvram_store) as a
  userspace library.

The new compact SPROM image layout encoding is loosely modeled on Apple
dyld compressed LINKEDIT symbol binding opcodes; it provides a compact
state-machine encoding of the mapping between NVRAM variables and the SPROM
image offset, mask, and shift instructions necessary to decode or encode
the SPROM variable data.

The compact encoding reduces the size of the generated SPROM layout data
from roughly 60KB to 3KB. The sequential nature SPROM layout opcode tables
also simplify iteration of the SPROM variables, as it's no longer
neccessary to iterate the full NVRAM variable definition table, but
instead simply scan the SPROM revision's layout opcode table.

Approved by:    adrian (mentor)
Differential Revision:  https://reviews.freebsd.org/D8645
2016-11-26 23:22:32 +00:00

1033 lines
27 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 <net/ethernet.h>
#ifdef _KERNEL
#include <sys/ctype.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <machine/_inttypes.h>
#else /* !_KERNEL */
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <string.h>
#endif /* _KERNEL */
#include "bhnd_nvram_private.h"
#include "bhnd_nvram_valuevar.h"
static bool bhnd_nvram_ident_octet_string(const char *inp,
size_t ilen, char *delim, size_t *nelem);
static bool bhnd_nvram_ident_num_string(const char *inp,
size_t ilen, u_int base, u_int *obase);
static int bhnd_nvram_val_bcm_macaddr_filter(
const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype);
static int bhnd_nvram_val_bcm_macaddr_encode(
bhnd_nvram_val_t *value, void *outp, size_t *olen,
bhnd_nvram_type otype);
static int bhnd_nvram_val_bcm_macaddr_string_filter(
const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype);
static int bhnd_nvram_val_bcm_macaddr_string_encode_elem(
bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen,
bhnd_nvram_type otype);
static const void *bhnd_nvram_val_bcm_macaddr_string_next(
bhnd_nvram_val_t *value, const void *prev,
size_t *len);
static int bhnd_nvram_val_bcm_int_filter(
const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype);
static int bhnd_nvram_val_bcm_int_encode(bhnd_nvram_val_t *value,
void *outp, size_t *olen, bhnd_nvram_type otype);
static int bhnd_nvram_val_bcm_decimal_encode_elem(
bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen,
bhnd_nvram_type otype);
static int bhnd_nvram_val_bcm_hex_encode_elem(
bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen,
bhnd_nvram_type otype);
static int bhnd_nvram_val_bcm_leddc_filter(
const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype);
static int bhnd_nvram_val_bcm_leddc_encode_elem(
bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen,
bhnd_nvram_type otype);
static int bhnd_nvram_val_bcmstr_encode(bhnd_nvram_val_t *value,
void *outp, size_t *olen, bhnd_nvram_type otype);
static int bhnd_nvram_val_bcmstr_csv_filter(
const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype);
static const void *bhnd_nvram_val_bcmstr_csv_next(bhnd_nvram_val_t *value,
const void *prev, size_t *len);
/**
* Broadcom NVRAM MAC address format.
*/
const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_macaddr_fmt = {
.name = "bcm-macaddr",
.native_type = BHND_NVRAM_TYPE_UINT8_ARRAY,
.op_filter = bhnd_nvram_val_bcm_macaddr_filter,
.op_encode = bhnd_nvram_val_bcm_macaddr_encode,
};
/** Broadcom NVRAM MAC address string format. */
static const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_macaddr_string_fmt = {
.name = "bcm-macaddr-string",
.native_type = BHND_NVRAM_TYPE_STRING,
.op_filter = bhnd_nvram_val_bcm_macaddr_string_filter,
.op_encode_elem = bhnd_nvram_val_bcm_macaddr_string_encode_elem,
.op_next = bhnd_nvram_val_bcm_macaddr_string_next,
};
/**
* Broadcom NVRAM LED duty-cycle format.
*/
const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_leddc_fmt = {
.name = "bcm-leddc",
.native_type = BHND_NVRAM_TYPE_UINT32,
.op_filter = bhnd_nvram_val_bcm_leddc_filter,
.op_encode_elem = bhnd_nvram_val_bcm_leddc_encode_elem,
};
/**
* Broadcom NVRAM decimal integer format.
*
* Extends standard integer handling, encoding the string representation of
* the integer value as a decimal string:
* - Positive values will be string-encoded without a prefix.
* - Negative values will be string-encoded with a leading '-' sign.
*/
const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_decimal_fmt = {
.name = "bcm-decimal",
.native_type = BHND_NVRAM_TYPE_UINT64,
.op_filter = bhnd_nvram_val_bcm_int_filter,
.op_encode = bhnd_nvram_val_bcm_int_encode,
.op_encode_elem = bhnd_nvram_val_bcm_decimal_encode_elem,
};
/**
* Broadcom NVRAM decimal integer format.
*
* Extends standard integer handling, encoding the string representation of
* unsigned and positive signed integer values as an 0x-prefixed hexadecimal
* string.
*
* For compatibility with standard Broadcom NVRAM parsing, if the integer is
* both signed and negative, it will be string encoded as a negative decimal
* value, not as a twos-complement hexadecimal value.
*/
const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_hex_fmt = {
.name = "bcm-hex",
.native_type = BHND_NVRAM_TYPE_UINT64,
.op_filter = bhnd_nvram_val_bcm_int_filter,
.op_encode = bhnd_nvram_val_bcm_int_encode,
.op_encode_elem = bhnd_nvram_val_bcm_hex_encode_elem,
};
/**
* Broadcom NVRAM string format.
*
* Handles standard, comma-delimited, and octet-string values as used in
* Broadcom NVRAM data.
*/
const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_string_fmt = {
.name = "bcm-string",
.native_type = BHND_NVRAM_TYPE_STRING,
.op_encode = bhnd_nvram_val_bcmstr_encode,
};
/** Broadcom comma-delimited string. */
static const bhnd_nvram_val_fmt_t bhnd_nvram_val_bcm_string_csv_fmt = {
.name = "bcm-string[]",
.native_type = BHND_NVRAM_TYPE_STRING,
.op_filter = bhnd_nvram_val_bcmstr_csv_filter,
.op_next = bhnd_nvram_val_bcmstr_csv_next,
};
/**
* Common hex/decimal integer filter implementation.
*/
static int
bhnd_nvram_val_bcm_int_filter(const bhnd_nvram_val_fmt_t **fmt, const void *inp,
size_t ilen, bhnd_nvram_type itype)
{
bhnd_nvram_type itype_base;
itype_base = bhnd_nvram_base_type(itype);
switch (itype_base) {
case BHND_NVRAM_TYPE_STRING:
/*
* If the input is a string, delegate to the Broadcom
* string format -- preserving the original string value
* takes priority over enforcing hexadecimal/integer string
* formatting.
*/
*fmt = &bhnd_nvram_val_bcm_string_fmt;
return (0);
default:
if (bhnd_nvram_is_int_type(itype_base))
return (0);
return (EFTYPE);
}
}
/**
* Broadcom hex/decimal integer encode implementation.
*/
static int
bhnd_nvram_val_bcm_int_encode(bhnd_nvram_val_t *value, void *outp, size_t *olen,
bhnd_nvram_type otype)
{
/* If encoding to a string, format multiple elements (if any) with a
* comma delimiter. */
if (otype == BHND_NVRAM_TYPE_STRING)
return (bhnd_nvram_val_printf(value, "%[]s", outp, olen, ","));
return (bhnd_nvram_val_generic_encode(value, outp, olen, otype));
}
/**
* Broadcom hex integer encode_elem implementation.
*/
static int
bhnd_nvram_val_bcm_hex_encode_elem(bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
bhnd_nvram_type itype;
ssize_t width;
int error;
itype = bhnd_nvram_val_elem_type(value);
BHND_NV_ASSERT(bhnd_nvram_is_int_type(itype), ("invalid type"));
/* If not encoding as a string, perform generic value encoding */
if (otype != BHND_NVRAM_TYPE_STRING)
return (bhnd_nvram_val_generic_encode_elem(value, inp, ilen,
outp, olen, otype));
/* If the value is a signed, negative value, encode as a decimal
* string */
if (bhnd_nvram_is_signed_type(itype)) {
int64_t sval;
size_t slen;
bhnd_nvram_type stype;
stype = BHND_NVRAM_TYPE_INT64;
slen = sizeof(sval);
/* Fetch 64-bit signed representation */
error = bhnd_nvram_value_coerce(inp, ilen, itype, &sval, &slen,
stype);
if (error)
return (error);
/* Decimal encoding required? */
if (sval < 0)
return (bhnd_nvram_value_printf("%I64d", &sval, slen,
stype, outp, olen, otype));
}
/*
* Encode the value as a hex string.
*
* Most producers of Broadcom NVRAM values zero-pad hex values out to
* their native width (width * two hex characters), and we do the same
* for compatibility
*/
width = bhnd_nvram_value_size(itype, NULL, 0, 1) * 2;
return (bhnd_nvram_value_printf("0x%0*I64X", inp, ilen, itype,
outp, olen, width));
}
/**
* Broadcom decimal integer encode_elem implementation.
*/
static int
bhnd_nvram_val_bcm_decimal_encode_elem(bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
const char *sfmt;
bhnd_nvram_type itype;
itype = bhnd_nvram_val_elem_type(value);
BHND_NV_ASSERT(bhnd_nvram_is_int_type(itype), ("invalid type"));
/* If not encoding as a string, perform generic value encoding */
if (otype != BHND_NVRAM_TYPE_STRING)
return (bhnd_nvram_val_generic_encode_elem(value, inp, ilen,
outp, olen, otype));
sfmt = bhnd_nvram_is_signed_type(itype) ? "%I64d" : "%I64u";
return (bhnd_nvram_value_printf(sfmt, inp, ilen, itype, outp, olen));
}
/**
* Broadcom LED duty-cycle filter.
*/
static int
bhnd_nvram_val_bcm_leddc_filter(const bhnd_nvram_val_fmt_t **fmt,
const void *inp, size_t ilen, bhnd_nvram_type itype)
{
const char *p;
size_t plen;
switch (itype) {
case BHND_NVRAM_TYPE_UINT16:
case BHND_NVRAM_TYPE_UINT32:
return (0);
case BHND_NVRAM_TYPE_STRING:
/* Trim any whitespace */
p = inp;
plen = bhnd_nvram_trim_field(&p, ilen, '\0');
/* If the value is not a valid integer string, delegate to the
* Broadcom string format */
if (!bhnd_nvram_ident_num_string(p, plen, 0, NULL))
*fmt = &bhnd_nvram_val_bcm_string_fmt;
return (0);
default:
return (EFTYPE);
}
}
/**
* Broadcom LED duty-cycle encode.
*/
static int
bhnd_nvram_val_bcm_leddc_encode_elem(bhnd_nvram_val_t *value, const void *inp,
size_t ilen, void *outp, size_t *olen, bhnd_nvram_type otype)
{
bhnd_nvram_type itype;
size_t limit, nbytes;
int error;
uint16_t led16;
uint32_t led32;
bool led16_lossy;
union {
uint16_t u16;
uint32_t u32;
} strval;
/*
* LED duty-cycle values represent the on/off periods as a 32-bit
* integer, with the top 16 bits representing on cycles, and the
* bottom 16 representing off cycles.
*
* LED duty cycle values have three different formats:
*
* - SPROM: A 16-bit unsigned integer, with on/off cycles encoded
* as 8-bit values.
* - NVRAM: A 16-bit decimal or hexadecimal string, with on/off
* cycles encoded as 8-bit values as per the SPROM format.
* - NVRAM: A 32-bit decimal or hexadecimal string, with on/off
* cycles encoded as 16-bit values.
*
* To convert from a 16-bit representation to a 32-bit representation:
* ((value & 0xFF00) << 16) | ((value & 0x00FF) << 8)
*
* To convert from a 32-bit representation to a 16-bit representation,
* perform the same operation in reverse, discarding the lower 8-bits
* of each half of the 32-bit representation:
* ((value >> 16) & 0xFF00) | ((value >> 8) & 0x00FF)
*/
itype = bhnd_nvram_val_elem_type(value);
nbytes = 0;
led16_lossy = false;
/* Determine output byte limit */
if (outp != NULL)
limit = *olen;
else
limit = 0;
/* If the input/output types match, just delegate to standard value
* encoding support */
if (otype == itype) {
return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen,
otype));
}
/* If our value is a string, it may either be a 16-bit or a 32-bit
* representation of the duty cycle */
if (itype == BHND_NVRAM_TYPE_STRING) {
const char *p;
uint32_t ival;
size_t nlen, parsed;
/* Parse integer value */
p = inp;
nlen = sizeof(ival);
error = bhnd_nvram_parse_int(p, ilen, 0, &parsed, &ival, &nlen,
BHND_NVRAM_TYPE_UINT32);
if (error)
return (error);
/* Trailing garbage? */
if (parsed < ilen && *(p+parsed) != '\0')
return (EFTYPE);
/* Point inp and itype to either our parsed 32-bit or 16-bit
* value */
inp = &strval;
if (ival & 0xFFFF0000) {
strval.u32 = ival;
itype = BHND_NVRAM_TYPE_UINT32;
} else {
strval.u16 = ival;
itype = BHND_NVRAM_TYPE_UINT16;
}
}
/* Populate both u32 and (possibly lossy) u16 LEDDC representations */
switch (itype) {
case BHND_NVRAM_TYPE_UINT16: {
led16 = *(const uint16_t *)inp;
led32 = ((led16 & 0xFF00) << 16) | ((led16 & 0x00FF) << 8);
/* If all bits are set in the 16-bit value (indicating that
* the value is 'unset' in SPROM), we must update the 32-bit
* representation to match. */
if (led16 == UINT16_MAX)
led32 = UINT32_MAX;
break;
}
case BHND_NVRAM_TYPE_UINT32:
led32 = *(const uint32_t *)inp;
led16 = ((led32 >> 16) & 0xFF00) | ((led32 >> 8) & 0x00FF);
/*
* Determine whether the led16 conversion is lossy:
*
* - If the lower 8 bits of each half of the 32-bit value
* aren't set, we can safely use the 16-bit representation
* without losing data.
* - If all bits in the 32-bit value are set, the variable is
* treated as unset in SPROM. We can safely use the 16-bit
* representation without losing data.
*/
if ((led32 & 0x00FF00FF) != 0 && led32 != UINT32_MAX)
led16_lossy = true;
break;
default:
BHND_NV_PANIC("unsupported backing data type: %s",
bhnd_nvram_type_name(itype));
}
/*
* Encode as requested output type.
*/
switch (otype) {
case BHND_NVRAM_TYPE_STRING:
/*
* Prefer 16-bit format.
*/
if (!led16_lossy) {
return (bhnd_nvram_value_printf("0x%04hX", &led16,
sizeof(led16), BHND_NVRAM_TYPE_UINT16, outp, olen));
} else {
return (bhnd_nvram_value_printf("0x%04X", &led32,
sizeof(led32), BHND_NVRAM_TYPE_UINT32, outp, olen));
}
break;
case BHND_NVRAM_TYPE_UINT16: {
/* Can we encode as uint16 without losing data? */
if (led16_lossy)
return (ERANGE);
/* Write led16 format */
nbytes += sizeof(uint16_t);
if (limit >= nbytes)
*(uint16_t *)outp = led16;
break;
}
case BHND_NVRAM_TYPE_UINT32:
/* Write led32 format */
nbytes += sizeof(uint32_t);
if (limit >= nbytes)
*(uint32_t *)outp = led32;
break;
default:
/* No other output formats are supported */
return (EFTYPE);
}
/* Provide the actual length */
*olen = nbytes;
/* Report insufficient space (if output was requested) */
if (limit < nbytes && outp != NULL)
return (ENOMEM);
return (0);
}
/**
* Broadcom NVRAM string encoding.
*/
static int
bhnd_nvram_val_bcmstr_encode(bhnd_nvram_val_t *value, void *outp,
size_t *olen, bhnd_nvram_type otype)
{
bhnd_nvram_val_t array;
const bhnd_nvram_val_fmt_t *array_fmt;
const void *inp;
bhnd_nvram_type itype;
size_t ilen;
int error;
inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
/* If the output is not an array type (or if it's a character array),
* we can fall back on standard string encoding */
if (!bhnd_nvram_is_array_type(otype) ||
otype == BHND_NVRAM_TYPE_CHAR_ARRAY)
{
return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen,
otype));
}
/* Otherwise, we need to interpret our value as either a macaddr
* string, or a comma-delimited string. */
inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
if (bhnd_nvram_ident_octet_string(inp, ilen, NULL, NULL))
array_fmt = &bhnd_nvram_val_bcm_macaddr_string_fmt;
else
array_fmt = &bhnd_nvram_val_bcm_string_csv_fmt;
/* Wrap in array-typed representation */
error = bhnd_nvram_val_init(&array, array_fmt, inp, ilen, itype,
BHND_NVRAM_VAL_BORROW_DATA);
if (error) {
BHND_NV_LOG("error initializing array representation: %d\n",
error);
return (error);
}
/* Ask the array-typed value to perform the encode */
error = bhnd_nvram_val_encode(&array, outp, olen, otype);
if (error)
BHND_NV_LOG("error encoding array representation: %d\n", error);
bhnd_nvram_val_release(&array);
return (error);
}
/**
* Broadcom NVRAM comma-delimited string filter.
*/
static int
bhnd_nvram_val_bcmstr_csv_filter(const bhnd_nvram_val_fmt_t **fmt,
const void *inp, size_t ilen, bhnd_nvram_type itype)
{
switch (itype) {
case BHND_NVRAM_TYPE_STRING:
case BHND_NVRAM_TYPE_STRING_ARRAY:
return (0);
default:
return (EFTYPE);
}
}
/**
* Broadcom NVRAM comma-delimited string iteration.
*/
static const void *
bhnd_nvram_val_bcmstr_csv_next(bhnd_nvram_val_t *value, const void *prev,
size_t *len)
{
const char *next;
const char *inp;
bhnd_nvram_type itype;
size_t ilen, remain;
char delim;
/* Fetch backing representation */
inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
/* Fetch next value */
switch (itype) {
case BHND_NVRAM_TYPE_STRING:
/* Zero-length array? */
if (ilen == 0)
return (NULL);
if (prev == NULL) {
/* First element */
next = inp;
remain = ilen;
delim = ',';
} else {
/* Advance to the previous element's delimiter */
next = (const char *)prev + *len;
/* Did we hit the end of the string? */
if ((size_t)(next - inp) >= ilen)
return (NULL);
/* Fetch (and skip past) the delimiter */
delim = *next;
next++;
remain = ilen - (size_t)(next - inp);
/* Was the delimiter the final character? */
if (remain == 0)
return (NULL);
}
/* Parse the field value, up to the next delimiter */
*len = bhnd_nvram_parse_field(&next, remain, delim);
return (next);
case BHND_NVRAM_TYPE_STRING_ARRAY:
next = bhnd_nvram_string_array_next(inp, ilen, prev);
if (next != NULL) {
*len = strlen(next);
/* Account for trailing NUL */
if (*len + (size_t)(next - inp) < ilen)
(*len)++;
}
return (next);
default:
BHND_NV_PANIC("unsupported type: %d", itype);
}
}
/**
* MAC address filter.
*/
static int
bhnd_nvram_val_bcm_macaddr_filter(const bhnd_nvram_val_fmt_t **fmt,
const void *inp, size_t ilen, bhnd_nvram_type itype)
{
switch (itype) {
case BHND_NVRAM_TYPE_UINT8_ARRAY:
return (0);
case BHND_NVRAM_TYPE_STRING:
/* Let bcm_macaddr_string format handle it */
*fmt = &bhnd_nvram_val_bcm_macaddr_string_fmt;
return (0);
default:
return (EFTYPE);
}
}
/**
* MAC address encoding.
*/
static int
bhnd_nvram_val_bcm_macaddr_encode(bhnd_nvram_val_t *value, void *outp,
size_t *olen, bhnd_nvram_type otype)
{
const void *inp;
bhnd_nvram_type itype;
size_t ilen;
/*
* If converting to a string (or a single-element string array),
* produce an octet string (00:00:...).
*/
if (bhnd_nvram_base_type(otype) == BHND_NVRAM_TYPE_STRING) {
return (bhnd_nvram_val_printf(value, "%[]02hhX", outp, olen,
":"));
}
/* Otherwise, use standard encoding support */
inp = bhnd_nvram_val_bytes(value, &ilen, &itype);
return (bhnd_nvram_value_coerce(inp, ilen, itype, outp, olen, otype));}
/**
* MAC address string filter.
*/
static int
bhnd_nvram_val_bcm_macaddr_string_filter(const bhnd_nvram_val_fmt_t **fmt,
const void *inp, size_t ilen, bhnd_nvram_type itype)
{
switch (itype) {
case BHND_NVRAM_TYPE_STRING:
/* Use the standard Broadcom string format implementation if
* the input is not an octet string. */
if (!bhnd_nvram_ident_octet_string(inp, ilen, NULL, NULL))
*fmt = &bhnd_nvram_val_bcm_string_fmt;
return (0);
default:
return (EFTYPE);
}
}
/**
* MAC address string octet encoding.
*/
static int
bhnd_nvram_val_bcm_macaddr_string_encode_elem(bhnd_nvram_val_t *value,
const void *inp, size_t ilen, void *outp, size_t *olen,
bhnd_nvram_type otype)
{
size_t nparsed;
int error;
/* If integer encoding is requested, explicitly parse our
* non-0x-prefixed as a base 16 integer value */
if (bhnd_nvram_is_int_type(otype)) {
error = bhnd_nvram_parse_int(inp, ilen, 16, &nparsed, outp,
olen, otype);
if (error)
return (error);
if (nparsed != ilen)
return (EFTYPE);
return (0);
}
/* Otherwise, use standard encoding support */
return (bhnd_nvram_value_coerce(inp, ilen,
bhnd_nvram_val_elem_type(value), outp, olen, otype));
}
/**
* MAC address string octet iteration.
*/
static const void *
bhnd_nvram_val_bcm_macaddr_string_next(bhnd_nvram_val_t *value, const void *prev,
size_t *len)
{
const char *next;
const char *str;
bhnd_nvram_type stype;
size_t slen, remain;
char delim;
/* Fetch backing string */
str = bhnd_nvram_val_bytes(value, &slen, &stype);
BHND_NV_ASSERT(stype == BHND_NVRAM_TYPE_STRING,
("unsupported type: %d", stype));
/* Zero-length array? */
if (slen == 0)
return (NULL);
if (prev == NULL) {
/* First element */
/* Determine delimiter */
if (!bhnd_nvram_ident_octet_string(str, slen, &delim, NULL)) {
/* Default to comma-delimited parsing */
delim = ',';
}
/* Parsing will start at the base string pointer */
next = str;
remain = slen;
} else {
/* Advance to the previous element's delimiter */
next = (const char *)prev + *len;
/* Did we hit the end of the string? */
if ((size_t)(next - str) >= slen)
return (NULL);
/* Fetch (and skip past) the delimiter */
delim = *next;
next++;
remain = slen - (size_t)(next - str);
/* Was the delimiter the final character? */
if (remain == 0)
return (NULL);
}
/* Parse the field value, up to the next delimiter */
*len = bhnd_nvram_parse_field(&next, remain, delim);
return (next);
}
/**
* Determine whether @p inp is in octet string format, consisting of a
* fields of two hex characters, separated with ':' or '-' delimiters.
*
* This may be used to identify MAC address octet strings
* (BHND_NVRAM_SFMT_MACADDR).
*
* @param inp The string to be parsed.
* @param ilen The length of @p inp, in bytes.
* @param[out] delim On success, the delimiter used by this octet
* string. May be set to NULL if the field
* delimiter is not desired.
* @param[out] nelem On success, the number of fields in this
* octet string. May be set to NULL if the field
* count is not desired.
*
*
* @retval true if @p inp is a valid octet string
* @retval false if @p inp is not a valid octet string.
*/
static bool
bhnd_nvram_ident_octet_string(const char *inp, size_t ilen, char *delim,
size_t *nelem)
{
size_t elem_count;
size_t max_elem_count, min_elem_count;
size_t field_count;
char idelim;
field_count = 0;
/* Require exactly two digits. If we relax this, there is room
* for ambiguity with signed integers and the '-' delimiter */
min_elem_count = 2;
max_elem_count = 2;
/* Identify the delimiter used. The standard delimiter for MAC
* addresses is ':', but some earlier NVRAM formats may use '-' */
for (const char *d = ":-";; d++) {
const char *loc;
/* No delimiter found, not an octet string */
if (*d == '\0')
return (false);
/* Look for the delimiter */
if ((loc = memchr(inp, *d, ilen)) == NULL)
continue;
/* Delimiter found */
idelim = *loc;
break;
}
/* To disambiguate from signed integers, if the delimiter is "-",
* the octets must be exactly 2 chars each */
if (idelim == '-')
min_elem_count = 2;
/* String must be composed of individual octets (zero or more hex
* digits) separated by our delimiter. */
elem_count = 0;
for (const char *p = inp; (size_t)(p - inp) < ilen; p++) {
switch (*p) {
case ':':
case '-':
case '\0':
/* Hit a delim character; all delims must match
* the first delimiter used */
if (*p != '\0' && *p != idelim)
return (false);
/* Must have parsed at least min_elem_count digits */
if (elem_count < min_elem_count)
return (false);
/* Reset element count */
elem_count = 0;
/* Bump field count */
field_count++;
break;
default:
/* More than maximum number of hex digits? */
if (elem_count >= max_elem_count)
return (false);
/* Octet values must be hex digits */
if (!bhnd_nv_isxdigit(*p))
return (false);
elem_count++;
break;
}
}
if (delim != NULL)
*delim = idelim;
if (nelem != NULL)
*nelem = field_count;
return (true);
}
/**
* Determine whether @p inp is in hexadecimal, octal, or decimal string
* format.
*
* - A @p str may be prefixed with a single optional '+' or '-' sign denoting
* signedness.
* - A hexadecimal @p str may include an '0x' or '0X' prefix, denoting that a
* base 16 integer follows.
* - An octal @p str may include a '0' prefix, denoting that an octal integer
* follows.
*
* @param inp The string to be parsed.
* @param ilen The length of @p inp, in bytes.
* @param base The input string's base (2-36), or 0.
* @param[out] obase On success, will be set to the base of the parsed
* integer. May be set to NULL if the base is not
* desired.
*
* @retval true if @p inp is a valid number string
* @retval false if @p inp is not a valid number string.
* @retval false if @p base is invalid.
*/
static bool
bhnd_nvram_ident_num_string(const char *inp, size_t ilen, u_int base,
u_int *obase)
{
size_t nbytes, ndigits;
nbytes = 0;
ndigits = 0;
/* Parse and skip sign */
if (nbytes >= ilen)
return (false);
if (inp[nbytes] == '-' || inp[nbytes] == '+')
nbytes++;
/* Truncated after sign character? */
if (nbytes == ilen)
return (false);
/* Identify (or validate) hex base, skipping 0x/0X prefix */
if (base == 16 || base == 0) {
/* Check for (and skip) 0x/0X prefix */
if (ilen - nbytes >= 2 && inp[nbytes] == '0' &&
(inp[nbytes+1] == 'x' || inp[nbytes+1] == 'X'))
{
base = 16;
nbytes += 2;
}
}
/* Truncated after hex prefix? */
if (nbytes == ilen)
return (false);
/* Differentiate decimal/octal by looking for a leading 0 */
if (base == 0) {
if (inp[nbytes] == '0') {
base = 8;
} else {
base = 10;
}
}
/* Consume and validate all remaining digit characters */
for (; nbytes < ilen; nbytes++) {
u_int carry;
char c;
/* Parse carry value */
c = inp[nbytes];
if (bhnd_nv_isdigit(c)) {
carry = c - '0';
} else if (bhnd_nv_isxdigit(c)) {
if (bhnd_nv_isupper(c))
carry = (c - 'A') + 10;
else
carry = (c - 'a') + 10;
} else {
/* Hit a non-digit character */
return (false);
}
/* If carry is outside the base, it's not a valid digit
* in the current parse context; consider it a non-digit
* character */
if (carry >= base)
return (false);
/* Increment parsed digit count */
ndigits++;
}
/* Empty integer string? */
if (ndigits == 0)
return (false);
/* Valid integer -- provide the base and return */
if (obase != NULL)
*obase = base;
return (true);
}