Path: blob/main/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c
39536 views
/*-1* Copyright (c) 2015-2016 Landon Fuller <[email protected]>2* All rights reserved.3*4* Redistribution and use in source and binary forms, with or without5* modification, are permitted provided that the following conditions6* are met:7* 1. Redistributions of source code must retain the above copyright8* notice, this list of conditions and the following disclaimer,9* without modification.10* 2. Redistributions in binary form must reproduce at minimum a disclaimer11* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any12* redistribution must be conditioned upon including a substantially13* similar Disclaimer requirement for further binary redistribution.14*15* NO WARRANTY16* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY19* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL20* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,21* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF22* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS23* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER24* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)25* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF26* THE POSSIBILITY OF SUCH DAMAGES.27*/2829#include <sys/param.h>30#include <sys/endian.h>3132#ifdef _KERNEL33#include <sys/systm.h>34#include <machine/_inttypes.h>35#else /* !_KERNEL */36#include <errno.h>37#include <inttypes.h>38#include <stdint.h>39#include <string.h>40#endif /* _KERNEL */4142#include "bhnd_nvram_private.h"43#include "bhnd_nvram_data_spromvar.h"4445static int bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs);46static int bhnd_nvram_opcode_idx_vid_compare(const void *key,47const void *rhs);4849static int bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state);5051static int bhnd_sprom_opcode_set_type(bhnd_sprom_opcode_state *state,52bhnd_nvram_type type);5354static int bhnd_sprom_opcode_set_var(bhnd_sprom_opcode_state *state,55size_t vid);56static int bhnd_sprom_opcode_clear_var(bhnd_sprom_opcode_state *state);5758static int bhnd_sprom_opcode_flush_bind(bhnd_sprom_opcode_state *state);5960static int bhnd_sprom_opcode_read_opval32(bhnd_sprom_opcode_state *state,61uint8_t type, uint32_t *opval);6263static int bhnd_sprom_opcode_step(bhnd_sprom_opcode_state *state,64uint8_t *opcode);6566#define SPROM_OP_BAD(_state, _fmt, ...) \67BHND_NV_LOG("bad encoding at %td: " _fmt, \68(_state)->input - (_state)->layout->bindings, ##__VA_ARGS__)6970/**71* Initialize SPROM opcode evaluation state.72*73* @param state The opcode state to be initialized.74* @param layout The SPROM layout to be parsed by this instance.75*76*77* @retval 0 success78* @retval non-zero If initialization fails, a regular unix error code will be79* returned.80*/81int82bhnd_sprom_opcode_init(bhnd_sprom_opcode_state *state,83const struct bhnd_sprom_layout *layout)84{85bhnd_sprom_opcode_idx_entry *idx;86size_t num_vars, num_idx;87int error;8889idx = NULL;9091state->layout = layout;92state->idx = NULL;93state->num_idx = 0;9495/* Initialize interpretation state */96if ((error = bhnd_sprom_opcode_reset(state)))97return (error);9899/* Allocate and populate our opcode index */100num_idx = state->layout->num_vars;101idx = bhnd_nv_calloc(num_idx, sizeof(*idx));102if (idx == NULL)103return (ENOMEM);104105for (num_vars = 0; num_vars < num_idx; num_vars++) {106/* Seek to next entry */107if ((error = bhnd_sprom_opcode_next_var(state))) {108SPROM_OP_BAD(state, "error reading expected variable "109"entry: %d\n", error);110bhnd_nv_free(idx);111return (error);112}113114/* Record entry state in our index */115error = bhnd_sprom_opcode_init_entry(state, &idx[num_vars]);116if (error) {117SPROM_OP_BAD(state, "error initializing index for "118"entry: %d\n", error);119bhnd_nv_free(idx);120return (error);121}122}123124/* Should have reached end of binding table; next read must return125* ENOENT */126if ((error = bhnd_sprom_opcode_next_var(state)) != ENOENT) {127BHND_NV_LOG("expected EOF parsing binding table: %d\n", error);128bhnd_nv_free(idx);129return (ENXIO);130}131132/* Reset interpretation state */133if ((error = bhnd_sprom_opcode_reset(state))) {134bhnd_nv_free(idx);135return (error);136}137138/* Make index available to opcode state evaluation */139qsort(idx, num_idx, sizeof(idx[0]), bhnd_sprom_opcode_sort_idx);140141state->idx = idx;142state->num_idx = num_idx;143144return (0);145}146147/**148* Reset SPROM opcode evaluation state; future evaluation will be performed149* starting at the first opcode.150*151* @param state The opcode state to be reset.152*153* @retval 0 success154* @retval non-zero If reset fails, a regular unix error code will be returned.155*/156static int157bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state)158{159memset(&state->var, 0, sizeof(state->var));160161state->input = state->layout->bindings;162state->offset = 0;163state->vid = 0;164state->var_state = SPROM_OPCODE_VAR_STATE_NONE;165bit_set(state->revs, state->layout->rev);166167return (0);168}169170/**171* Free any resources associated with @p state.172*173* @param state An opcode state previously successfully initialized with174* bhnd_sprom_opcode_init().175*/176void177bhnd_sprom_opcode_fini(bhnd_sprom_opcode_state *state)178{179bhnd_nv_free(state->idx);180}181182/**183* Sort function used to prepare our index for querying; sorts184* bhnd_sprom_opcode_idx_entry values by variable ID, ascending.185*/186static int187bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs)188{189const bhnd_sprom_opcode_idx_entry *l, *r;190191l = lhs;192r = rhs;193194if (l->vid < r->vid)195return (-1);196if (l->vid > r->vid)197return (1);198return (0);199}200201/**202* Binary search comparison function used by bhnd_sprom_opcode_index_find();203* searches bhnd_sprom_opcode_idx_entry values by variable ID, ascending.204*/205static int206bhnd_nvram_opcode_idx_vid_compare(const void *key, const void *rhs)207{208const bhnd_sprom_opcode_idx_entry *entry;209size_t vid;210211vid = *(const size_t *)key;212entry = rhs;213214if (vid < entry->vid)215return (-1);216if (vid > entry->vid)217return (1);218219return (0);220}221222/**223* Locate an index entry for the variable with @p name, or NULL if not found.224*225* @param state The opcode state to be queried.226* @param name The name to search for.227*228* @retval non-NULL If @p name is found, its index entry value will be229* returned.230* @retval NULL If @p name is not found.231*/232bhnd_sprom_opcode_idx_entry *233bhnd_sprom_opcode_index_find(bhnd_sprom_opcode_state *state, const char *name)234{235const struct bhnd_nvram_vardefn *var;236size_t vid;237238/* Determine the variable ID for the given name */239if ((var = bhnd_nvram_find_vardefn(name)) == NULL)240return (NULL);241242vid = bhnd_nvram_get_vardefn_id(var);243244/* Search our index for the variable ID */245return (bsearch(&vid, state->idx, state->num_idx, sizeof(state->idx[0]),246bhnd_nvram_opcode_idx_vid_compare));247}248249/**250* Iterate over all index entries in @p state.251*252* @param state The opcode state to be iterated.253* @param[in,out] prev An entry previously returned by254* bhnd_sprom_opcode_index_next(), or a NULL value255* to begin iteration.256*257* @return Returns the next index entry name, or NULL if all entries have258* been iterated.259*/260bhnd_sprom_opcode_idx_entry *261bhnd_sprom_opcode_index_next(bhnd_sprom_opcode_state *state,262bhnd_sprom_opcode_idx_entry *prev)263{264size_t idxpos;265266/* Get next index position */267if (prev == NULL) {268idxpos = 0;269} else {270/* Determine current position */271idxpos = (size_t)(prev - state->idx);272BHND_NV_ASSERT(idxpos < state->num_idx,273("invalid index %zu", idxpos));274275/* Advance to next entry */276idxpos++;277}278279/* Check for EOF */280if (idxpos == state->num_idx)281return (NULL);282283return (&state->idx[idxpos]);284}285286/**287* Initialize @p entry with the current variable's opcode state.288*289* @param state The opcode state to be saved.290* @param[out] entry The opcode index entry to be initialized from @p state.291*292* @retval 0 success293* @retval ENXIO if @p state cannot be serialized as an index entry.294*/295int296bhnd_sprom_opcode_init_entry(bhnd_sprom_opcode_state *state,297bhnd_sprom_opcode_idx_entry *entry)298{299size_t opcodes;300301/* We limit the SPROM index representations to the minimal type widths302* capable of covering all known layouts */303304/* Save SPROM image offset */305if (state->offset > UINT16_MAX) {306SPROM_OP_BAD(state, "cannot index large offset %u\n",307state->offset);308return (ENXIO);309}310311entry->offset = state->offset;312313/* Save current variable ID */314if (state->vid > UINT16_MAX) {315SPROM_OP_BAD(state, "cannot index large vid %zu\n",316state->vid);317return (ENXIO);318}319entry->vid = state->vid;320321/* Save opcode position */322opcodes = (state->input - state->layout->bindings);323if (opcodes > UINT16_MAX) {324SPROM_OP_BAD(state, "cannot index large opcode offset "325"%zu\n", opcodes);326return (ENXIO);327}328entry->opcodes = opcodes;329330return (0);331}332333/**334* Reset SPROM opcode evaluation state and seek to the @p entry's position.335*336* @param state The opcode state to be reset.337* @param entry The indexed entry to which we'll seek the opcode state.338*/339int340bhnd_sprom_opcode_seek(bhnd_sprom_opcode_state *state,341bhnd_sprom_opcode_idx_entry *entry)342{343int error;344345BHND_NV_ASSERT(entry->opcodes < state->layout->bindings_size,346("index entry references invalid opcode position"));347348/* Reset state */349if ((error = bhnd_sprom_opcode_reset(state)))350return (error);351352/* Seek to the indexed sprom opcode offset */353state->input = state->layout->bindings + entry->opcodes;354355/* Restore the indexed sprom data offset and VID */356state->offset = entry->offset;357358/* Restore the indexed sprom variable ID */359if ((error = bhnd_sprom_opcode_set_var(state, entry->vid)))360return (error);361362return (0);363}364365/**366* Set the current revision range for @p state. This also resets367* variable state.368*369* @param state The opcode state to update370* @param start The first revision in the range.371* @param end The last revision in the range.372*373* @retval 0 success374* @retval non-zero If updating @p state fails, a regular unix error code will375* be returned.376*/377static inline int378bhnd_sprom_opcode_set_revs(bhnd_sprom_opcode_state *state, uint8_t start,379uint8_t end)380{381int error;382383/* Validate the revision range */384if (start > SPROM_OP_REV_MAX ||385end > SPROM_OP_REV_MAX ||386end < start)387{388SPROM_OP_BAD(state, "invalid revision range: %hhu-%hhu\n",389start, end);390return (EINVAL);391}392393/* Clear variable state */394if ((error = bhnd_sprom_opcode_clear_var(state)))395return (error);396397/* Reset revision mask */398memset(state->revs, 0x0, sizeof(state->revs));399bit_nset(state->revs, start, end);400401return (0);402}403404/**405* Set the current variable's value mask for @p state.406*407* @param state The opcode state to update408* @param mask The mask to be set409*410* @retval 0 success411* @retval non-zero If updating @p state fails, a regular unix error code will412* be returned.413*/414static inline int415bhnd_sprom_opcode_set_mask(bhnd_sprom_opcode_state *state, uint32_t mask)416{417if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {418SPROM_OP_BAD(state, "no open variable definition\n");419return (EINVAL);420}421422state->var.mask = mask;423return (0);424}425426/**427* Set the current variable's value shift for @p state.428*429* @param state The opcode state to update430* @param shift The shift to be set431*432* @retval 0 success433* @retval non-zero If updating @p state fails, a regular unix error code will434* be returned.435*/436static inline int437bhnd_sprom_opcode_set_shift(bhnd_sprom_opcode_state *state, int8_t shift)438{439if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {440SPROM_OP_BAD(state, "no open variable definition\n");441return (EINVAL);442}443444state->var.shift = shift;445return (0);446}447448/**449* Register a new BIND/BINDN operation with @p state.450*451* @param state The opcode state to update.452* @param count The number of elements to be bound.453* @param skip_in The number of input elements to skip after each bind.454* @param skip_in_negative If true, the input skip should be subtracted from455* the current offset after each bind. If false, the input skip should be456* added.457* @param skip_out The number of output elements to skip after each bind.458*459* @retval 0 success460* @retval EINVAL if a variable definition is not open.461* @retval EINVAL if @p skip_in and @p count would trigger an overflow or462* underflow when applied to the current input offset.463* @retval ERANGE if @p skip_in would overflow uint32_t when multiplied by464* @p count and the scale value.465* @retval ERANGE if @p skip_out would overflow uint32_t when multiplied by466* @p count and the scale value.467* @retval non-zero If updating @p state otherwise fails, a regular unix error468* code will be returned.469*/470static inline int471bhnd_sprom_opcode_set_bind(bhnd_sprom_opcode_state *state, uint8_t count,472uint8_t skip_in, bool skip_in_negative, uint8_t skip_out)473{474uint32_t iskip_total;475uint32_t iskip_scaled;476int error;477478/* Must have an open variable */479if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {480SPROM_OP_BAD(state, "no open variable definition\n");481SPROM_OP_BAD(state, "BIND outside of variable definition\n");482return (EINVAL);483}484485/* Cannot overwite an existing bind definition */486if (state->var.have_bind) {487SPROM_OP_BAD(state, "BIND overwrites existing definition\n");488return (EINVAL);489}490491/* Must have a count of at least 1 */492if (count == 0) {493SPROM_OP_BAD(state, "BIND with zero count\n");494return (EINVAL);495}496497/* Scale skip_in by the current type width */498iskip_scaled = skip_in;499if ((error = bhnd_sprom_opcode_apply_scale(state, &iskip_scaled)))500return (error);501502/* Calculate total input bytes skipped: iskip_scaled * count) */503if (iskip_scaled > 0 && UINT32_MAX / iskip_scaled < count) {504SPROM_OP_BAD(state, "skip_in %hhu would overflow", skip_in);505return (EINVAL);506}507508iskip_total = iskip_scaled * count;509510/* Verify that the skip_in value won't under/overflow the current511* input offset. */512if (skip_in_negative) {513if (iskip_total > state->offset) {514SPROM_OP_BAD(state, "skip_in %hhu would underflow "515"offset %u\n", skip_in, state->offset);516return (EINVAL);517}518} else {519if (UINT32_MAX - iskip_total < state->offset) {520SPROM_OP_BAD(state, "skip_in %hhu would overflow "521"offset %u\n", skip_in, state->offset);522return (EINVAL);523}524}525526/* Set the actual count and skip values */527state->var.have_bind = true;528state->var.bind.count = count;529state->var.bind.skip_in = skip_in;530state->var.bind.skip_out = skip_out;531532state->var.bind.skip_in_negative = skip_in_negative;533534/* Update total bind count for the current variable */535state->var.bind_total++;536537return (0);538}539540/**541* Apply and clear the current opcode bind state, if any.542*543* @param state The opcode state to update.544*545* @retval 0 success546* @retval non-zero If updating @p state otherwise fails, a regular unix error547* code will be returned.548*/549static int550bhnd_sprom_opcode_flush_bind(bhnd_sprom_opcode_state *state)551{552int error;553uint32_t skip;554555/* Nothing to do? */556if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN ||557!state->var.have_bind)558return (0);559560/* Apply SPROM offset adjustment */561if (state->var.bind.count > 0) {562skip = state->var.bind.skip_in * state->var.bind.count;563if ((error = bhnd_sprom_opcode_apply_scale(state, &skip)))564return (error);565566if (state->var.bind.skip_in_negative) {567state->offset -= skip;568} else {569state->offset += skip;570}571}572573/* Clear bind state */574memset(&state->var.bind, 0, sizeof(state->var.bind));575state->var.have_bind = false;576577return (0);578}579580/**581* Set the current type to @p type, and reset type-specific582* stream state.583*584* @param state The opcode state to update.585* @param type The new type.586*587* @retval 0 success588* @retval EINVAL if @p vid is not a valid variable ID.589*/590static int591bhnd_sprom_opcode_set_type(bhnd_sprom_opcode_state *state, bhnd_nvram_type type)592{593bhnd_nvram_type base_type;594size_t width;595uint32_t mask;596597/* Must have an open variable definition */598if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {599SPROM_OP_BAD(state, "type set outside variable definition\n");600return (EINVAL);601}602603/* Fetch type width for use as our scale value */604width = bhnd_nvram_type_width(type);605if (width == 0) {606SPROM_OP_BAD(state, "unsupported variable-width type: %d\n",607type);608return (EINVAL);609} else if (width > UINT32_MAX) {610SPROM_OP_BAD(state, "invalid type width %zu for type: %d\n",611width, type);612return (EINVAL);613}614615/* Determine default mask value for the element type */616base_type = bhnd_nvram_base_type(type);617switch (base_type) {618case BHND_NVRAM_TYPE_UINT8:619case BHND_NVRAM_TYPE_INT8:620case BHND_NVRAM_TYPE_CHAR:621mask = UINT8_MAX;622break;623case BHND_NVRAM_TYPE_UINT16:624case BHND_NVRAM_TYPE_INT16:625mask = UINT16_MAX;626break;627case BHND_NVRAM_TYPE_UINT32:628case BHND_NVRAM_TYPE_INT32:629mask = UINT32_MAX;630break;631case BHND_NVRAM_TYPE_STRING:632/* fallthrough (unused by SPROM) */633default:634SPROM_OP_BAD(state, "unsupported type: %d\n", type);635return (EINVAL);636}637638/* Update state */639state->var.base_type = base_type;640state->var.mask = mask;641state->var.scale = (uint32_t)width;642643return (0);644}645646/**647* Clear current variable state, if any.648*649* @param state The opcode state to update.650*/651static int652bhnd_sprom_opcode_clear_var(bhnd_sprom_opcode_state *state)653{654if (state->var_state == SPROM_OPCODE_VAR_STATE_NONE)655return (0);656657BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE,658("incomplete variable definition"));659BHND_NV_ASSERT(!state->var.have_bind, ("stale bind state"));660661memset(&state->var, 0, sizeof(state->var));662state->var_state = SPROM_OPCODE_VAR_STATE_NONE;663664return (0);665}666667/**668* Set the current variable's array element count to @p nelem.669*670* @param state The opcode state to update.671* @param nelem The new array length.672*673* @retval 0 success674* @retval EINVAL if no open variable definition exists.675* @retval EINVAL if @p nelem is zero.676* @retval ENXIO if @p nelem is greater than one, and the current variable does677* not have an array type.678* @retval ENXIO if @p nelem exceeds the array length of the NVRAM variable679* definition.680*/681static int682bhnd_sprom_opcode_set_nelem(bhnd_sprom_opcode_state *state, uint8_t nelem)683{684const struct bhnd_nvram_vardefn *var;685686/* Must have a defined variable */687if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {688SPROM_OP_BAD(state, "array length set without open variable "689"state");690return (EINVAL);691}692693/* Locate the actual variable definition */694if ((var = bhnd_nvram_get_vardefn(state->vid)) == NULL) {695SPROM_OP_BAD(state, "unknown variable ID: %zu\n", state->vid);696return (EINVAL);697}698699/* Must be greater than zero */700if (nelem == 0) {701SPROM_OP_BAD(state, "invalid nelem: %hhu\n", nelem);702return (EINVAL);703}704705/* If the variable is not an array-typed value, the array length706* must be 1 */707if (!bhnd_nvram_is_array_type(var->type) && nelem != 1) {708SPROM_OP_BAD(state, "nelem %hhu on non-array %zu\n", nelem,709state->vid);710return (ENXIO);711}712713/* Cannot exceed the variable's defined array length */714if (nelem > var->nelem) {715SPROM_OP_BAD(state, "nelem %hhu exceeds %zu length %hhu\n",716nelem, state->vid, var->nelem);717return (ENXIO);718}719720/* Valid length; update state */721state->var.nelem = nelem;722723return (0);724}725726/**727* Set the current variable ID to @p vid, and reset variable-specific728* stream state.729*730* @param state The opcode state to update.731* @param vid The new variable ID.732*733* @retval 0 success734* @retval EINVAL if @p vid is not a valid variable ID.735*/736static int737bhnd_sprom_opcode_set_var(bhnd_sprom_opcode_state *state, size_t vid)738{739const struct bhnd_nvram_vardefn *var;740int error;741742BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_NONE,743("overwrite of open variable definition"));744745/* Locate the variable definition */746if ((var = bhnd_nvram_get_vardefn(vid)) == NULL) {747SPROM_OP_BAD(state, "unknown variable ID: %zu\n", vid);748return (EINVAL);749}750751/* Update vid and var state */752state->vid = vid;753state->var_state = SPROM_OPCODE_VAR_STATE_OPEN;754755/* Initialize default variable record values */756memset(&state->var, 0x0, sizeof(state->var));757758/* Set initial base type */759if ((error = bhnd_sprom_opcode_set_type(state, var->type)))760return (error);761762/* Set default array length */763if ((error = bhnd_sprom_opcode_set_nelem(state, var->nelem)))764return (error);765766return (0);767}768769/**770* Mark the currently open variable definition as complete.771*772* @param state The opcode state to update.773*774* @retval 0 success775* @retval EINVAL if no incomplete open variable definition exists.776*/777static int778bhnd_sprom_opcode_end_var(bhnd_sprom_opcode_state *state)779{780if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {781SPROM_OP_BAD(state, "no open variable definition\n");782return (EINVAL);783}784785state->var_state = SPROM_OPCODE_VAR_STATE_DONE;786return (0);787}788789/**790* Apply the current scale to @p value.791*792* @param state The SPROM opcode state.793* @param[in,out] value The value to scale794*795* @retval 0 success796* @retval EINVAL if no open variable definition exists.797* @retval EINVAL if applying the current scale would overflow.798*/799int800bhnd_sprom_opcode_apply_scale(bhnd_sprom_opcode_state *state, uint32_t *value)801{802/* Must have a defined variable (and thus, scale) */803if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN) {804SPROM_OP_BAD(state, "scaled value encoded without open "805"variable state");806return (EINVAL);807}808809/* Applying the scale value must not overflow */810if (UINT32_MAX / state->var.scale < *value) {811SPROM_OP_BAD(state, "cannot represent %" PRIu32 " * %" PRIu32812"\n", *value, state->var.scale);813return (EINVAL);814}815816*value = (*value) * state->var.scale;817return (0);818}819820/**821* Read a SPROM_OP_DATA_* value from @p opcodes.822*823* @param state The SPROM opcode state.824* @param type The SROM_OP_DATA_* type to be read.825* @param opval On success, the 32bit data representation. If @p type is signed,826* the value will be appropriately sign extended and may be directly cast to827* int32_t.828*829* @retval 0 success830* @retval non-zero If reading the value otherwise fails, a regular unix error831* code will be returned.832*/833static int834bhnd_sprom_opcode_read_opval32(bhnd_sprom_opcode_state *state, uint8_t type,835uint32_t *opval)836{837const uint8_t *p;838int error;839840p = state->input;841switch (type) {842case SPROM_OP_DATA_I8:843/* Convert to signed value first, then sign extend */844*opval = (int32_t)(int8_t)(*p);845p += 1;846break;847case SPROM_OP_DATA_U8:848*opval = *p;849p += 1;850break;851case SPROM_OP_DATA_U8_SCALED:852*opval = *p;853854if ((error = bhnd_sprom_opcode_apply_scale(state, opval)))855return (error);856857p += 1;858break;859case SPROM_OP_DATA_U16:860*opval = le16dec(p);861p += 2;862break;863case SPROM_OP_DATA_U32:864*opval = le32dec(p);865p += 4;866break;867default:868SPROM_OP_BAD(state, "unsupported data type: %hhu\n", type);869return (EINVAL);870}871872/* Update read address */873state->input = p;874875return (0);876}877878/**879* Return true if our layout revision is currently defined by the SPROM880* opcode state.881*882* This may be used to test whether the current opcode stream state applies883* to the layout that we are actually parsing.884*885* A given opcode stream may cover multiple layout revisions, switching886* between them prior to defining a set of variables.887*/888static inline bool889bhnd_sprom_opcode_matches_layout_rev(bhnd_sprom_opcode_state *state)890{891return (bit_test(state->revs, state->layout->rev));892}893894/**895* When evaluating @p state and @p opcode, rewrite @p opcode based on the896* current evaluation state.897*898* This allows the insertion of implicit opcodes into interpretation of the899* opcode stream.900*901* If @p opcode is rewritten, it should be returned from902* bhnd_sprom_opcode_step() instead of the opcode parsed from @p state's opcode903* stream.904*905* If @p opcode remains unmodified, then bhnd_sprom_opcode_step() should906* proceed to standard evaluation.907*/908static int909bhnd_sprom_opcode_rewrite_opcode(bhnd_sprom_opcode_state *state,910uint8_t *opcode)911{912uint8_t op;913int error;914915op = SPROM_OPCODE_OP(*opcode);916switch (state->var_state) {917case SPROM_OPCODE_VAR_STATE_NONE:918/* No open variable definition */919return (0);920921case SPROM_OPCODE_VAR_STATE_OPEN:922/* Open variable definition; check for implicit closure. */923924/*925* If a variable definition contains no explicit bind926* instructions prior to closure, we must generate a DO_BIND927* instruction with count and skip values of 1.928*/929if (SPROM_OP_IS_VAR_END(op) &&930state->var.bind_total == 0)931{932uint8_t count, skip_in, skip_out;933bool skip_in_negative;934935/* Create bind with skip_in/skip_out of 1, count of 1 */936count = 1;937skip_in = 1;938skip_out = 1;939skip_in_negative = false;940941error = bhnd_sprom_opcode_set_bind(state, count,942skip_in, skip_in_negative, skip_out);943if (error)944return (error);945946/* Return DO_BIND */947*opcode = SPROM_OPCODE_DO_BIND |948(0 << SPROM_OP_BIND_SKIP_IN_SIGN) |949(1 << SPROM_OP_BIND_SKIP_IN_SHIFT) |950(1 << SPROM_OP_BIND_SKIP_OUT_SHIFT);951952return (0);953}954955/*956* If a variable is implicitly closed (e.g. by a new variable957* definition), we must generate a VAR_END instruction.958*/959if (SPROM_OP_IS_IMPLICIT_VAR_END(op)) {960/* Mark as complete */961if ((error = bhnd_sprom_opcode_end_var(state)))962return (error);963964/* Return VAR_END */965*opcode = SPROM_OPCODE_VAR_END;966return (0);967}968break;969970case SPROM_OPCODE_VAR_STATE_DONE:971/* Previously completed variable definition. Discard variable972* state */973return (bhnd_sprom_opcode_clear_var(state));974}975976/* Nothing to do */977return (0);978}979980/**981* Evaluate one opcode from @p state.982*983* @param state The opcode state to be evaluated.984* @param[out] opcode On success, the evaluated opcode985*986* @retval 0 success987* @retval ENOENT if EOF is reached988* @retval non-zero if evaluation otherwise fails, a regular unix error989* code will be returned.990*/991static int992bhnd_sprom_opcode_step(bhnd_sprom_opcode_state *state, uint8_t *opcode)993{994int error;995996while (*state->input != SPROM_OPCODE_EOF) {997uint32_t val;998uint8_t op, rewrite, immd;9991000/* Fetch opcode */1001*opcode = *state->input;1002op = SPROM_OPCODE_OP(*opcode);1003immd = SPROM_OPCODE_IMM(*opcode);10041005/* Clear any existing bind state */1006if ((error = bhnd_sprom_opcode_flush_bind(state)))1007return (error);10081009/* Insert local opcode based on current state? */1010rewrite = *opcode;1011if ((error = bhnd_sprom_opcode_rewrite_opcode(state, &rewrite)))1012return (error);10131014if (rewrite != *opcode) {1015/* Provide rewritten opcode */1016*opcode = rewrite;10171018/* We must keep evaluating until we hit a state1019* applicable to the SPROM revision we're parsing */1020if (!bhnd_sprom_opcode_matches_layout_rev(state))1021continue;10221023return (0);1024}10251026/* Advance input */1027state->input++;10281029switch (op) {1030case SPROM_OPCODE_VAR_IMM:1031if ((error = bhnd_sprom_opcode_set_var(state, immd)))1032return (error);1033break;10341035case SPROM_OPCODE_VAR_REL_IMM:1036error = bhnd_sprom_opcode_set_var(state,1037state->vid + immd);1038if (error)1039return (error);1040break;10411042case SPROM_OPCODE_VAR:1043error = bhnd_sprom_opcode_read_opval32(state, immd,1044&val);1045if (error)1046return (error);10471048if ((error = bhnd_sprom_opcode_set_var(state, val)))1049return (error);10501051break;10521053case SPROM_OPCODE_VAR_END:1054if ((error = bhnd_sprom_opcode_end_var(state)))1055return (error);1056break;10571058case SPROM_OPCODE_NELEM:1059immd = *state->input;1060if ((error = bhnd_sprom_opcode_set_nelem(state, immd)))1061return (error);10621063state->input++;1064break;10651066case SPROM_OPCODE_DO_BIND:1067case SPROM_OPCODE_DO_BINDN: {1068uint8_t count, skip_in, skip_out;1069bool skip_in_negative;10701071/* Fetch skip arguments */1072skip_in = (immd & SPROM_OP_BIND_SKIP_IN_MASK) >>1073SPROM_OP_BIND_SKIP_IN_SHIFT;10741075skip_in_negative =1076((immd & SPROM_OP_BIND_SKIP_IN_SIGN) != 0);10771078skip_out = (immd & SPROM_OP_BIND_SKIP_OUT_MASK) >>1079SPROM_OP_BIND_SKIP_OUT_SHIFT;10801081/* Fetch count argument (if any) */1082if (op == SPROM_OPCODE_DO_BINDN) {1083/* Count is provided as trailing U8 */1084count = *state->input;1085state->input++;1086} else {1087count = 1;1088}10891090/* Set BIND state */1091error = bhnd_sprom_opcode_set_bind(state, count,1092skip_in, skip_in_negative, skip_out);1093if (error)1094return (error);10951096break;1097}1098case SPROM_OPCODE_DO_BINDN_IMM: {1099uint8_t count, skip_in, skip_out;1100bool skip_in_negative;11011102/* Implicit skip_in/skip_out of 1, count encoded as immd1103* value */1104count = immd;1105skip_in = 1;1106skip_out = 1;1107skip_in_negative = false;11081109error = bhnd_sprom_opcode_set_bind(state, count,1110skip_in, skip_in_negative, skip_out);1111if (error)1112return (error);1113break;1114}11151116case SPROM_OPCODE_REV_IMM:1117error = bhnd_sprom_opcode_set_revs(state, immd, immd);1118if (error)1119return (error);1120break;11211122case SPROM_OPCODE_REV_RANGE: {1123uint8_t range;1124uint8_t rstart, rend;11251126/* Revision range is encoded in next byte, as1127* { uint8_t start:4, uint8_t end:4 } */1128range = *state->input;1129rstart = (range & SPROM_OP_REV_START_MASK) >>1130SPROM_OP_REV_START_SHIFT;1131rend = (range & SPROM_OP_REV_END_MASK) >>1132SPROM_OP_REV_END_SHIFT;11331134/* Update revision bitmask */1135error = bhnd_sprom_opcode_set_revs(state, rstart, rend);1136if (error)1137return (error);11381139/* Advance input */1140state->input++;1141break;1142}1143case SPROM_OPCODE_MASK_IMM:1144if ((error = bhnd_sprom_opcode_set_mask(state, immd)))1145return (error);1146break;11471148case SPROM_OPCODE_MASK:1149error = bhnd_sprom_opcode_read_opval32(state, immd,1150&val);1151if (error)1152return (error);11531154if ((error = bhnd_sprom_opcode_set_mask(state, val)))1155return (error);1156break;11571158case SPROM_OPCODE_SHIFT_IMM:1159error = bhnd_sprom_opcode_set_shift(state, immd * 2);1160if (error)1161return (error);1162break;11631164case SPROM_OPCODE_SHIFT: {1165int8_t shift;11661167if (immd == SPROM_OP_DATA_I8) {1168shift = (int8_t)(*state->input);1169} else if (immd == SPROM_OP_DATA_U8) {1170val = *state->input;1171if (val > INT8_MAX) {1172SPROM_OP_BAD(state, "invalid shift "1173"value: %#x\n", val);1174}11751176shift = val;1177} else {1178SPROM_OP_BAD(state, "unsupported shift data "1179"type: %#hhx\n", immd);1180return (EINVAL);1181}11821183if ((error = bhnd_sprom_opcode_set_shift(state, shift)))1184return (error);11851186state->input++;1187break;1188}1189case SPROM_OPCODE_OFFSET_REL_IMM:1190/* Fetch unscaled relative offset */1191val = immd;11921193/* Apply scale */1194error = bhnd_sprom_opcode_apply_scale(state, &val);1195if (error)1196return (error);11971198/* Adding val must not overflow our offset */1199if (UINT32_MAX - state->offset < val) {1200BHND_NV_LOG("offset out of range\n");1201return (EINVAL);1202}12031204/* Adjust offset */1205state->offset += val;1206break;1207case SPROM_OPCODE_OFFSET:1208error = bhnd_sprom_opcode_read_opval32(state, immd,1209&val);1210if (error)1211return (error);12121213state->offset = val;1214break;12151216case SPROM_OPCODE_TYPE:1217/* Type follows as U8 */1218immd = *state->input;1219state->input++;12201221/* fall through */1222case SPROM_OPCODE_TYPE_IMM:1223switch (immd) {1224case BHND_NVRAM_TYPE_UINT8:1225case BHND_NVRAM_TYPE_UINT16:1226case BHND_NVRAM_TYPE_UINT32:1227case BHND_NVRAM_TYPE_UINT64:1228case BHND_NVRAM_TYPE_INT8:1229case BHND_NVRAM_TYPE_INT16:1230case BHND_NVRAM_TYPE_INT32:1231case BHND_NVRAM_TYPE_INT64:1232case BHND_NVRAM_TYPE_CHAR:1233case BHND_NVRAM_TYPE_STRING:1234error = bhnd_sprom_opcode_set_type(state,1235(bhnd_nvram_type)immd);1236if (error)1237return (error);1238break;1239default:1240BHND_NV_LOG("unrecognized type %#hhx\n", immd);1241return (EINVAL);1242}1243break;12441245default:1246BHND_NV_LOG("unrecognized opcode %#hhx\n", *opcode);1247return (EINVAL);1248}12491250/* We must keep evaluating until we hit a state applicable to1251* the SPROM revision we're parsing */1252if (bhnd_sprom_opcode_matches_layout_rev(state))1253return (0);1254}12551256/* End of opcode stream */1257return (ENOENT);1258}12591260/**1261* Reset SPROM opcode evaluation state, seek to the @p entry's position,1262* and perform complete evaluation of the variable's opcodes.1263*1264* @param state The opcode state to be to be evaluated.1265* @param entry The indexed variable location.1266*1267* @retval 0 success1268* @retval non-zero If evaluation fails, a regular unix error code will be1269* returned.1270*/1271int1272bhnd_sprom_opcode_eval_var(bhnd_sprom_opcode_state *state,1273bhnd_sprom_opcode_idx_entry *entry)1274{1275uint8_t opcode;1276int error;12771278/* Seek to entry */1279if ((error = bhnd_sprom_opcode_seek(state, entry)))1280return (error);12811282/* Parse full variable definition */1283while ((error = bhnd_sprom_opcode_step(state, &opcode)) == 0) {1284/* Iterate until VAR_END */1285if (SPROM_OPCODE_OP(opcode) != SPROM_OPCODE_VAR_END)1286continue;12871288BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_DONE,1289("incomplete variable definition"));12901291return (0);1292}12931294/* Error parsing definition */1295return (error);1296}12971298/**1299* Evaluate @p state until the next variable definition is found.1300*1301* @param state The opcode state to be evaluated.1302*1303* @retval 0 success1304* @retval ENOENT if no additional variable definitions are available.1305* @retval non-zero if evaluation otherwise fails, a regular unix error1306* code will be returned.1307*/1308int1309bhnd_sprom_opcode_next_var(bhnd_sprom_opcode_state *state)1310{1311uint8_t opcode;1312int error;13131314/* Step until we hit a variable opcode */1315while ((error = bhnd_sprom_opcode_step(state, &opcode)) == 0) {1316switch (SPROM_OPCODE_OP(opcode)) {1317case SPROM_OPCODE_VAR:1318case SPROM_OPCODE_VAR_IMM:1319case SPROM_OPCODE_VAR_REL_IMM:1320BHND_NV_ASSERT(1321state->var_state == SPROM_OPCODE_VAR_STATE_OPEN,1322("missing variable definition"));13231324return (0);1325default:1326continue;1327}1328}13291330/* Reached EOF, or evaluation failed */1331return (error);1332}13331334/**1335* Evaluate @p state until the next binding for the current variable definition1336* is found.1337*1338* @param state The opcode state to be evaluated.1339*1340* @retval 0 success1341* @retval ENOENT if no additional binding opcodes are found prior to reaching1342* a new variable definition, or the end of @p state's binding opcodes.1343* @retval non-zero if evaluation otherwise fails, a regular unix error1344* code will be returned.1345*/1346int1347bhnd_sprom_opcode_next_binding(bhnd_sprom_opcode_state *state)1348{1349uint8_t opcode;1350int error;13511352if (state->var_state != SPROM_OPCODE_VAR_STATE_OPEN)1353return (EINVAL);13541355/* Step until we hit a bind opcode, or a new variable */1356while ((error = bhnd_sprom_opcode_step(state, &opcode)) == 0) {1357switch (SPROM_OPCODE_OP(opcode)) {1358case SPROM_OPCODE_DO_BIND:1359case SPROM_OPCODE_DO_BINDN:1360case SPROM_OPCODE_DO_BINDN_IMM:1361/* Found next bind */1362BHND_NV_ASSERT(1363state->var_state == SPROM_OPCODE_VAR_STATE_OPEN,1364("missing variable definition"));1365BHND_NV_ASSERT(state->var.have_bind, ("missing bind"));13661367return (0);13681369case SPROM_OPCODE_VAR_END:1370/* No further binding opcodes */1371BHND_NV_ASSERT(1372state->var_state == SPROM_OPCODE_VAR_STATE_DONE,1373("variable definition still available"));1374return (ENOENT);1375}1376}13771378/* Not found, or evaluation failed */1379return (error);1380}138113821383