Path: blob/main/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.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/cdefs.h>30#include <sys/endian.h>3132#ifdef _KERNEL33#include <sys/param.h>34#include <sys/ctype.h>35#include <sys/malloc.h>36#include <sys/systm.h>3738#include <machine/_inttypes.h>39#else /* !_KERNEL */40#include <ctype.h>41#include <errno.h>42#include <inttypes.h>43#include <stdint.h>44#include <stdio.h>45#include <stdlib.h>46#include <string.h>47#endif /* _KERNEL */4849#include "bhnd_nvram_map.h"5051#include "bhnd_nvram_private.h"52#include "bhnd_nvram_datavar.h"5354#include "bhnd_nvram_data_spromvar.h"5556/*57* BHND SPROM NVRAM data class58*59* The SPROM data format is a fixed-layout, non-self-descriptive binary format,60* used on Broadcom wireless and wired adapters, that provides a subset of the61* variables defined by Broadcom SoC NVRAM formats.62*/6364static const bhnd_sprom_layout *bhnd_nvram_sprom_get_layout(uint8_t sromrev);6566static int bhnd_nvram_sprom_ident(67struct bhnd_nvram_io *io,68const bhnd_sprom_layout **ident);6970static int bhnd_nvram_sprom_write_var(71bhnd_sprom_opcode_state *state,72bhnd_sprom_opcode_idx_entry *entry,73bhnd_nvram_val *value,74struct bhnd_nvram_io *io);7576static int bhnd_nvram_sprom_read_var(77struct bhnd_sprom_opcode_state *state,78struct bhnd_sprom_opcode_idx_entry *entry,79struct bhnd_nvram_io *io,80union bhnd_nvram_sprom_storage *storage,81bhnd_nvram_val *val);8283static int bhnd_nvram_sprom_write_offset(84const struct bhnd_nvram_vardefn *var,85struct bhnd_nvram_io *data,86bhnd_nvram_type type, size_t offset,87uint32_t mask, int8_t shift,88uint32_t value);8990static int bhnd_nvram_sprom_read_offset(91const struct bhnd_nvram_vardefn *var,92struct bhnd_nvram_io *data,93bhnd_nvram_type type, size_t offset,94uint32_t mask, int8_t shift,95uint32_t *value);9697static bool bhnd_sprom_is_external_immutable(98const char *name);99100BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM",101BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_sprom))102103#define SPROM_COOKIE_TO_VID(_cookie) \104(((struct bhnd_sprom_opcode_idx_entry *)(_cookie))->vid)105106#define SPROM_COOKIE_TO_NVRAM_VAR(_cookie) \107bhnd_nvram_get_vardefn(SPROM_COOKIE_TO_VID(_cookie))108109/**110* Read the magic value from @p io, and verify that it matches111* the @p layout's expected magic value.112*113* If @p layout does not defined a magic value, @p magic is set to 0x0114* and success is returned.115*116* @param io An I/O context mapping the SPROM data to be identified.117* @param layout The SPROM layout against which @p io should be verified.118* @param[out] magic On success, the SPROM magic value.119*120* @retval 0 success121* @retval non-zero If checking @p io otherwise fails, a regular unix122* error code will be returned.123*/124static int125bhnd_nvram_sprom_check_magic(struct bhnd_nvram_io *io,126const bhnd_sprom_layout *layout, uint16_t *magic)127{128int error;129130/* Skip if layout does not define a magic value */131if (layout->flags & SPROM_LAYOUT_MAGIC_NONE)132return (0);133134/* Read the magic value */135error = bhnd_nvram_io_read(io, layout->magic_offset, magic,136sizeof(*magic));137if (error)138return (error);139140*magic = le16toh(*magic);141142/* If the signature does not match, skip to next layout */143if (*magic != layout->magic_value)144return (ENXIO);145146return (0);147}148149/**150* Attempt to identify the format of the SPROM data mapped by @p io.151*152* The SPROM data format does not provide any identifying information at a153* known offset, instead requiring that we iterate over the known SPROM image154* sizes until we are able to compute a valid checksum (and, for later155* revisions, validate a signature at a revision-specific offset).156*157* @param io An I/O context mapping the SPROM data to be identified.158* @param[out] ident On success, the identified SPROM layout.159*160* @retval 0 success161* @retval non-zero If identifying @p io otherwise fails, a regular unix162* error code will be returned.163*/164static int165bhnd_nvram_sprom_ident(struct bhnd_nvram_io *io,166const bhnd_sprom_layout **ident)167{168uint8_t crc;169size_t crc_errors;170size_t nbytes;171int error;172173crc = BHND_NVRAM_CRC8_INITIAL;174crc_errors = 0;175nbytes = 0;176177/* We iterate the SPROM layouts smallest to largest, allowing us to178* perform incremental checksum calculation */179for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {180const bhnd_sprom_layout *layout;181u_char buf[512];182size_t nread;183uint16_t magic;184uint8_t srevcrc[2];185uint8_t srev;186bool crc_valid;187bool have_magic;188189layout = &bhnd_sprom_layouts[i];190crc_valid = true;191192have_magic = true;193if ((layout->flags & SPROM_LAYOUT_MAGIC_NONE))194have_magic = false;195196/*197* Read image data and update CRC (errors are reported198* after the signature check)199*200* Layout instances must be ordered from smallest to largest by201* the nvram_map compiler, allowing us to incrementally update202* our CRC.203*/204if (nbytes > layout->size)205BHND_NV_PANIC("SPROM layout defined out-of-order");206207nread = layout->size - nbytes;208209while (nread > 0) {210size_t nr;211212nr = bhnd_nv_ummin(nread, sizeof(buf));213214if ((error = bhnd_nvram_io_read(io, nbytes, buf, nr)))215return (error);216217crc = bhnd_nvram_crc8(buf, nr, crc);218crc_valid = (crc == BHND_NVRAM_CRC8_VALID);219if (!crc_valid)220crc_errors++;221222nread -= nr;223nbytes += nr;224}225226/* Read 8-bit SPROM revision, maintaining 16-bit size alignment227* required by some OTP/SPROM chipsets. */228error = bhnd_nvram_io_read(io, layout->srev_offset, &srevcrc,229sizeof(srevcrc));230if (error)231return (error);232233srev = srevcrc[0];234235/* Early sromrev 1 devices (specifically some BCM440x enet236* cards) are reported to have been incorrectly programmed237* with a revision of 0x10. */238if (layout->rev == 1 && srev == 0x10)239srev = 0x1;240241/* Check revision against the layout definition */242if (srev != layout->rev)243continue;244245/* Check the magic value, skipping to the next layout on246* failure. */247error = bhnd_nvram_sprom_check_magic(io, layout, &magic);248if (error) {249/* If the CRC is was valid, log the mismatch */250if (crc_valid || BHND_NV_VERBOSE) {251BHND_NV_LOG("invalid sprom %hhu signature: "252"0x%hx (expected 0x%hx)\n", srev,253magic, layout->magic_value);254255return (ENXIO);256}257258continue;259}260261/* Check for an earlier CRC error */262if (!crc_valid) {263/* If the magic check succeeded, then we may just have264* data corruption -- log the CRC error */265if (have_magic || BHND_NV_VERBOSE) {266BHND_NV_LOG("sprom %hhu CRC error (crc=%#hhx, "267"expected=%#x)\n", srev, crc,268BHND_NVRAM_CRC8_VALID);269}270271continue;272}273274/* Identified */275*ident = layout;276return (0);277}278279/* No match */280if (crc_errors > 0 && BHND_NV_VERBOSE) {281BHND_NV_LOG("sprom parsing failed with %zu CRC errors\n",282crc_errors);283}284285return (ENXIO);286}287288static int289bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io)290{291const bhnd_sprom_layout *layout;292int error;293294/* Try to parse the input */295if ((error = bhnd_nvram_sprom_ident(io, &layout)))296return (error);297298return (BHND_NVRAM_DATA_PROBE_DEFAULT);299}300301static int302bhnd_nvram_sprom_getvar_direct(struct bhnd_nvram_io *io, const char *name,303void *buf, size_t *len, bhnd_nvram_type type)304{305const bhnd_sprom_layout *layout;306bhnd_sprom_opcode_state state;307const struct bhnd_nvram_vardefn *var;308size_t vid;309int error;310311/* Look up the variable definition and ID */312if ((var = bhnd_nvram_find_vardefn(name)) == NULL)313return (ENOENT);314315vid = bhnd_nvram_get_vardefn_id(var);316317/* Identify the SPROM image layout */318if ((error = bhnd_nvram_sprom_ident(io, &layout)))319return (error);320321/* Initialize SPROM layout interpreter */322if ((error = bhnd_sprom_opcode_init(&state, layout))) {323BHND_NV_LOG("error initializing opcode state: %d\n", error);324return (ENXIO);325}326327/* Find SPROM layout entry for the requested variable */328while ((error = bhnd_sprom_opcode_next_var(&state)) == 0) {329bhnd_sprom_opcode_idx_entry entry;330union bhnd_nvram_sprom_storage storage;331bhnd_nvram_val val;332333/* Fetch the variable's entry state */334if ((error = bhnd_sprom_opcode_init_entry(&state, &entry)))335return (error);336337/* Match against expected VID */338if (entry.vid != vid)339continue;340341/* Decode variable to a new value instance */342error = bhnd_nvram_sprom_read_var(&state, &entry, io, &storage,343&val);344if (error)345return (error);346347/* Perform value coercion */348error = bhnd_nvram_val_encode(&val, buf, len, type);349350/* Clean up */351bhnd_nvram_val_release(&val);352return (error);353}354355/* Hit EOF without matching the requested variable? */356if (error == ENOENT)357return (ENOENT);358359/* Some other parse error occurred */360return (error);361}362363/**364* Return the SPROM layout definition for the given @p sromrev, or NULL if365* not found.366*/367static const bhnd_sprom_layout *368bhnd_nvram_sprom_get_layout(uint8_t sromrev)369{370/* Find matching SPROM layout definition */371for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {372if (bhnd_sprom_layouts[i].rev == sromrev)373return (&bhnd_sprom_layouts[i]);374}375376/* Not found */377return (NULL);378}379380/**381* Serialize a SPROM variable.382*383* @param state The SPROM opcode state describing the layout of @p io.384* @param entry The variable's SPROM opcode index entry.385* @param value The value to encode to @p io as per @p entry.386* @param io I/O context to which @p value should be written, or NULL387* if no output should be produced. This may be used to validate388* values prior to write.389*390* @retval 0 success391* @retval EFTYPE If value coercion from @p value to the type required by392* @p entry is unsupported.393* @retval ERANGE If value coercion from @p value would overflow394* (or underflow) the type required by @p entry.395* @retval non-zero If serialization otherwise fails, a regular unix error396* code will be returned.397*/398static int399bhnd_nvram_sprom_write_var(bhnd_sprom_opcode_state *state,400bhnd_sprom_opcode_idx_entry *entry, bhnd_nvram_val *value,401struct bhnd_nvram_io *io)402{403const struct bhnd_nvram_vardefn *var;404uint32_t u32[BHND_SPROM_ARRAY_MAXLEN];405bhnd_nvram_type itype, var_base_type;406size_t ipos, ilen, nelem;407int error;408409/* Fetch variable definition and the native element type */410var = bhnd_nvram_get_vardefn(entry->vid);411BHND_NV_ASSERT(var != NULL, ("missing variable definition"));412413var_base_type = bhnd_nvram_base_type(var->type);414415/* Fetch the element count from the SPROM variable layout definition */416if ((error = bhnd_sprom_opcode_eval_var(state, entry)))417return (error);418419nelem = state->var.nelem;420BHND_NV_ASSERT(nelem <= var->nelem, ("SPROM nelem=%zu exceeds maximum "421"NVRAM nelem=%hhu", nelem, var->nelem));422423/* Promote the data to a common 32-bit representation */424if (bhnd_nvram_is_signed_type(var_base_type))425itype = BHND_NVRAM_TYPE_INT32_ARRAY;426else427itype = BHND_NVRAM_TYPE_UINT32_ARRAY;428429/* Calculate total size of the 32-bit promoted representation */430if ((ilen = bhnd_nvram_value_size(NULL, 0, itype, nelem)) == 0) {431/* Variable-width types are unsupported */432BHND_NV_LOG("invalid %s SPROM variable type %d\n",433var->name, var->type);434return (EFTYPE);435}436437/* The native representation must fit within our scratch array */438if (ilen > sizeof(u32)) {439BHND_NV_LOG("error encoding '%s', SPROM_ARRAY_MAXLEN "440"incorrect\n", var->name);441return (EFTYPE);442}443444/* Initialize our common 32-bit value representation */445if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) {446/* No value provided; can this variable be encoded as missing447* by setting all bits to one? */448if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) {449BHND_NV_LOG("missing required property: %s\n",450var->name);451return (EINVAL);452}453454/* Set all bits */455memset(u32, 0xFF, ilen);456} else {457bhnd_nvram_val bcm_val;458const void *var_ptr;459bhnd_nvram_type var_type, raw_type;460size_t var_len, enc_nelem;461462/* Try to coerce the value to the native variable format. */463error = bhnd_nvram_val_convert_init(&bcm_val, var->fmt, value,464BHND_NVRAM_VAL_DYNAMIC|BHND_NVRAM_VAL_BORROW_DATA);465if (error) {466BHND_NV_LOG("error converting input type %s to %s "467"format\n",468bhnd_nvram_type_name(bhnd_nvram_val_type(value)),469bhnd_nvram_val_fmt_name(var->fmt));470return (error);471}472473var_ptr = bhnd_nvram_val_bytes(&bcm_val, &var_len, &var_type);474475/*476* Promote to a common 32-bit representation.477*478* We must use the raw type to interpret the input data as its479* underlying integer representation -- otherwise, coercion480* would attempt to parse the input as its complex481* representation.482*483* For example, direct CHAR -> UINT32 coercion would attempt to484* parse the character as a decimal integer, rather than485* promoting the raw UTF8 byte value to a 32-bit value.486*/487raw_type = bhnd_nvram_raw_type(var_type);488error = bhnd_nvram_value_coerce(var_ptr, var_len, raw_type,489u32, &ilen, itype);490491/* Clean up temporary value representation */492bhnd_nvram_val_release(&bcm_val);493494/* Report coercion failure */495if (error) {496BHND_NV_LOG("error promoting %s to %s: %d\n",497bhnd_nvram_type_name(var_type),498bhnd_nvram_type_name(itype), error);499return (error);500}501502/* Encoded element count must match SPROM's definition */503error = bhnd_nvram_value_nelem(u32, ilen, itype, &enc_nelem);504if (error)505return (error);506507if (enc_nelem != nelem) {508const char *type_name;509510type_name = bhnd_nvram_type_name(var_base_type);511BHND_NV_LOG("invalid %s property value '%s[%zu]': "512"required %s[%zu]", var->name, type_name,513enc_nelem, type_name, nelem);514return (EFTYPE);515}516}517518/*519* Seek to the start of the variable's SPROM layout definition and520* iterate over all bindings.521*/522if ((error = bhnd_sprom_opcode_seek(state, entry))) {523BHND_NV_LOG("variable seek failed: %d\n", error);524return (error);525}526527ipos = 0;528while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) {529bhnd_sprom_opcode_bind *binding;530bhnd_sprom_opcode_var *binding_var;531size_t offset;532uint32_t skip_out_bytes;533534BHND_NV_ASSERT(535state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN,536("invalid var state"));537BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state"));538539binding_var = &state->var;540binding = &state->var.bind;541542/* Calculate output skip bytes for this binding.543*544* Skip directions are defined in terms of decoding, and545* reversed when encoding. */546skip_out_bytes = binding->skip_in;547error = bhnd_sprom_opcode_apply_scale(state, &skip_out_bytes);548if (error)549return (error);550551/* Bind */552offset = state->offset;553for (size_t i = 0; i < binding->count; i++) {554if (ipos >= nelem) {555BHND_NV_LOG("input skip %u positioned %zu "556"beyond nelem %zu\n", binding->skip_out,557ipos, nelem);558return (EINVAL);559}560561/* Write next offset */562if (io != NULL) {563error = bhnd_nvram_sprom_write_offset(var, io,564binding_var->base_type,565offset,566binding_var->mask,567binding_var->shift,568u32[ipos]);569if (error)570return (error);571}572573/* Adjust output position; this was already verified to574* not overflow/underflow during SPROM opcode575* evaluation */576if (binding->skip_in_negative) {577offset -= skip_out_bytes;578} else {579offset += skip_out_bytes;580}581582/* Skip advancing input if additional bindings are583* required to fully encode intv */584if (binding->skip_out == 0)585continue;586587/* Advance input position */588if (SIZE_MAX - binding->skip_out < ipos) {589BHND_NV_LOG("output skip %u would overflow "590"%zu\n", binding->skip_out, ipos);591return (EINVAL);592}593594ipos += binding->skip_out;595}596}597598/* Did we iterate all bindings until hitting end of the variable599* definition? */600BHND_NV_ASSERT(error != 0, ("loop terminated early"));601if (error != ENOENT)602return (error);603604return (0);605}606607static int608bhnd_nvram_sprom_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,609bhnd_nvram_plist *options, void *outp, size_t *olen)610{611bhnd_sprom_opcode_state state;612struct bhnd_nvram_io *io;613bhnd_nvram_prop *prop;614bhnd_sprom_opcode_idx_entry *entry;615const bhnd_sprom_layout *layout;616size_t limit;617uint8_t crc;618uint8_t sromrev;619int error;620621limit = *olen;622layout = NULL;623io = NULL;624625/* Fetch sromrev property */626if (!bhnd_nvram_plist_contains(props, BHND_NVAR_SROMREV)) {627BHND_NV_LOG("missing required property: %s\n",628BHND_NVAR_SROMREV);629return (EINVAL);630}631632error = bhnd_nvram_plist_get_uint8(props, BHND_NVAR_SROMREV, &sromrev);633if (error) {634BHND_NV_LOG("error reading sromrev property: %d\n", error);635return (EFTYPE);636}637638/* Find SPROM layout definition */639if ((layout = bhnd_nvram_sprom_get_layout(sromrev)) == NULL) {640BHND_NV_LOG("unsupported sromrev: %hhu\n", sromrev);641return (EFTYPE);642}643644/* Provide required size to caller */645*olen = layout->size;646if (outp == NULL)647return (0);648else if (limit < *olen)649return (ENOMEM);650651/* Initialize SPROM layout interpreter */652if ((error = bhnd_sprom_opcode_init(&state, layout))) {653BHND_NV_LOG("error initializing opcode state: %d\n", error);654return (ENXIO);655}656657/* Check for unsupported properties */658prop = NULL;659while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {660const char *name;661662/* Fetch the corresponding SPROM layout index entry */663name = bhnd_nvram_prop_name(prop);664entry = bhnd_sprom_opcode_index_find(&state, name);665if (entry == NULL) {666BHND_NV_LOG("property '%s' unsupported by sromrev "667"%hhu\n", name, layout->rev);668error = EINVAL;669goto finished;670}671}672673/* Zero-initialize output */674memset(outp, 0, *olen);675676/* Allocate wrapping I/O context for output buffer */677io = bhnd_nvram_ioptr_new(outp, *olen, *olen, BHND_NVRAM_IOPTR_RDWR);678if (io == NULL) {679error = ENOMEM;680goto finished;681}682683/*684* Serialize all SPROM variable data.685*/686entry = NULL;687while ((entry = bhnd_sprom_opcode_index_next(&state, entry)) != NULL) {688const struct bhnd_nvram_vardefn *var;689bhnd_nvram_val *val;690691var = bhnd_nvram_get_vardefn(entry->vid);692BHND_NV_ASSERT(var != NULL, ("missing variable definition"));693694/* Fetch prop; will be NULL if unavailable */695prop = bhnd_nvram_plist_get_prop(props, var->name);696if (prop != NULL) {697val = bhnd_nvram_prop_val(prop);698} else {699val = BHND_NVRAM_VAL_NULL;700}701702/* Attempt to serialize the property value to the appropriate703* offset within the output buffer */704error = bhnd_nvram_sprom_write_var(&state, entry, val, io);705if (error) {706BHND_NV_LOG("error serializing %s to required type "707"%s: %d\n", var->name,708bhnd_nvram_type_name(var->type), error);709710/* ENOMEM is reserved for signaling that the output711* buffer capacity is insufficient */712if (error == ENOMEM)713error = EINVAL;714715goto finished;716}717}718719/*720* Write magic value, if any.721*/722if (!(layout->flags & SPROM_LAYOUT_MAGIC_NONE)) {723uint16_t magic;724725magic = htole16(layout->magic_value);726error = bhnd_nvram_io_write(io, layout->magic_offset, &magic,727sizeof(magic));728if (error) {729BHND_NV_LOG("error writing magic value: %d\n", error);730goto finished;731}732}733734/* Calculate the CRC over all SPROM data, not including the CRC byte. */735crc = ~bhnd_nvram_crc8(outp, layout->crc_offset,736BHND_NVRAM_CRC8_INITIAL);737738/* Write the checksum. */739error = bhnd_nvram_io_write(io, layout->crc_offset, &crc, sizeof(crc));740if (error) {741BHND_NV_LOG("error writing CRC value: %d\n", error);742goto finished;743}744745/*746* Success!747*/748error = 0;749750finished:751bhnd_sprom_opcode_fini(&state);752753if (io != NULL)754bhnd_nvram_io_free(io);755756return (error);757}758759static int760bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)761{762struct bhnd_nvram_sprom *sp;763int error;764765sp = (struct bhnd_nvram_sprom *)nv;766767/* Identify the SPROM input data */768if ((error = bhnd_nvram_sprom_ident(io, &sp->layout)))769return (error);770771/* Copy SPROM image to our shadow buffer */772sp->data = bhnd_nvram_iobuf_copy_range(io, 0, sp->layout->size);773if (sp->data == NULL)774goto failed;775776/* Initialize SPROM binding eval state */777if ((error = bhnd_sprom_opcode_init(&sp->state, sp->layout)))778goto failed;779780return (0);781782failed:783if (sp->data != NULL)784bhnd_nvram_io_free(sp->data);785786return (error);787}788789static void790bhnd_nvram_sprom_free(struct bhnd_nvram_data *nv)791{792struct bhnd_nvram_sprom *sp = (struct bhnd_nvram_sprom *)nv;793794bhnd_sprom_opcode_fini(&sp->state);795bhnd_nvram_io_free(sp->data);796}797798size_t799bhnd_nvram_sprom_count(struct bhnd_nvram_data *nv)800{801struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv;802return (sprom->layout->num_vars);803}804805static bhnd_nvram_plist *806bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv)807{808return (NULL);809}810811static uint32_t812bhnd_nvram_sprom_caps(struct bhnd_nvram_data *nv)813{814return (BHND_NVRAM_DATA_CAP_INDEXED);815}816817static const char *818bhnd_nvram_sprom_next(struct bhnd_nvram_data *nv, void **cookiep)819{820struct bhnd_nvram_sprom *sp;821bhnd_sprom_opcode_idx_entry *entry;822const struct bhnd_nvram_vardefn *var;823824sp = (struct bhnd_nvram_sprom *)nv;825826/* Find next index entry that is not disabled by virtue of IGNALL1 */827entry = *cookiep;828while ((entry = bhnd_sprom_opcode_index_next(&sp->state, entry))) {829/* Update cookiep and fetch variable definition */830*cookiep = entry;831var = SPROM_COOKIE_TO_NVRAM_VAR(*cookiep);832BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));833834/* We might need to parse the variable's value to determine835* whether it should be treated as unset */836if (var->flags & BHND_NVRAM_VF_IGNALL1) {837int error;838size_t len;839840error = bhnd_nvram_sprom_getvar(nv, *cookiep, NULL,841&len, var->type);842if (error) {843BHND_NV_ASSERT(error == ENOENT, ("unexpected "844"error parsing variable: %d", error));845continue;846}847}848849/* Found! */850return (var->name);851}852853/* Reached end of index entries */854return (NULL);855}856857static void *858bhnd_nvram_sprom_find(struct bhnd_nvram_data *nv, const char *name)859{860struct bhnd_nvram_sprom *sp;861bhnd_sprom_opcode_idx_entry *entry;862863sp = (struct bhnd_nvram_sprom *)nv;864865entry = bhnd_sprom_opcode_index_find(&sp->state, name);866return (entry);867}868869/**870* Write @p value of @p type to the SPROM @p data at @p offset, applying871* @p mask and @p shift, and OR with the existing data.872*873* @param var The NVRAM variable definition.874* @param data The SPROM data to be modified.875* @param type The type to write at @p offset.876* @param offset The data offset to be written.877* @param mask The mask to be applied to @p value after shifting.878* @param shift The shift to be applied to @p value; if positive, a left879* shift will be applied, if negative, a right shift (this is the reverse of the880* decoding behavior)881* @param value The value to be written. The parsed value will be OR'd with the882* current contents of @p data at @p offset.883*/884static int885bhnd_nvram_sprom_write_offset(const struct bhnd_nvram_vardefn *var,886struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset,887uint32_t mask, int8_t shift, uint32_t value)888{889union bhnd_nvram_sprom_storage scratch;890int error;891892#define NV_WRITE_INT(_widen, _repr, _swap) do { \893/* Narrow the 32-bit representation */ \894scratch._repr[1] = (_widen)value; \895\896/* Shift and mask the new value */ \897if (shift > 0) \898scratch._repr[1] <<= shift; \899else if (shift < 0) \900scratch._repr[1] >>= -shift; \901scratch._repr[1] &= mask; \902\903/* Swap to output byte order */ \904scratch._repr[1] = _swap(scratch._repr[1]); \905\906/* Fetch the current value */ \907error = bhnd_nvram_io_read(data, offset, \908&scratch._repr[0], sizeof(scratch._repr[0])); \909if (error) { \910BHND_NV_LOG("error reading %s SPROM offset " \911"%#zx: %d\n", var->name, offset, error); \912return (EFTYPE); \913} \914\915/* Mask and set our new value's bits in the current \916* value */ \917if (shift >= 0) \918scratch._repr[0] &= ~_swap(mask << shift); \919else if (shift < 0) \920scratch._repr[0] &= ~_swap(mask >> (-shift)); \921scratch._repr[0] |= scratch._repr[1]; \922\923/* Perform write */ \924error = bhnd_nvram_io_write(data, offset, \925&scratch._repr[0], sizeof(scratch._repr[0])); \926if (error) { \927BHND_NV_LOG("error writing %s SPROM offset " \928"%#zx: %d\n", var->name, offset, error); \929return (EFTYPE); \930} \931} while(0)932933/* Apply mask/shift and widen to a common 32bit representation */934switch (type) {935case BHND_NVRAM_TYPE_UINT8:936NV_WRITE_INT(uint32_t, u8, );937break;938case BHND_NVRAM_TYPE_UINT16:939NV_WRITE_INT(uint32_t, u16, htole16);940break;941case BHND_NVRAM_TYPE_UINT32:942NV_WRITE_INT(uint32_t, u32, htole32);943break;944case BHND_NVRAM_TYPE_INT8:945NV_WRITE_INT(int32_t, i8, );946break;947case BHND_NVRAM_TYPE_INT16:948NV_WRITE_INT(int32_t, i16, htole16);949break;950case BHND_NVRAM_TYPE_INT32:951NV_WRITE_INT(int32_t, i32, htole32);952break;953case BHND_NVRAM_TYPE_CHAR:954NV_WRITE_INT(uint32_t, u8, );955break;956default:957BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type);958return (EFTYPE);959}960#undef NV_WRITE_INT961962return (0);963}964965/**966* Read the value of @p type from the SPROM @p data at @p offset, apply @p mask967* and @p shift, and OR with the existing @p value.968*969* @param var The NVRAM variable definition.970* @param data The SPROM data to be decoded.971* @param type The type to read at @p offset972* @param offset The data offset to be read.973* @param mask The mask to be applied to the value read at @p offset.974* @param shift The shift to be applied after masking; if positive, a right975* shift will be applied, if negative, a left shift.976* @param value The read destination; the parsed value will be OR'd with the977* current contents of @p value.978*/979static int980bhnd_nvram_sprom_read_offset(const struct bhnd_nvram_vardefn *var,981struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset,982uint32_t mask, int8_t shift, uint32_t *value)983{984union bhnd_nvram_sprom_storage scratch;985int error;986987#define NV_PARSE_INT(_widen, _repr, _swap) do { \988/* Perform read */ \989error = bhnd_nvram_io_read(data, offset, \990&scratch._repr[0], sizeof(scratch._repr[0])); \991if (error) { \992BHND_NV_LOG("error reading %s SPROM offset " \993"%#zx: %d\n", var->name, offset, error); \994return (EFTYPE); \995} \996\997/* Swap to host byte order */ \998scratch._repr[0] = _swap(scratch._repr[0]); \999\1000/* Mask and shift the value */ \1001scratch._repr[0] &= mask; \1002if (shift > 0) { \1003scratch. _repr[0] >>= shift; \1004} else if (shift < 0) { \1005scratch. _repr[0] <<= -shift; \1006} \1007\1008/* Widen to 32-bit representation and OR with current \1009* value */ \1010(*value) |= (_widen)scratch._repr[0]; \1011} while(0)10121013/* Apply mask/shift and widen to a common 32bit representation */1014switch (type) {1015case BHND_NVRAM_TYPE_UINT8:1016NV_PARSE_INT(uint32_t, u8, );1017break;1018case BHND_NVRAM_TYPE_UINT16:1019NV_PARSE_INT(uint32_t, u16, le16toh);1020break;1021case BHND_NVRAM_TYPE_UINT32:1022NV_PARSE_INT(uint32_t, u32, le32toh);1023break;1024case BHND_NVRAM_TYPE_INT8:1025NV_PARSE_INT(int32_t, i8, );1026break;1027case BHND_NVRAM_TYPE_INT16:1028NV_PARSE_INT(int32_t, i16, le16toh);1029break;1030case BHND_NVRAM_TYPE_INT32:1031NV_PARSE_INT(int32_t, i32, le32toh);1032break;1033case BHND_NVRAM_TYPE_CHAR:1034NV_PARSE_INT(uint32_t, u8, );1035break;1036default:1037BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type);1038return (EFTYPE);1039}1040#undef NV_PARSE_INT10411042return (0);1043}10441045/**1046* Read a SPROM variable value from @p io.1047*1048* @param state The SPROM opcode state describing the layout of @p io.1049* @param entry The variable's SPROM opcode index entry.1050* @param io The input I/O context.1051* @param storage Storage to be used with @p val.1052* @param[out] val Value instance to be initialized with the1053* parsed variable data.1054*1055* The returned @p val instance will hold a borrowed reference to @p storage,1056* and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond1057* the lifetime of @p storage.1058*1059* The caller is responsible for releasing any allocated value state1060* via bhnd_nvram_val_release().1061*/1062static int1063bhnd_nvram_sprom_read_var(struct bhnd_sprom_opcode_state *state,1064struct bhnd_sprom_opcode_idx_entry *entry, struct bhnd_nvram_io *io,1065union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val)1066{1067union bhnd_nvram_sprom_storage *inp;1068const struct bhnd_nvram_vardefn *var;1069bhnd_nvram_type var_btype;1070uint32_t intv;1071size_t ilen, ipos, iwidth;1072size_t nelem;1073bool all_bits_set;1074int error;10751076/* Fetch canonical variable definition */1077var = bhnd_nvram_get_vardefn(entry->vid);1078BHND_NV_ASSERT(var != NULL, ("invalid entry"));10791080/*1081* Fetch the array length from the SPROM variable definition.1082*1083* This generally be identical to the array length provided by the1084* canonical NVRAM variable definition, but some SPROM layouts may1085* define a smaller element count.1086*/1087if ((error = bhnd_sprom_opcode_eval_var(state, entry))) {1088BHND_NV_LOG("variable evaluation failed: %d\n", error);1089return (error);1090}10911092nelem = state->var.nelem;1093if (nelem > var->nelem) {1094BHND_NV_LOG("SPROM array element count %zu cannot be "1095"represented by '%s' element count of %hhu\n", nelem,1096var->name, var->nelem);1097return (EFTYPE);1098}10991100/* Fetch the var's base element type */1101var_btype = bhnd_nvram_base_type(var->type);11021103/* Calculate total byte length of the native encoding */1104if ((iwidth = bhnd_nvram_value_size(NULL, 0, var_btype, 1)) == 0) {1105/* SPROM does not use (and we do not support) decoding of1106* variable-width data types */1107BHND_NV_LOG("invalid SPROM data type: %d", var->type);1108return (EFTYPE);1109}1110ilen = nelem * iwidth;11111112/* Decode into our caller's local storage */1113inp = storage;1114if (ilen > sizeof(*storage)) {1115BHND_NV_LOG("error decoding '%s', SPROM_ARRAY_MAXLEN "1116"incorrect\n", var->name);1117return (EFTYPE);1118}11191120/* Zero-initialize our decode buffer; any output elements skipped1121* during decode should default to zero. */1122memset(inp, 0, ilen);11231124/*1125* Decode the SPROM data, iteratively decoding up to nelem values.1126*/1127if ((error = bhnd_sprom_opcode_seek(state, entry))) {1128BHND_NV_LOG("variable seek failed: %d\n", error);1129return (error);1130}11311132ipos = 0;1133intv = 0x0;1134if (var->flags & BHND_NVRAM_VF_IGNALL1)1135all_bits_set = true;1136else1137all_bits_set = false;1138while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) {1139bhnd_sprom_opcode_bind *binding;1140bhnd_sprom_opcode_var *binding_var;1141bhnd_nvram_type intv_type;1142size_t offset;1143size_t nbyte;1144uint32_t skip_in_bytes;1145void *ptr;11461147BHND_NV_ASSERT(1148state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN,1149("invalid var state"));1150BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state"));11511152binding_var = &state->var;1153binding = &state->var.bind;11541155if (ipos >= nelem) {1156BHND_NV_LOG("output skip %u positioned "1157"%zu beyond nelem %zu\n",1158binding->skip_out, ipos, nelem);1159return (EINVAL);1160}11611162/* Calculate input skip bytes for this binding */1163skip_in_bytes = binding->skip_in;1164error = bhnd_sprom_opcode_apply_scale(state, &skip_in_bytes);1165if (error)1166return (error);11671168/* Bind */1169offset = state->offset;1170for (size_t i = 0; i < binding->count; i++) {1171/* Read the offset value, OR'ing with the current1172* value of intv */1173error = bhnd_nvram_sprom_read_offset(var, io,1174binding_var->base_type,1175offset,1176binding_var->mask,1177binding_var->shift,1178&intv);1179if (error)1180return (error);11811182/* If IGNALL1, record whether value does not have1183* all bits set. */1184if (var->flags & BHND_NVRAM_VF_IGNALL1 &&1185all_bits_set)1186{1187uint32_t all1;11881189all1 = binding_var->mask;1190if (binding_var->shift > 0)1191all1 >>= binding_var->shift;1192else if (binding_var->shift < 0)1193all1 <<= -binding_var->shift;11941195if ((intv & all1) != all1)1196all_bits_set = false;1197}11981199/* Adjust input position; this was already verified to1200* not overflow/underflow during SPROM opcode1201* evaluation */1202if (binding->skip_in_negative) {1203offset -= skip_in_bytes;1204} else {1205offset += skip_in_bytes;1206}12071208/* Skip writing to inp if additional bindings are1209* required to fully populate intv */1210if (binding->skip_out == 0)1211continue;12121213/* We use bhnd_nvram_value_coerce() to perform1214* overflow-checked coercion from the widened1215* uint32/int32 intv value to the requested output1216* type */1217if (bhnd_nvram_is_signed_type(var_btype))1218intv_type = BHND_NVRAM_TYPE_INT32;1219else1220intv_type = BHND_NVRAM_TYPE_UINT32;12211222/* Calculate address of the current element output1223* position */1224ptr = (uint8_t *)inp + (iwidth * ipos);12251226/* Perform coercion of the array element */1227nbyte = iwidth;1228error = bhnd_nvram_value_coerce(&intv, sizeof(intv),1229intv_type, ptr, &nbyte, var_btype);1230if (error)1231return (error);12321233/* Clear temporary state */1234intv = 0x0;12351236/* Advance output position */1237if (SIZE_MAX - binding->skip_out < ipos) {1238BHND_NV_LOG("output skip %u would overflow "1239"%zu\n", binding->skip_out, ipos);1240return (EINVAL);1241}12421243ipos += binding->skip_out;1244}1245}12461247/* Did we iterate all bindings until hitting end of the variable1248* definition? */1249BHND_NV_ASSERT(error != 0, ("loop terminated early"));1250if (error != ENOENT) {1251return (error);1252}12531254/* If marked IGNALL1 and all bits are set, treat variable as1255* unavailable */1256if ((var->flags & BHND_NVRAM_VF_IGNALL1) && all_bits_set)1257return (ENOENT);12581259/* Provide value wrapper */1260return (bhnd_nvram_val_init(val, var->fmt, inp, ilen, var->type,1261BHND_NVRAM_VAL_BORROW_DATA));1262}12631264/**1265* Common variable decoding; fetches and decodes variable to @p val,1266* using @p storage for actual data storage.1267*1268* The returned @p val instance will hold a borrowed reference to @p storage,1269* and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond1270* the lifetime of @p storage.1271*1272* The caller is responsible for releasing any allocated value state1273* via bhnd_nvram_val_release().1274*/1275static int1276bhnd_nvram_sprom_getvar_common(struct bhnd_nvram_data *nv, void *cookiep,1277union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val)1278{1279struct bhnd_nvram_sprom *sp;1280bhnd_sprom_opcode_idx_entry *entry;1281const struct bhnd_nvram_vardefn *var __diagused;12821283BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));12841285sp = (struct bhnd_nvram_sprom *)nv;1286entry = cookiep;12871288/* Fetch canonical variable definition */1289var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep);1290BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));12911292return (bhnd_nvram_sprom_read_var(&sp->state, entry, sp->data, storage,1293val));1294}12951296static int1297bhnd_nvram_sprom_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,1298void *cookiep2)1299{1300struct bhnd_sprom_opcode_idx_entry *e1, *e2;13011302e1 = cookiep1;1303e2 = cookiep2;13041305/* Use the index entry order; this matches the order of variables1306* returned via bhnd_nvram_sprom_next() */1307if (e1 < e2)1308return (-1);1309else if (e1 > e2)1310return (1);13111312return (0);1313}13141315static int1316bhnd_nvram_sprom_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,1317size_t *len, bhnd_nvram_type otype)1318{1319bhnd_nvram_val val;1320union bhnd_nvram_sprom_storage storage;1321int error;13221323/* Decode variable to a new value instance */1324error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val);1325if (error)1326return (error);13271328/* Perform value coercion */1329error = bhnd_nvram_val_encode(&val, buf, len, otype);13301331/* Clean up */1332bhnd_nvram_val_release(&val);1333return (error);1334}13351336static int1337bhnd_nvram_sprom_copy_val(struct bhnd_nvram_data *nv, void *cookiep,1338bhnd_nvram_val **value)1339{1340bhnd_nvram_val val;1341union bhnd_nvram_sprom_storage storage;1342int error;13431344/* Decode variable to a new value instance */1345error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val);1346if (error)1347return (error);13481349/* Attempt to copy to heap */1350*value = bhnd_nvram_val_copy(&val);1351bhnd_nvram_val_release(&val);13521353if (*value == NULL)1354return (ENOMEM);13551356return (0);1357}13581359static const void *1360bhnd_nvram_sprom_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,1361size_t *len, bhnd_nvram_type *type)1362{1363/* Unsupported */1364return (NULL);1365}13661367static const char *1368bhnd_nvram_sprom_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)1369{1370const struct bhnd_nvram_vardefn *var;13711372BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));13731374var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep);1375BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));13761377return (var->name);1378}13791380static int1381bhnd_nvram_sprom_filter_setvar(struct bhnd_nvram_data *nv, const char *name,1382bhnd_nvram_val *value, bhnd_nvram_val **result)1383{1384struct bhnd_nvram_sprom *sp;1385const struct bhnd_nvram_vardefn *var;1386bhnd_sprom_opcode_idx_entry *entry;1387bhnd_nvram_val *spval;1388int error;13891390sp = (struct bhnd_nvram_sprom *)nv;13911392/* Is this an externally immutable variable name? */1393if (bhnd_sprom_is_external_immutable(name))1394return (EINVAL);13951396/* Variable must be defined in our SPROM layout */1397if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL)1398return (ENOENT);13991400var = bhnd_nvram_get_vardefn(entry->vid);1401BHND_NV_ASSERT(var != NULL, ("missing variable definition"));14021403/* Value must be convertible to the native variable type */1404error = bhnd_nvram_val_convert_new(&spval, var->fmt, value,1405BHND_NVRAM_VAL_DYNAMIC);1406if (error)1407return (error);14081409/* Value must be encodeable by our SPROM layout */1410error = bhnd_nvram_sprom_write_var(&sp->state, entry, spval, NULL);1411if (error) {1412bhnd_nvram_val_release(spval);1413return (error);1414}14151416/* Success. Transfer our ownership of the converted value to the1417* caller */1418*result = spval;1419return (0);1420}14211422static int1423bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)1424{1425struct bhnd_nvram_sprom *sp;1426const struct bhnd_nvram_vardefn *var;1427bhnd_sprom_opcode_idx_entry *entry;14281429sp = (struct bhnd_nvram_sprom *)nv;14301431/* Is this an externally immutable variable name? */1432if (bhnd_sprom_is_external_immutable(name))1433return (EINVAL);14341435/* Variable must be defined in our SPROM layout */1436if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL)1437return (ENOENT);14381439var = bhnd_nvram_get_vardefn(entry->vid);1440BHND_NV_ASSERT(var != NULL, ("missing variable definition"));14411442/* Variable must be capable of representing a NULL/deleted value.1443*1444* Since SPROM's layout is fixed, this requires IGNALL -- if1445* all bits are set, an IGNALL variable is treated as unset. */1446if (!(var->flags & BHND_NVRAM_VF_IGNALL1))1447return (EINVAL);14481449return (0);1450}14511452/**1453* Return true if @p name represents a special immutable variable name1454* (e.g. sromrev) that cannot be updated in an SPROM existing image.1455*1456* @param name The name to check.1457*/1458static bool1459bhnd_sprom_is_external_immutable(const char *name)1460{1461/* The layout revision is immutable and cannot be changed */1462if (strcmp(name, BHND_NVAR_SROMREV) == 0)1463return (true);14641465return (false);1466}146714681469