9be0790d19
This adds support for bhnd_nvram_val_convert_init() and bhnd_nvram_val_convert_new(), which may be used to perform value format-aware encoding of an NVRAM value to a new target format/type. This will be used to simplify converting to/from serialized format-specific NVRAM value representations to common external representations. Approved by: adrian (mentor) Differential Revision: https://reviews.freebsd.org/D8757
1989 lines
51 KiB
C
1989 lines
51 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/endian.h>
|
|
|
|
#ifdef _KERNEL
|
|
#include <sys/param.h>
|
|
#include <sys/ctype.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 <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#endif /* _KERNEL */
|
|
|
|
#include "bhnd_nvram_private.h"
|
|
|
|
#include "bhnd_nvram_datavar.h"
|
|
|
|
#include "bhnd_nvram_data_spromvar.h"
|
|
|
|
/*
|
|
* BHND SPROM NVRAM data class
|
|
*
|
|
* The SPROM data format is a fixed-layout, non-self-descriptive binary format,
|
|
* used on Broadcom wireless and wired adapters, that provides a subset of the
|
|
* variables defined by Broadcom SoC NVRAM formats.
|
|
*/
|
|
BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM",
|
|
sizeof(struct bhnd_nvram_sprom))
|
|
|
|
static int sprom_sort_idx(const void *lhs, const void *rhs);
|
|
|
|
static int sprom_opcode_state_init(struct sprom_opcode_state *state,
|
|
const struct bhnd_sprom_layout *layout);
|
|
static int sprom_opcode_state_reset(struct sprom_opcode_state *state);
|
|
static int sprom_opcode_state_seek(struct sprom_opcode_state *state,
|
|
struct sprom_opcode_idx *indexed);
|
|
|
|
static int sprom_opcode_next_var(struct sprom_opcode_state *state);
|
|
static int sprom_opcode_parse_var(struct sprom_opcode_state *state,
|
|
struct sprom_opcode_idx *indexed);
|
|
|
|
static int sprom_opcode_next_binding(struct sprom_opcode_state *state);
|
|
|
|
static int sprom_opcode_set_type(struct sprom_opcode_state *state,
|
|
bhnd_nvram_type type);
|
|
|
|
static int sprom_opcode_set_var(struct sprom_opcode_state *state,
|
|
size_t vid);
|
|
static int sprom_opcode_clear_var(struct sprom_opcode_state *state);
|
|
static int sprom_opcode_flush_bind(struct sprom_opcode_state *state);
|
|
static int sprom_opcode_read_opval32(struct sprom_opcode_state *state,
|
|
uint8_t type, uint32_t *opval);
|
|
static int sprom_opcode_apply_scale(struct sprom_opcode_state *state,
|
|
uint32_t *value);
|
|
|
|
static int sprom_opcode_step(struct sprom_opcode_state *state,
|
|
uint8_t *opcode);
|
|
|
|
#define SPROM_OP_BAD(_state, _fmt, ...) \
|
|
BHND_NV_LOG("bad encoding at %td: " _fmt, \
|
|
(_state)->input - (_state)->layout->bindings, ##__VA_ARGS__)
|
|
|
|
#define SPROM_COOKIE_TO_NVRAM(_cookie) \
|
|
bhnd_nvram_get_vardefn(((struct sprom_opcode_idx *)_cookie)->vid)
|
|
|
|
/**
|
|
* Read the magic value from @p io, and verify that it matches
|
|
* the @p layout's expected magic value.
|
|
*
|
|
* If @p layout does not defined a magic value, @p magic is set to 0x0
|
|
* and success is returned.
|
|
*
|
|
* @param io An I/O context mapping the SPROM data to be identified.
|
|
* @param layout The SPROM layout against which @p io should be verified.
|
|
* @param[out] magic On success, the SPROM magic value.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If checking @p io otherwise fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
static int
|
|
bhnd_nvram_sprom_check_magic(struct bhnd_nvram_io *io,
|
|
const struct bhnd_sprom_layout *layout, uint16_t *magic)
|
|
{
|
|
int error;
|
|
|
|
/* Skip if layout does not define a magic value */
|
|
if (layout->flags & SPROM_LAYOUT_MAGIC_NONE)
|
|
return (0);
|
|
|
|
/* Read the magic value */
|
|
error = bhnd_nvram_io_read(io, layout->magic_offset, magic,
|
|
sizeof(*magic));
|
|
if (error)
|
|
return (error);
|
|
|
|
*magic = le16toh(*magic);
|
|
|
|
/* If the signature does not match, skip to next layout */
|
|
if (*magic != layout->magic_value)
|
|
return (ENXIO);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Attempt to identify the format of the SPROM data mapped by @p io.
|
|
*
|
|
* The SPROM data format does not provide any identifying information at a
|
|
* known offset, instead requiring that we iterate over the known SPROM image
|
|
* sizes until we are able to compute a valid checksum (and, for later
|
|
* revisions, validate a signature at a revision-specific offset).
|
|
*
|
|
* @param io An I/O context mapping the SPROM data to be identified.
|
|
* @param[out] ident On success, the identified SPROM layout.
|
|
* @param[out] shadow On success, a correctly sized iobuf instance mapping
|
|
* a copy of the identified SPROM image. The caller is
|
|
* responsible for deallocating this instance via
|
|
* bhnd_nvram_io_free()
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If identifying @p io otherwise fails, a regular unix
|
|
* error code will be returned.
|
|
*/
|
|
static int
|
|
bhnd_nvram_sprom_ident(struct bhnd_nvram_io *io,
|
|
const struct bhnd_sprom_layout **ident, struct bhnd_nvram_io **shadow)
|
|
{
|
|
struct bhnd_nvram_io *buf;
|
|
uint8_t crc;
|
|
size_t crc_errors;
|
|
size_t sprom_sz_max;
|
|
int error;
|
|
|
|
/* Find the largest SPROM layout size */
|
|
sprom_sz_max = 0;
|
|
for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {
|
|
sprom_sz_max = bhnd_nv_ummax(sprom_sz_max,
|
|
bhnd_sprom_layouts[i].size);
|
|
}
|
|
|
|
/* Allocate backing buffer and initialize CRC state */
|
|
buf = bhnd_nvram_iobuf_empty(0, sprom_sz_max);
|
|
crc = BHND_NVRAM_CRC8_INITIAL;
|
|
crc_errors = 0;
|
|
|
|
/* We iterate the SPROM layouts smallest to largest, allowing us to
|
|
* perform incremental checksum calculation */
|
|
for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {
|
|
const struct bhnd_sprom_layout *layout;
|
|
void *ptr;
|
|
size_t nbytes, nr;
|
|
uint16_t magic;
|
|
uint8_t srev;
|
|
bool crc_valid;
|
|
bool have_magic;
|
|
|
|
layout = &bhnd_sprom_layouts[i];
|
|
nbytes = bhnd_nvram_io_getsize(buf);
|
|
|
|
if ((layout->flags & SPROM_LAYOUT_MAGIC_NONE)) {
|
|
have_magic = false;
|
|
} else {
|
|
have_magic = true;
|
|
}
|
|
|
|
/* Layout instances must be ordered from smallest to largest by
|
|
* the nvram_map compiler */
|
|
if (nbytes > layout->size)
|
|
BHND_NV_PANIC("SPROM layout is defined out-of-order");
|
|
|
|
/* Calculate number of additional bytes to be read */
|
|
nr = layout->size - nbytes;
|
|
|
|
/* Adjust the buffer size and fetch a write pointer */
|
|
if ((error = bhnd_nvram_io_setsize(buf, layout->size)))
|
|
goto failed;
|
|
|
|
error = bhnd_nvram_io_write_ptr(buf, nbytes, &ptr, nr, NULL);
|
|
if (error)
|
|
goto failed;
|
|
|
|
/* Read image data and update CRC (errors are reported
|
|
* after the signature check) */
|
|
if ((error = bhnd_nvram_io_read(io, nbytes, ptr, nr)))
|
|
goto failed;
|
|
|
|
crc = bhnd_nvram_crc8(ptr, nr, crc);
|
|
crc_valid = (crc == BHND_NVRAM_CRC8_VALID);
|
|
if (!crc_valid)
|
|
crc_errors++;
|
|
|
|
/* Fetch SPROM revision */
|
|
error = bhnd_nvram_io_read(buf, layout->srev_offset, &srev,
|
|
sizeof(srev));
|
|
if (error)
|
|
goto failed;
|
|
|
|
/* Early sromrev 1 devices (specifically some BCM440x enet
|
|
* cards) are reported to have been incorrectly programmed
|
|
* with a revision of 0x10. */
|
|
if (layout->rev == 1 && srev == 0x10)
|
|
srev = 0x1;
|
|
|
|
/* Check revision against the layout definition */
|
|
if (srev != layout->rev)
|
|
continue;
|
|
|
|
/* Check the magic value, skipping to the next layout on
|
|
* failure. */
|
|
error = bhnd_nvram_sprom_check_magic(buf, layout, &magic);
|
|
if (error) {
|
|
/* If the CRC is was valid, log the mismatch */
|
|
if (crc_valid || BHND_NV_VERBOSE) {
|
|
BHND_NV_LOG("invalid sprom %hhu signature: "
|
|
"0x%hx (expected 0x%hx)\n", srev,
|
|
magic, layout->magic_value);
|
|
|
|
error = ENXIO;
|
|
goto failed;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Check for an earlier CRC error */
|
|
if (!crc_valid) {
|
|
/* If the magic check succeeded, then we may just have
|
|
* data corruption -- log the CRC error */
|
|
if (have_magic || BHND_NV_VERBOSE) {
|
|
BHND_NV_LOG("sprom %hhu CRC error (crc=%#hhx, "
|
|
"expected=%#x)\n", srev, crc,
|
|
BHND_NVRAM_CRC8_VALID);
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Identified */
|
|
*shadow = buf;
|
|
*ident = layout;
|
|
return (0);
|
|
}
|
|
|
|
/* No match -- set error and fallthrough */
|
|
error = ENXIO;
|
|
if (crc_errors > 0 && BHND_NV_VERBOSE) {
|
|
BHND_NV_LOG("sprom parsing failed with %zu CRC errors\n",
|
|
crc_errors);
|
|
}
|
|
|
|
failed:
|
|
bhnd_nvram_io_free(buf);
|
|
return (error);
|
|
}
|
|
|
|
static int
|
|
bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io)
|
|
{
|
|
const struct bhnd_sprom_layout *layout;
|
|
struct bhnd_nvram_io *shadow;
|
|
int error;
|
|
|
|
/* Try to parse the input */
|
|
if ((error = bhnd_nvram_sprom_ident(io, &layout, &shadow)))
|
|
return (error);
|
|
|
|
/* Clean up the shadow iobuf */
|
|
bhnd_nvram_io_free(shadow);
|
|
|
|
return (BHND_NVRAM_DATA_PROBE_DEFAULT);
|
|
}
|
|
|
|
static int
|
|
bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
|
|
{
|
|
struct bhnd_nvram_sprom *sp;
|
|
size_t num_vars;
|
|
int error;
|
|
|
|
sp = (struct bhnd_nvram_sprom *)nv;
|
|
|
|
/* Identify the SPROM input data */
|
|
if ((error = bhnd_nvram_sprom_ident(io, &sp->layout, &sp->data)))
|
|
goto failed;
|
|
|
|
/* Initialize SPROM binding eval state */
|
|
if ((error = sprom_opcode_state_init(&sp->state, sp->layout)))
|
|
goto failed;
|
|
|
|
/* Allocate our opcode index */
|
|
sp->num_idx = sp->layout->num_vars;
|
|
if ((sp->idx = bhnd_nv_calloc(sp->num_idx, sizeof(*sp->idx))) == NULL)
|
|
goto failed;
|
|
|
|
/* Parse out index entries from our stateful opcode stream */
|
|
for (num_vars = 0; num_vars < sp->num_idx; num_vars++) {
|
|
size_t opcodes;
|
|
|
|
/* Seek to next entry */
|
|
if ((error = sprom_opcode_next_var(&sp->state))) {
|
|
SPROM_OP_BAD(&sp->state,
|
|
"error reading expected variable entry: %d\n",
|
|
error);
|
|
goto failed;
|
|
}
|
|
|
|
/* We limit the SPROM index representations to the minimal
|
|
* type widths capable of covering all known layouts */
|
|
|
|
/* Save SPROM image offset */
|
|
if (sp->state.offset > UINT16_MAX) {
|
|
SPROM_OP_BAD(&sp->state,
|
|
"cannot index large offset %u\n", sp->state.offset);
|
|
}
|
|
sp->idx[num_vars].offset = sp->state.offset;
|
|
|
|
/* Save current variable ID */
|
|
if (sp->state.vid > UINT16_MAX) {
|
|
SPROM_OP_BAD(&sp->state,
|
|
"cannot index large vid %zu\n", sp->state.vid);
|
|
}
|
|
sp->idx[num_vars].vid = sp->state.vid;
|
|
|
|
/* Save opcode position */
|
|
opcodes = (sp->state.input - sp->layout->bindings);
|
|
if (opcodes > UINT16_MAX) {
|
|
SPROM_OP_BAD(&sp->state,
|
|
"cannot index large opcode offset %zu\n", opcodes);
|
|
}
|
|
sp->idx[num_vars].opcodes = opcodes;
|
|
}
|
|
|
|
/* Should have reached end of binding table; next read must return
|
|
* ENOENT */
|
|
if ((error = sprom_opcode_next_var(&sp->state)) != ENOENT) {
|
|
BHND_NV_LOG("expected EOF parsing binding table: %d\n", error);
|
|
goto failed;
|
|
}
|
|
|
|
/* Sort index by variable ID, ascending */
|
|
qsort(sp->idx, sp->num_idx, sizeof(sp->idx[0]), sprom_sort_idx);
|
|
|
|
return (0);
|
|
|
|
failed:
|
|
if (sp->data != NULL)
|
|
bhnd_nvram_io_free(sp->data);
|
|
|
|
if (sp->idx != NULL)
|
|
bhnd_nv_free(sp->idx);
|
|
|
|
return (error);
|
|
}
|
|
|
|
/* sort function for sprom_opcode_idx values */
|
|
static int
|
|
sprom_sort_idx(const void *lhs, const void *rhs)
|
|
{
|
|
const struct sprom_opcode_idx *l, *r;
|
|
|
|
l = lhs;
|
|
r = rhs;
|
|
|
|
if (l->vid < r->vid)
|
|
return (-1);
|
|
if (l->vid > r->vid)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
static void
|
|
bhnd_nvram_sprom_free(struct bhnd_nvram_data *nv)
|
|
{
|
|
struct bhnd_nvram_sprom *sp = (struct bhnd_nvram_sprom *)nv;
|
|
|
|
bhnd_nvram_io_free(sp->data);
|
|
bhnd_nv_free(sp->idx);
|
|
}
|
|
|
|
size_t
|
|
bhnd_nvram_sprom_count(struct bhnd_nvram_data *nv)
|
|
{
|
|
struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv;
|
|
return (sprom->layout->num_vars);
|
|
}
|
|
|
|
static int
|
|
bhnd_nvram_sprom_size(struct bhnd_nvram_data *nv, size_t *size)
|
|
{
|
|
struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv;
|
|
|
|
/* The serialized form will be identical in length
|
|
* to our backing buffer representation */
|
|
*size = bhnd_nvram_io_getsize(sprom->data);
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhnd_nvram_sprom_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len)
|
|
{
|
|
struct bhnd_nvram_sprom *sprom;
|
|
size_t limit, req_len;
|
|
int error;
|
|
|
|
sprom = (struct bhnd_nvram_sprom *)nv;
|
|
limit = *len;
|
|
|
|
/* Provide the required size */
|
|
if ((error = bhnd_nvram_sprom_size(nv, &req_len)))
|
|
return (error);
|
|
|
|
*len = req_len;
|
|
|
|
if (buf == NULL) {
|
|
return (0);
|
|
} else if (*len > limit) {
|
|
return (ENOMEM);
|
|
}
|
|
|
|
/* Write to the output buffer */
|
|
return (bhnd_nvram_io_read(sprom->data, 0x0, buf, *len));
|
|
}
|
|
|
|
static uint32_t
|
|
bhnd_nvram_sprom_caps(struct bhnd_nvram_data *nv)
|
|
{
|
|
return (BHND_NVRAM_DATA_CAP_INDEXED);
|
|
}
|
|
|
|
static const char *
|
|
bhnd_nvram_sprom_next(struct bhnd_nvram_data *nv, void **cookiep)
|
|
{
|
|
struct bhnd_nvram_sprom *sp;
|
|
struct sprom_opcode_idx *idx_entry;
|
|
size_t idx_next;
|
|
const struct bhnd_nvram_vardefn *var;
|
|
|
|
sp = (struct bhnd_nvram_sprom *)nv;
|
|
|
|
/* Seek to appropriate starting point */
|
|
if (*cookiep == NULL) {
|
|
/* Start search at first index entry */
|
|
idx_next = 0;
|
|
} else {
|
|
/* Determine current index position */
|
|
idx_entry = *cookiep;
|
|
idx_next = (size_t)(idx_entry - sp->idx);
|
|
BHND_NV_ASSERT(idx_next < sp->num_idx,
|
|
("invalid index %zu; corrupt cookie?", idx_next));
|
|
|
|
/* Advance to next entry */
|
|
idx_next++;
|
|
|
|
/* Check for EOF */
|
|
if (idx_next == sp->num_idx)
|
|
return (NULL);
|
|
}
|
|
|
|
/* Skip entries that are disabled by virtue of IGNALL1 */
|
|
for (; idx_next < sp->num_idx; idx_next++) {
|
|
/* Fetch index entry and update cookiep */
|
|
idx_entry = &sp->idx[idx_next];
|
|
*cookiep = idx_entry;
|
|
|
|
/* Fetch variable definition */
|
|
var = bhnd_nvram_get_vardefn(idx_entry->vid);
|
|
|
|
/* We might need to parse the variable's value to determine
|
|
* whether it should be treated as unset */
|
|
if (var->flags & BHND_NVRAM_VF_IGNALL1) {
|
|
int error;
|
|
size_t len;
|
|
|
|
error = bhnd_nvram_sprom_getvar(nv, *cookiep, NULL,
|
|
&len, var->type);
|
|
if (error) {
|
|
BHND_NV_ASSERT(error == ENOENT, ("unexpected "
|
|
"error parsing variable: %d", error));
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Found! */
|
|
return (var->name);
|
|
}
|
|
|
|
/* Reached end of index entries */
|
|
return (NULL);
|
|
}
|
|
|
|
/* bsearch function used by bhnd_nvram_sprom_find() */
|
|
static int
|
|
bhnd_nvram_sprom_find_vid_compare(const void *key, const void *rhs)
|
|
{
|
|
const struct sprom_opcode_idx *r;
|
|
size_t l;
|
|
|
|
l = *(const size_t *)key;
|
|
r = rhs;
|
|
|
|
if (l < r->vid)
|
|
return (-1);
|
|
if (l > r->vid)
|
|
return (1);
|
|
return (0);
|
|
}
|
|
|
|
static void *
|
|
bhnd_nvram_sprom_find(struct bhnd_nvram_data *nv, const char *name)
|
|
{
|
|
struct bhnd_nvram_sprom *sp;
|
|
const struct bhnd_nvram_vardefn *var;
|
|
size_t vid;
|
|
|
|
sp = (struct bhnd_nvram_sprom *)nv;
|
|
|
|
/* Determine the variable ID for the given name */
|
|
if ((var = bhnd_nvram_find_vardefn(name)) == NULL)
|
|
return (NULL);
|
|
|
|
vid = bhnd_nvram_get_vardefn_id(var);
|
|
|
|
/* Search our index for the variable ID */
|
|
return (bsearch(&vid, sp->idx, sp->num_idx, sizeof(sp->idx[0]),
|
|
bhnd_nvram_sprom_find_vid_compare));
|
|
}
|
|
|
|
/**
|
|
* Read the value of @p type from the SPROM data at @p offset, apply @p mask
|
|
* and @p shift, and OR with the existing @p value.
|
|
*
|
|
* @param sp The SPROM data instance.
|
|
* @param var The NVRAM variable definition
|
|
* @param type The type to read at @p offset
|
|
* @param offset The data offset to be read.
|
|
* @param mask The mask to be applied to the value read at @p offset.
|
|
* @param shift The shift to be applied after masking; if positive, a right
|
|
* shift will be applied, if negative, a left shift.
|
|
* @param value The read destination; the parsed value will be OR'd with the
|
|
* current contents of @p value.
|
|
*/
|
|
static int
|
|
bhnd_nvram_sprom_read_offset(struct bhnd_nvram_sprom *sp,
|
|
const struct bhnd_nvram_vardefn *var, bhnd_nvram_type type,
|
|
size_t offset, uint32_t mask, int8_t shift,
|
|
union bhnd_nvram_sprom_intv *value)
|
|
{
|
|
size_t sp_width;
|
|
int error;
|
|
union {
|
|
uint8_t u8;
|
|
uint16_t u16;
|
|
uint32_t u32;
|
|
int8_t s8;
|
|
int16_t s16;
|
|
int32_t s32;
|
|
} sp_value;
|
|
|
|
/* Determine type width */
|
|
sp_width = bhnd_nvram_type_width(type);
|
|
if (sp_width == 0) {
|
|
/* Variable-width types are unsupported */
|
|
BHND_NV_LOG("invalid %s SPROM offset type %d\n", var->name,
|
|
type);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
/* Perform read */
|
|
error = bhnd_nvram_io_read(sp->data, offset, &sp_value,
|
|
sp_width);
|
|
if (error) {
|
|
BHND_NV_LOG("error reading %s SPROM offset %#zx: %d\n",
|
|
var->name, offset, error);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
#define NV_PARSE_INT(_type, _src, _dest, _swap) do { \
|
|
/* Swap to host byte order */ \
|
|
sp_value. _src = (_type) _swap(sp_value. _src); \
|
|
\
|
|
/* Mask and shift the value */ \
|
|
sp_value. _src &= mask; \
|
|
if (shift > 0) { \
|
|
sp_value. _src >>= shift; \
|
|
} else if (shift < 0) { \
|
|
sp_value. _src <<= -shift; \
|
|
} \
|
|
\
|
|
/* Emit output, widening to 32-bit representation */ \
|
|
value-> _dest |= sp_value. _src; \
|
|
} while(0)
|
|
|
|
/* Apply mask/shift and widen to a common 32bit representation */
|
|
switch (type) {
|
|
case BHND_NVRAM_TYPE_UINT8:
|
|
NV_PARSE_INT(uint8_t, u8, u32, );
|
|
break;
|
|
case BHND_NVRAM_TYPE_UINT16:
|
|
NV_PARSE_INT(uint16_t, u16, u32, le16toh);
|
|
break;
|
|
case BHND_NVRAM_TYPE_UINT32:
|
|
NV_PARSE_INT(uint32_t, u32, u32, le32toh);
|
|
break;
|
|
case BHND_NVRAM_TYPE_INT8:
|
|
NV_PARSE_INT(int8_t, s8, s32, );
|
|
break;
|
|
case BHND_NVRAM_TYPE_INT16:
|
|
NV_PARSE_INT(int16_t, s16, s32, le16toh);
|
|
break;
|
|
case BHND_NVRAM_TYPE_INT32:
|
|
NV_PARSE_INT(int32_t, s32, s32, le32toh);
|
|
break;
|
|
case BHND_NVRAM_TYPE_CHAR:
|
|
NV_PARSE_INT(uint8_t, u8, u32, );
|
|
break;
|
|
|
|
case BHND_NVRAM_TYPE_UINT64:
|
|
case BHND_NVRAM_TYPE_INT64:
|
|
case BHND_NVRAM_TYPE_STRING:
|
|
/* fallthrough (unused by SPROM) */
|
|
default:
|
|
BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
static int
|
|
bhnd_nvram_sprom_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
|
|
size_t *len, bhnd_nvram_type otype)
|
|
{
|
|
bhnd_nvram_val val;
|
|
struct bhnd_nvram_sprom *sp;
|
|
struct sprom_opcode_idx *idx;
|
|
const struct bhnd_nvram_vardefn *var;
|
|
union bhnd_nvram_sprom_storage storage;
|
|
union bhnd_nvram_sprom_storage *inp;
|
|
union bhnd_nvram_sprom_intv intv;
|
|
bhnd_nvram_type var_btype;
|
|
size_t ilen, ipos, iwidth;
|
|
size_t nelem;
|
|
bool all_bits_set;
|
|
int error;
|
|
|
|
sp = (struct bhnd_nvram_sprom *)nv;
|
|
idx = cookiep;
|
|
|
|
BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));
|
|
|
|
/* Fetch canonical variable definition */
|
|
var = SPROM_COOKIE_TO_NVRAM(cookiep);
|
|
BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));
|
|
|
|
/*
|
|
* Fetch the array length from the SPROM variable definition.
|
|
*
|
|
* This generally be identical to the array length provided by the
|
|
* canonical NVRAM variable definition, but some SPROM layouts may
|
|
* define a smaller element count.
|
|
*/
|
|
if ((error = sprom_opcode_parse_var(&sp->state, idx))) {
|
|
BHND_NV_LOG("variable evaluation failed: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
nelem = sp->state.var.nelem;
|
|
if (nelem > var->nelem) {
|
|
BHND_NV_LOG("SPROM array element count %zu cannot be "
|
|
"represented by '%s' element count of %hhu\n", nelem,
|
|
var->name, var->nelem);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
/* Fetch the var's base element type */
|
|
var_btype = bhnd_nvram_base_type(var->type);
|
|
|
|
/* Calculate total byte length of the native encoding */
|
|
if ((iwidth = bhnd_nvram_value_size(NULL, 0, var_btype, 1)) == 0) {
|
|
/* SPROM does not use (and we do not support) decoding of
|
|
* variable-width data types */
|
|
BHND_NV_LOG("invalid SPROM data type: %d", var->type);
|
|
return (EFTYPE);
|
|
}
|
|
ilen = nelem * iwidth;
|
|
|
|
/* Decode into our own local storage. */
|
|
inp = &storage;
|
|
if (ilen > sizeof(storage)) {
|
|
BHND_NV_LOG("error decoding '%s', SPROM_ARRAY_MAXLEN "
|
|
"incorrect\n", var->name);
|
|
return (EFTYPE);
|
|
}
|
|
|
|
/* Zero-initialize our decode buffer; any output elements skipped
|
|
* during decode should default to zero. */
|
|
memset(inp, 0, ilen);
|
|
|
|
/*
|
|
* Decode the SPROM data, iteratively decoding up to nelem values.
|
|
*/
|
|
if ((error = sprom_opcode_state_seek(&sp->state, idx))) {
|
|
BHND_NV_LOG("variable seek failed: %d\n", error);
|
|
return (error);
|
|
}
|
|
|
|
ipos = 0;
|
|
intv.u32 = 0x0;
|
|
if (var->flags & BHND_NVRAM_VF_IGNALL1)
|
|
all_bits_set = true;
|
|
else
|
|
all_bits_set = false;
|
|
while ((error = sprom_opcode_next_binding(&sp->state)) == 0) {
|
|
struct sprom_opcode_bind *binding;
|
|
struct sprom_opcode_var *binding_var;
|
|
bhnd_nvram_type intv_type;
|
|
size_t offset;
|
|
size_t nbyte;
|
|
uint32_t skip_in_bytes;
|
|
void *ptr;
|
|
|
|
BHND_NV_ASSERT(
|
|
sp->state.var_state >= SPROM_OPCODE_VAR_STATE_OPEN,
|
|
("invalid var state"));
|
|
BHND_NV_ASSERT(sp->state.var.have_bind, ("invalid bind state"));
|
|
|
|
binding_var = &sp->state.var;
|
|
binding = &sp->state.var.bind;
|
|
|
|
if (ipos >= nelem) {
|
|
BHND_NV_LOG("output skip %u positioned "
|
|
"%zu beyond nelem %zu\n",
|
|
binding->skip_out, ipos, nelem);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Calculate input skip bytes for this binding */
|
|
skip_in_bytes = binding->skip_in;
|
|
error = sprom_opcode_apply_scale(&sp->state, &skip_in_bytes);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Bind */
|
|
offset = sp->state.offset;
|
|
for (size_t i = 0; i < binding->count; i++) {
|
|
/* Read the offset value, OR'ing with the current
|
|
* value of intv */
|
|
error = bhnd_nvram_sprom_read_offset(sp, var,
|
|
binding_var->base_type,
|
|
offset,
|
|
binding_var->mask,
|
|
binding_var->shift,
|
|
&intv);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* If IGNALL1, record whether value does not have
|
|
* all bits set. */
|
|
if (var->flags & BHND_NVRAM_VF_IGNALL1 &&
|
|
all_bits_set)
|
|
{
|
|
uint32_t all1;
|
|
|
|
all1 = binding_var->mask;
|
|
if (binding_var->shift > 0)
|
|
all1 >>= binding_var->shift;
|
|
else if (binding_var->shift < 0)
|
|
all1 <<= -binding_var->shift;
|
|
|
|
if ((intv.u32 & all1) != all1)
|
|
all_bits_set = false;
|
|
}
|
|
|
|
/* Adjust input position; this was already verified to
|
|
* not overflow/underflow during SPROM opcode
|
|
* evaluation */
|
|
if (binding->skip_in_negative) {
|
|
offset -= skip_in_bytes;
|
|
} else {
|
|
offset += skip_in_bytes;
|
|
}
|
|
|
|
/* Skip writing to inp if additional bindings are
|
|
* required to fully populate intv */
|
|
if (binding->skip_out == 0)
|
|
continue;
|
|
|
|
/* We use bhnd_nvram_value_coerce() to perform
|
|
* overflow-checked coercion from the widened
|
|
* uint32/int32 intv value to the requested output
|
|
* type */
|
|
if (bhnd_nvram_is_signed_type(var_btype))
|
|
intv_type = BHND_NVRAM_TYPE_INT32;
|
|
else
|
|
intv_type = BHND_NVRAM_TYPE_UINT32;
|
|
|
|
/* Calculate address of the current element output
|
|
* position */
|
|
ptr = (uint8_t *)inp + (iwidth * ipos);
|
|
|
|
/* Perform coercion of the array element */
|
|
nbyte = iwidth;
|
|
error = bhnd_nvram_value_coerce(&intv, sizeof(intv),
|
|
intv_type, ptr, &nbyte, var_btype);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Clear temporary state */
|
|
intv.u32 = 0x0;
|
|
|
|
/* Advance output position */
|
|
if (SIZE_MAX - binding->skip_out < ipos) {
|
|
BHND_NV_LOG("output skip %u would overflow "
|
|
"%zu\n", binding->skip_out, ipos);
|
|
return (EINVAL);
|
|
}
|
|
|
|
ipos += binding->skip_out;
|
|
}
|
|
}
|
|
|
|
/* Did we iterate all bindings until hitting end of the variable
|
|
* definition? */
|
|
BHND_NV_ASSERT(error != 0, ("loop terminated early"));
|
|
if (error != ENOENT) {
|
|
return (error);
|
|
}
|
|
|
|
/* If marked IGNALL1 and all bits are set, treat variable as
|
|
* unavailable */
|
|
if ((var->flags & BHND_NVRAM_VF_IGNALL1) && all_bits_set)
|
|
return (ENOENT);
|
|
|
|
|
|
/* Perform value coercion from our local representation */
|
|
error = bhnd_nvram_val_init(&val, var->fmt, inp, ilen, var->type,
|
|
BHND_NVRAM_VAL_BORROW_DATA);
|
|
if (error)
|
|
return (error);
|
|
|
|
error = bhnd_nvram_val_encode(&val, buf, len, otype);
|
|
|
|
/* Clean up */
|
|
bhnd_nvram_val_release(&val);
|
|
return (error);
|
|
}
|
|
|
|
static const void *
|
|
bhnd_nvram_sprom_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
|
|
size_t *len, bhnd_nvram_type *type)
|
|
{
|
|
/* Unsupported */
|
|
return (NULL);
|
|
}
|
|
|
|
static const char *
|
|
bhnd_nvram_sprom_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
|
|
{
|
|
const struct bhnd_nvram_vardefn *var;
|
|
|
|
BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));
|
|
|
|
var = SPROM_COOKIE_TO_NVRAM(cookiep);
|
|
BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));
|
|
|
|
return (var->name);
|
|
}
|
|
|
|
/**
|
|
* Initialize SPROM opcode evaluation state.
|
|
*
|
|
* @param state The opcode state to be initialized.
|
|
* @param layout The SPROM layout to be parsed by this instance.
|
|
*
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If initialization fails, a regular unix error code will be
|
|
* returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_state_init(struct sprom_opcode_state *state,
|
|
const struct bhnd_sprom_layout *layout)
|
|
{
|
|
memset(state, 0, sizeof(*state));
|
|
|
|
state->layout = layout;
|
|
state->input = layout->bindings;
|
|
state->var_state = SPROM_OPCODE_VAR_STATE_NONE;
|
|
|
|
bit_set(state->revs, layout->rev);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Reset SPROM opcode evaluation state; future evaluation will be performed
|
|
* starting at the first opcode.
|
|
*
|
|
* @param state The opcode state to be reset.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If reset fails, a regular unix error code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_state_reset(struct sprom_opcode_state *state)
|
|
{
|
|
return (sprom_opcode_state_init(state, state->layout));
|
|
}
|
|
|
|
/**
|
|
* Reset SPROM opcode evaluation state and seek to the @p indexed position.
|
|
*
|
|
* @param state The opcode state to be reset.
|
|
* @param indexed The indexed location to which we'll seek the opcode state.
|
|
*/
|
|
static int
|
|
sprom_opcode_state_seek(struct sprom_opcode_state *state,
|
|
struct sprom_opcode_idx *indexed)
|
|
{
|
|
int error;
|
|
|
|
BHND_NV_ASSERT(indexed->opcodes < state->layout->bindings_size,
|
|
("index entry references invalid opcode position"));
|
|
|
|
/* Reset state */
|
|
if ((error = sprom_opcode_state_reset(state)))
|
|
return (error);
|
|
|
|
/* Seek to the indexed sprom opcode offset */
|
|
state->input = state->layout->bindings + indexed->opcodes;
|
|
|
|
/* Restore the indexed sprom data offset and VID */
|
|
state->offset = indexed->offset;
|
|
|
|
/* Restore the indexed sprom variable ID */
|
|
if ((error = sprom_opcode_set_var(state, indexed->vid)))
|
|
return (error);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current revision range for @p state. This also resets
|
|
* variable state.
|
|
*
|
|
* @param state The opcode state to update
|
|
* @param start The first revision in the range.
|
|
* @param end The last revision in the range.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If updating @p state fails, a regular unix error code will
|
|
* be returned.
|
|
*/
|
|
static inline int
|
|
sprom_opcode_set_revs(struct sprom_opcode_state *state, uint8_t start,
|
|
uint8_t end)
|
|
{
|
|
int error;
|
|
|
|
/* Validate the revision range */
|
|
if (start > SPROM_OP_REV_MAX ||
|
|
end > SPROM_OP_REV_MAX ||
|
|
end < start)
|
|
{
|
|
SPROM_OP_BAD(state, "invalid revision range: %hhu-%hhu\n",
|
|
start, end);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Clear variable state */
|
|
if ((error = sprom_opcode_clear_var(state)))
|
|
return (error);
|
|
|
|
/* Reset revision mask */
|
|
memset(state->revs, 0x0, sizeof(state->revs));
|
|
bit_nset(state->revs, start, end);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current variable's value mask for @p state.
|
|
*
|
|
* @param state The opcode state to update
|
|
* @param mask The mask to be set
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If updating @p state fails, a regular unix error code will
|
|
* be returned.
|
|
*/
|
|
static inline int
|
|
sprom_opcode_set_mask(struct sprom_opcode_state *state, uint32_t mask)
|
|
{
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "no open variable definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
state->var.mask = mask;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current variable's value shift for @p state.
|
|
*
|
|
* @param state The opcode state to update
|
|
* @param shift The shift to be set
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If updating @p state fails, a regular unix error code will
|
|
* be returned.
|
|
*/
|
|
static inline int
|
|
sprom_opcode_set_shift(struct sprom_opcode_state *state, int8_t shift)
|
|
{
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "no open variable definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
state->var.shift = shift;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Register a new BIND/BINDN operation with @p state.
|
|
*
|
|
* @param state The opcode state to update.
|
|
* @param count The number of elements to be bound.
|
|
* @param skip_in The number of input elements to skip after each bind.
|
|
* @param skip_in_negative If true, the input skip should be subtracted from
|
|
* the current offset after each bind. If false, the input skip should be
|
|
* added.
|
|
* @param skip_out The number of output elements to skip after each bind.
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if a variable definition is not open.
|
|
* @retval EINVAL if @p skip_in and @p count would trigger an overflow or
|
|
* underflow when applied to the current input offset.
|
|
* @retval ERANGE if @p skip_in would overflow uint32_t when multiplied by
|
|
* @p count and the scale value.
|
|
* @retval ERANGE if @p skip_out would overflow uint32_t when multiplied by
|
|
* @p count and the scale value.
|
|
* @retval non-zero If updating @p state otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static inline int
|
|
sprom_opcode_set_bind(struct sprom_opcode_state *state, uint8_t count,
|
|
uint8_t skip_in, bool skip_in_negative, uint8_t skip_out)
|
|
{
|
|
uint32_t iskip_total;
|
|
uint32_t iskip_scaled;
|
|
int error;
|
|
|
|
/* Must have an open variable */
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "no open variable definition\n");
|
|
SPROM_OP_BAD(state, "BIND outside of variable definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Cannot overwite an existing bind definition */
|
|
if (state->var.have_bind) {
|
|
SPROM_OP_BAD(state, "BIND overwrites existing definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Must have a count of at least 1 */
|
|
if (count == 0) {
|
|
SPROM_OP_BAD(state, "BIND with zero count\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Scale skip_in by the current type width */
|
|
iskip_scaled = skip_in;
|
|
if ((error = sprom_opcode_apply_scale(state, &iskip_scaled)))
|
|
return (error);
|
|
|
|
/* Calculate total input bytes skipped: iskip_scaled * count) */
|
|
if (iskip_scaled > 0 && UINT32_MAX / iskip_scaled < count) {
|
|
SPROM_OP_BAD(state, "skip_in %hhu would overflow", skip_in);
|
|
return (EINVAL);
|
|
}
|
|
|
|
iskip_total = iskip_scaled * count;
|
|
|
|
/* Verify that the skip_in value won't under/overflow the current
|
|
* input offset. */
|
|
if (skip_in_negative) {
|
|
if (iskip_total > state->offset) {
|
|
SPROM_OP_BAD(state, "skip_in %hhu would underflow "
|
|
"offset %u\n", skip_in, state->offset);
|
|
return (EINVAL);
|
|
}
|
|
} else {
|
|
if (UINT32_MAX - iskip_total < state->offset) {
|
|
SPROM_OP_BAD(state, "skip_in %hhu would overflow "
|
|
"offset %u\n", skip_in, state->offset);
|
|
return (EINVAL);
|
|
}
|
|
}
|
|
|
|
/* Set the actual count and skip values */
|
|
state->var.have_bind = true;
|
|
state->var.bind.count = count;
|
|
state->var.bind.skip_in = skip_in;
|
|
state->var.bind.skip_out = skip_out;
|
|
|
|
state->var.bind.skip_in_negative = skip_in_negative;
|
|
|
|
/* Update total bind count for the current variable */
|
|
state->var.bind_total++;
|
|
|
|
return (0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Apply and clear the current opcode bind state, if any.
|
|
*
|
|
* @param state The opcode state to update.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If updating @p state otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_flush_bind(struct sprom_opcode_state *state)
|
|
{
|
|
int error;
|
|
uint32_t skip;
|
|
|
|
/* Nothing to do? */
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN ||
|
|
!state->var.have_bind)
|
|
return (0);
|
|
|
|
/* Apply SPROM offset adjustment */
|
|
if (state->var.bind.count > 0) {
|
|
skip = state->var.bind.skip_in * state->var.bind.count;
|
|
if ((error = sprom_opcode_apply_scale(state, &skip)))
|
|
return (error);
|
|
|
|
if (state->var.bind.skip_in_negative) {
|
|
state->offset -= skip;
|
|
} else {
|
|
state->offset += skip;
|
|
}
|
|
}
|
|
|
|
/* Clear bind state */
|
|
memset(&state->var.bind, 0, sizeof(state->var.bind));
|
|
state->var.have_bind = false;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current type to @p type, and reset type-specific
|
|
* stream state.
|
|
*
|
|
* @param state The opcode state to update.
|
|
* @param type The new type.
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if @p vid is not a valid variable ID.
|
|
*/
|
|
static int
|
|
sprom_opcode_set_type(struct sprom_opcode_state *state, bhnd_nvram_type type)
|
|
{
|
|
bhnd_nvram_type base_type;
|
|
size_t width;
|
|
uint32_t mask;
|
|
|
|
/* Must have an open variable definition */
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "type set outside variable definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Fetch type width for use as our scale value */
|
|
width = bhnd_nvram_type_width(type);
|
|
if (width == 0) {
|
|
SPROM_OP_BAD(state, "unsupported variable-width type: %d\n",
|
|
type);
|
|
return (EINVAL);
|
|
} else if (width > UINT32_MAX) {
|
|
SPROM_OP_BAD(state, "invalid type width %zu for type: %d\n",
|
|
width, type);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Determine default mask value for the element type */
|
|
base_type = bhnd_nvram_base_type(type);
|
|
switch (base_type) {
|
|
case BHND_NVRAM_TYPE_UINT8:
|
|
case BHND_NVRAM_TYPE_INT8:
|
|
case BHND_NVRAM_TYPE_CHAR:
|
|
mask = UINT8_MAX;
|
|
break;
|
|
case BHND_NVRAM_TYPE_UINT16:
|
|
case BHND_NVRAM_TYPE_INT16:
|
|
mask = UINT16_MAX;
|
|
break;
|
|
case BHND_NVRAM_TYPE_UINT32:
|
|
case BHND_NVRAM_TYPE_INT32:
|
|
mask = UINT32_MAX;
|
|
break;
|
|
case BHND_NVRAM_TYPE_STRING:
|
|
/* fallthrough (unused by SPROM) */
|
|
default:
|
|
SPROM_OP_BAD(state, "unsupported type: %d\n", type);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Update state */
|
|
state->var.base_type = base_type;
|
|
state->var.mask = mask;
|
|
state->var.scale = (uint32_t)width;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Clear current variable state, if any.
|
|
*
|
|
* @param state The opcode state to update.
|
|
*/
|
|
static int
|
|
sprom_opcode_clear_var(struct sprom_opcode_state *state)
|
|
{
|
|
if (state->var_state == SPROM_OPCODE_VAR_STATE_NONE)
|
|
return (0);
|
|
|
|
BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE,
|
|
("incomplete variable definition"));
|
|
BHND_NV_ASSERT(!state->var.have_bind, ("stale bind state"));
|
|
|
|
memset(&state->var, 0, sizeof(state->var));
|
|
state->var_state = SPROM_OPCODE_VAR_STATE_NONE;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current variable's array element count to @p nelem.
|
|
*
|
|
* @param state The opcode state to update.
|
|
* @param nelem The new array length.
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if no open variable definition exists.
|
|
* @retval EINVAL if @p nelem is zero.
|
|
* @retval ENXIO if @p nelem is greater than one, and the current variable does
|
|
* not have an array type.
|
|
* @retval ENXIO if @p nelem exceeds the array length of the NVRAM variable
|
|
* definition.
|
|
*/
|
|
static int
|
|
sprom_opcode_set_nelem(struct sprom_opcode_state *state, uint8_t nelem)
|
|
{
|
|
const struct bhnd_nvram_vardefn *var;
|
|
|
|
/* Must have a defined variable */
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "array length set without open variable "
|
|
"state");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Locate the actual variable definition */
|
|
if ((var = bhnd_nvram_get_vardefn(state->vid)) == NULL) {
|
|
SPROM_OP_BAD(state, "unknown variable ID: %zu\n", state->vid);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Must be greater than zero */
|
|
if (nelem == 0) {
|
|
SPROM_OP_BAD(state, "invalid nelem: %hhu\n", nelem);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* If the variable is not an array-typed value, the array length
|
|
* must be 1 */
|
|
if (!bhnd_nvram_is_array_type(var->type) && nelem != 1) {
|
|
SPROM_OP_BAD(state, "nelem %hhu on non-array %zu\n", nelem,
|
|
state->vid);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Cannot exceed the variable's defined array length */
|
|
if (nelem > var->nelem) {
|
|
SPROM_OP_BAD(state, "nelem %hhu exceeds %zu length %hhu\n",
|
|
nelem, state->vid, var->nelem);
|
|
return (ENXIO);
|
|
}
|
|
|
|
/* Valid length; update state */
|
|
state->var.nelem = nelem;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Set the current variable ID to @p vid, and reset variable-specific
|
|
* stream state.
|
|
*
|
|
* @param state The opcode state to update.
|
|
* @param vid The new variable ID.
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if @p vid is not a valid variable ID.
|
|
*/
|
|
static int
|
|
sprom_opcode_set_var(struct sprom_opcode_state *state, size_t vid)
|
|
{
|
|
const struct bhnd_nvram_vardefn *var;
|
|
int error;
|
|
|
|
BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_NONE,
|
|
("overwrite of open variable definition"));
|
|
|
|
/* Locate the variable definition */
|
|
if ((var = bhnd_nvram_get_vardefn(vid)) == NULL) {
|
|
SPROM_OP_BAD(state, "unknown variable ID: %zu\n", vid);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Update vid and var state */
|
|
state->vid = vid;
|
|
state->var_state = SPROM_OPCODE_VAR_STATE_OPEN;
|
|
|
|
/* Initialize default variable record values */
|
|
memset(&state->var, 0x0, sizeof(state->var));
|
|
|
|
/* Set initial base type */
|
|
if ((error = sprom_opcode_set_type(state, var->type)))
|
|
return (error);
|
|
|
|
/* Set default array length */
|
|
if ((error = sprom_opcode_set_nelem(state, var->nelem)))
|
|
return (error);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Mark the currently open variable definition as complete.
|
|
*
|
|
* @param state The opcode state to update.
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if no incomplete open variable definition exists.
|
|
*/
|
|
static int
|
|
sprom_opcode_end_var(struct sprom_opcode_state *state)
|
|
{
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "no open variable definition\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
state->var_state = SPROM_OPCODE_VAR_STATE_DONE;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Apply the current scale to @p value.
|
|
*
|
|
* @param state The SPROM opcode state.
|
|
* @param[in,out] value The value to scale
|
|
*
|
|
* @retval 0 success
|
|
* @retval EINVAL if no open variable definition exists.
|
|
* @retval EINVAL if applying the current scale would overflow.
|
|
*/
|
|
static int
|
|
sprom_opcode_apply_scale(struct sprom_opcode_state *state, uint32_t *value)
|
|
{
|
|
/* Must have a defined variable (and thus, scale) */
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {
|
|
SPROM_OP_BAD(state, "scaled value encoded without open "
|
|
"variable state");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Applying the scale value must not overflow */
|
|
if (UINT32_MAX / state->var.scale < *value) {
|
|
SPROM_OP_BAD(state, "cannot represent %" PRIu32 " * %" PRIu32
|
|
"\n", *value, state->var.scale);
|
|
return (EINVAL);
|
|
}
|
|
|
|
*value = (*value) * state->var.scale;
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Read a SPROM_OP_DATA_* value from @p opcodes.
|
|
*
|
|
* @param state The SPROM opcode state.
|
|
* @param type The SROM_OP_DATA_* type to be read.
|
|
* @param opval On success, the 32bit data representation. If @p type is signed,
|
|
* the value will be appropriately sign extended and may be directly cast to
|
|
* int32_t.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If reading the value otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_read_opval32(struct sprom_opcode_state *state, uint8_t type,
|
|
uint32_t *opval)
|
|
{
|
|
const uint8_t *p;
|
|
int error;
|
|
|
|
p = state->input;
|
|
switch (type) {
|
|
case SPROM_OP_DATA_I8:
|
|
/* Convert to signed value first, then sign extend */
|
|
*opval = (int32_t)(int8_t)(*p);
|
|
p += 1;
|
|
break;
|
|
case SPROM_OP_DATA_U8:
|
|
*opval = *p;
|
|
p += 1;
|
|
break;
|
|
case SPROM_OP_DATA_U8_SCALED:
|
|
*opval = *p;
|
|
|
|
if ((error = sprom_opcode_apply_scale(state, opval)))
|
|
return (error);
|
|
|
|
p += 1;
|
|
break;
|
|
case SPROM_OP_DATA_U16:
|
|
*opval = le16dec(p);
|
|
p += 2;
|
|
break;
|
|
case SPROM_OP_DATA_U32:
|
|
*opval = le32dec(p);
|
|
p += 4;
|
|
break;
|
|
default:
|
|
SPROM_OP_BAD(state, "unsupported data type: %hhu\n", type);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Update read address */
|
|
state->input = p;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Return true if our layout revision is currently defined by the SPROM
|
|
* opcode state.
|
|
*
|
|
* This may be used to test whether the current opcode stream state applies
|
|
* to the layout that we are actually parsing.
|
|
*
|
|
* A given opcode stream may cover multiple layout revisions, switching
|
|
* between them prior to defining a set of variables.
|
|
*/
|
|
static inline bool
|
|
sprom_opcode_matches_layout_rev(struct sprom_opcode_state *state)
|
|
{
|
|
return (bit_test(state->revs, state->layout->rev));
|
|
}
|
|
|
|
/**
|
|
* When evaluating @p state and @p opcode, rewrite @p opcode and the current
|
|
* evaluation state, as required.
|
|
*
|
|
* If @p opcode is rewritten, it should be returned from
|
|
* sprom_opcode_step() instead of the opcode parsed from @p state's opcode
|
|
* stream.
|
|
*
|
|
* If @p opcode remains unmodified, then sprom_opcode_step() should proceed
|
|
* to standard evaluation.
|
|
*/
|
|
static int
|
|
sprom_opcode_rewrite_opcode(struct sprom_opcode_state *state, uint8_t *opcode)
|
|
{
|
|
uint8_t op;
|
|
int error;
|
|
|
|
op = SPROM_OPCODE_OP(*opcode);
|
|
switch (state->var_state) {
|
|
case SPROM_OPCODE_VAR_STATE_NONE:
|
|
/* No open variable definition */
|
|
return (0);
|
|
|
|
case SPROM_OPCODE_VAR_STATE_OPEN:
|
|
/* Open variable definition; check for implicit closure. */
|
|
|
|
/*
|
|
* If a variable definition contains no explicit bind
|
|
* instructions prior to closure, we must generate a DO_BIND
|
|
* instruction with count and skip values of 1.
|
|
*/
|
|
if (SPROM_OP_IS_VAR_END(op) &&
|
|
state->var.bind_total == 0)
|
|
{
|
|
uint8_t count, skip_in, skip_out;
|
|
bool skip_in_negative;
|
|
|
|
/* Create bind with skip_in/skip_out of 1, count of 1 */
|
|
count = 1;
|
|
skip_in = 1;
|
|
skip_out = 1;
|
|
skip_in_negative = false;
|
|
|
|
error = sprom_opcode_set_bind(state, count, skip_in,
|
|
skip_in_negative, skip_out);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Return DO_BIND */
|
|
*opcode = SPROM_OPCODE_DO_BIND |
|
|
(0 << SPROM_OP_BIND_SKIP_IN_SIGN) |
|
|
(1 << SPROM_OP_BIND_SKIP_IN_SHIFT) |
|
|
(1 << SPROM_OP_BIND_SKIP_OUT_SHIFT);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*
|
|
* If a variable is implicitly closed (e.g. by a new variable
|
|
* definition), we must generate a VAR_END instruction.
|
|
*/
|
|
if (SPROM_OP_IS_IMPLICIT_VAR_END(op)) {
|
|
/* Mark as complete */
|
|
if ((error = sprom_opcode_end_var(state)))
|
|
return (error);
|
|
|
|
/* Return VAR_END */
|
|
*opcode = SPROM_OPCODE_VAR_END;
|
|
return (0);
|
|
}
|
|
break;
|
|
|
|
|
|
case SPROM_OPCODE_VAR_STATE_DONE:
|
|
/* Previously completed variable definition. Discard variable
|
|
* state */
|
|
return (sprom_opcode_clear_var(state));
|
|
}
|
|
|
|
/* Nothing to do */
|
|
return (0);
|
|
}
|
|
|
|
/**
|
|
* Evaluate one opcode from @p state.
|
|
*
|
|
* @param state The opcode state to be evaluated.
|
|
* @param[out] opcode On success, the evaluated opcode
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT if EOF is reached
|
|
* @retval non-zero if evaluation otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_step(struct sprom_opcode_state *state, uint8_t *opcode)
|
|
{
|
|
int error;
|
|
|
|
while (*state->input != SPROM_OPCODE_EOF) {
|
|
uint32_t val;
|
|
uint8_t op, rewrite, immd;
|
|
|
|
/* Fetch opcode */
|
|
*opcode = *state->input;
|
|
op = SPROM_OPCODE_OP(*opcode);
|
|
immd = SPROM_OPCODE_IMM(*opcode);
|
|
|
|
/* Clear any existing bind state */
|
|
if ((error = sprom_opcode_flush_bind(state)))
|
|
return (error);
|
|
|
|
/* Insert local opcode based on current state? */
|
|
rewrite = *opcode;
|
|
if ((error = sprom_opcode_rewrite_opcode(state, &rewrite)))
|
|
return (error);
|
|
|
|
if (rewrite != *opcode) {
|
|
/* Provide rewritten opcode */
|
|
*opcode = rewrite;
|
|
|
|
/* We must keep evaluating until we hit a state
|
|
* applicable to the SPROM revision we're parsing */
|
|
if (!sprom_opcode_matches_layout_rev(state))
|
|
continue;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Advance input */
|
|
state->input++;
|
|
|
|
switch (op) {
|
|
case SPROM_OPCODE_VAR_IMM:
|
|
if ((error = sprom_opcode_set_var(state, immd)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_VAR_REL_IMM:
|
|
error = sprom_opcode_set_var(state, state->vid + immd);
|
|
if (error)
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_VAR:
|
|
error = sprom_opcode_read_opval32(state, immd, &val);
|
|
if (error)
|
|
return (error);
|
|
|
|
if ((error = sprom_opcode_set_var(state, val)))
|
|
return (error);
|
|
|
|
break;
|
|
|
|
case SPROM_OPCODE_VAR_END:
|
|
if ((error = sprom_opcode_end_var(state)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_NELEM:
|
|
immd = *state->input;
|
|
if ((error = sprom_opcode_set_nelem(state, immd)))
|
|
return (error);
|
|
|
|
state->input++;
|
|
break;
|
|
|
|
case SPROM_OPCODE_DO_BIND:
|
|
case SPROM_OPCODE_DO_BINDN: {
|
|
uint8_t count, skip_in, skip_out;
|
|
bool skip_in_negative;
|
|
|
|
/* Fetch skip arguments */
|
|
skip_in = (immd & SPROM_OP_BIND_SKIP_IN_MASK) >>
|
|
SPROM_OP_BIND_SKIP_IN_SHIFT;
|
|
|
|
skip_in_negative =
|
|
((immd & SPROM_OP_BIND_SKIP_IN_SIGN) != 0);
|
|
|
|
skip_out = (immd & SPROM_OP_BIND_SKIP_OUT_MASK) >>
|
|
SPROM_OP_BIND_SKIP_OUT_SHIFT;
|
|
|
|
/* Fetch count argument (if any) */
|
|
if (op == SPROM_OPCODE_DO_BINDN) {
|
|
/* Count is provided as trailing U8 */
|
|
count = *state->input;
|
|
state->input++;
|
|
} else {
|
|
count = 1;
|
|
}
|
|
|
|
/* Set BIND state */
|
|
error = sprom_opcode_set_bind(state, count, skip_in,
|
|
skip_in_negative, skip_out);
|
|
if (error)
|
|
return (error);
|
|
|
|
break;
|
|
}
|
|
case SPROM_OPCODE_DO_BINDN_IMM: {
|
|
uint8_t count, skip_in, skip_out;
|
|
bool skip_in_negative;
|
|
|
|
/* Implicit skip_in/skip_out of 1, count encoded as immd
|
|
* value */
|
|
count = immd;
|
|
skip_in = 1;
|
|
skip_out = 1;
|
|
skip_in_negative = false;
|
|
|
|
error = sprom_opcode_set_bind(state, count, skip_in,
|
|
skip_in_negative, skip_out);
|
|
if (error)
|
|
return (error);
|
|
break;
|
|
}
|
|
|
|
case SPROM_OPCODE_REV_IMM:
|
|
if ((error = sprom_opcode_set_revs(state, immd, immd)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_REV_RANGE: {
|
|
uint8_t range;
|
|
uint8_t rstart, rend;
|
|
|
|
/* Revision range is encoded in next byte, as
|
|
* { uint8_t start:4, uint8_t end:4 } */
|
|
range = *state->input;
|
|
rstart = (range & SPROM_OP_REV_START_MASK) >>
|
|
SPROM_OP_REV_START_SHIFT;
|
|
rend = (range & SPROM_OP_REV_END_MASK) >>
|
|
SPROM_OP_REV_END_SHIFT;
|
|
|
|
/* Update revision bitmask */
|
|
error = sprom_opcode_set_revs(state, rstart, rend);
|
|
if (error)
|
|
return (error);
|
|
|
|
/* Advance input */
|
|
state->input++;
|
|
break;
|
|
}
|
|
case SPROM_OPCODE_MASK_IMM:
|
|
if ((error = sprom_opcode_set_mask(state, immd)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_MASK:
|
|
error = sprom_opcode_read_opval32(state, immd, &val);
|
|
if (error)
|
|
return (error);
|
|
|
|
if ((error = sprom_opcode_set_mask(state, val)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_SHIFT_IMM:
|
|
if ((error = sprom_opcode_set_shift(state, immd * 2)))
|
|
return (error);
|
|
break;
|
|
|
|
case SPROM_OPCODE_SHIFT: {
|
|
int8_t shift;
|
|
|
|
if (immd == SPROM_OP_DATA_I8) {
|
|
shift = (int8_t)(*state->input);
|
|
} else if (immd == SPROM_OP_DATA_U8) {
|
|
val = *state->input;
|
|
if (val > INT8_MAX) {
|
|
SPROM_OP_BAD(state, "invalid shift "
|
|
"value: %#x\n", val);
|
|
}
|
|
|
|
shift = val;
|
|
} else {
|
|
SPROM_OP_BAD(state, "unsupported shift data "
|
|
"type: %#hhx\n", immd);
|
|
return (EINVAL);
|
|
}
|
|
|
|
if ((error = sprom_opcode_set_shift(state, shift)))
|
|
return (error);
|
|
|
|
state->input++;
|
|
break;
|
|
}
|
|
case SPROM_OPCODE_OFFSET_REL_IMM:
|
|
/* Fetch unscaled relative offset */
|
|
val = immd;
|
|
|
|
/* Apply scale */
|
|
if ((error = sprom_opcode_apply_scale(state, &val)))
|
|
return (error);
|
|
|
|
/* Adding val must not overflow our offset */
|
|
if (UINT32_MAX - state->offset < val) {
|
|
BHND_NV_LOG("offset out of range\n");
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* Adjust offset */
|
|
state->offset += val;
|
|
break;
|
|
case SPROM_OPCODE_OFFSET:
|
|
error = sprom_opcode_read_opval32(state, immd, &val);
|
|
if (error)
|
|
return (error);
|
|
|
|
state->offset = val;
|
|
break;
|
|
|
|
case SPROM_OPCODE_TYPE:
|
|
/* Type follows as U8 */
|
|
immd = *state->input;
|
|
state->input++;
|
|
|
|
/* fall through */
|
|
case SPROM_OPCODE_TYPE_IMM:
|
|
switch (immd) {
|
|
case BHND_NVRAM_TYPE_UINT8:
|
|
case BHND_NVRAM_TYPE_UINT16:
|
|
case BHND_NVRAM_TYPE_UINT32:
|
|
case BHND_NVRAM_TYPE_UINT64:
|
|
case BHND_NVRAM_TYPE_INT8:
|
|
case BHND_NVRAM_TYPE_INT16:
|
|
case BHND_NVRAM_TYPE_INT32:
|
|
case BHND_NVRAM_TYPE_INT64:
|
|
case BHND_NVRAM_TYPE_CHAR:
|
|
case BHND_NVRAM_TYPE_STRING:
|
|
error = sprom_opcode_set_type(state,
|
|
(bhnd_nvram_type)immd);
|
|
if (error)
|
|
return (error);
|
|
break;
|
|
default:
|
|
BHND_NV_LOG("unrecognized type %#hhx\n", immd);
|
|
return (EINVAL);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
BHND_NV_LOG("unrecognized opcode %#hhx\n", *opcode);
|
|
return (EINVAL);
|
|
}
|
|
|
|
/* We must keep evaluating until we hit a state applicable to
|
|
* the SPROM revision we're parsing */
|
|
if (sprom_opcode_matches_layout_rev(state))
|
|
return (0);
|
|
}
|
|
|
|
/* End of opcode stream */
|
|
return (ENOENT);
|
|
}
|
|
|
|
/**
|
|
* Reset SPROM opcode evaluation state, seek to the @p indexed position,
|
|
* and perform complete evaluation of the variable's opcodes.
|
|
*
|
|
* @param state The opcode state to be to be evaluated.
|
|
* @param indexed The indexed variable location.
|
|
*
|
|
* @retval 0 success
|
|
* @retval non-zero If evaluation fails, a regular unix error code will be
|
|
* returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_parse_var(struct sprom_opcode_state *state,
|
|
struct sprom_opcode_idx *indexed)
|
|
{
|
|
uint8_t opcode;
|
|
int error;
|
|
|
|
/* Seek to entry */
|
|
if ((error = sprom_opcode_state_seek(state, indexed)))
|
|
return (error);
|
|
|
|
/* Parse full variable definition */
|
|
while ((error = sprom_opcode_step(state, &opcode)) == 0) {
|
|
/* Iterate until VAR_END */
|
|
if (SPROM_OPCODE_OP(opcode) != SPROM_OPCODE_VAR_END)
|
|
continue;
|
|
|
|
BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE,
|
|
("incomplete variable definition"));
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* Error parsing definition */
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Evaluate @p state until the next variable definition is found.
|
|
*
|
|
* @param state The opcode state to be evaluated.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT if no additional variable definitions are available.
|
|
* @retval non-zero if evaluation otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_next_var(struct sprom_opcode_state *state)
|
|
{
|
|
uint8_t opcode;
|
|
int error;
|
|
|
|
/* Step until we hit a variable opcode */
|
|
while ((error = sprom_opcode_step(state, &opcode)) == 0) {
|
|
switch (SPROM_OPCODE_OP(opcode)) {
|
|
case SPROM_OPCODE_VAR:
|
|
case SPROM_OPCODE_VAR_IMM:
|
|
case SPROM_OPCODE_VAR_REL_IMM:
|
|
BHND_NV_ASSERT(
|
|
state->var_state == SPROM_OPCODE_VAR_STATE_OPEN,
|
|
("missing variable definition"));
|
|
|
|
return (0);
|
|
default:
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* Reached EOF, or evaluation failed */
|
|
return (error);
|
|
}
|
|
|
|
/**
|
|
* Evaluate @p state until the next binding for the current variable definition
|
|
* is found.
|
|
*
|
|
* @param state The opcode state to be evaluated.
|
|
*
|
|
* @retval 0 success
|
|
* @retval ENOENT if no additional binding opcodes are found prior to reaching
|
|
* a new variable definition, or the end of @p state's binding opcodes.
|
|
* @retval non-zero if evaluation otherwise fails, a regular unix error
|
|
* code will be returned.
|
|
*/
|
|
static int
|
|
sprom_opcode_next_binding(struct sprom_opcode_state *state)
|
|
{
|
|
uint8_t opcode;
|
|
int error;
|
|
|
|
if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN)
|
|
return (EINVAL);
|
|
|
|
/* Step until we hit a bind opcode, or a new variable */
|
|
while ((error = sprom_opcode_step(state, &opcode)) == 0) {
|
|
switch (SPROM_OPCODE_OP(opcode)) {
|
|
case SPROM_OPCODE_DO_BIND:
|
|
case SPROM_OPCODE_DO_BINDN:
|
|
case SPROM_OPCODE_DO_BINDN_IMM:
|
|
/* Found next bind */
|
|
BHND_NV_ASSERT(
|
|
state->var_state == SPROM_OPCODE_VAR_STATE_OPEN,
|
|
("missing variable definition"));
|
|
BHND_NV_ASSERT(state->var.have_bind, ("missing bind"));
|
|
|
|
return (0);
|
|
|
|
case SPROM_OPCODE_VAR_END:
|
|
/* No further binding opcodes */
|
|
BHND_NV_ASSERT(
|
|
state->var_state == SPROM_OPCODE_VAR_STATE_DONE,
|
|
("variable definition still available"));
|
|
return (ENOENT);
|
|
}
|
|
}
|
|
|
|
/* Not found, or evaluation failed */
|
|
return (error);
|
|
}
|