Index: head/sys/conf/files =================================================================== --- head/sys/conf/files +++ head/sys/conf/files @@ -1236,6 +1236,7 @@ dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c optional bhnd dev/bhnd/nvram/bhnd_nvram_data_btxt.c optional bhnd dev/bhnd/nvram/bhnd_nvram_data_sprom.c optional bhnd +dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c optional bhnd dev/bhnd/nvram/bhnd_nvram_data_tlv.c optional bhnd dev/bhnd/nvram/bhnd_nvram_if.m optional bhnd dev/bhnd/nvram/bhnd_nvram_io.c optional bhnd Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.h @@ -91,6 +91,11 @@ }; const char *bhnd_nvram_data_class_desc(bhnd_nvram_data_class *cls); +uint32_t bhnd_nvram_data_class_caps(bhnd_nvram_data_class *cls); + +int bhnd_nvram_data_serialize(bhnd_nvram_data_class *cls, + bhnd_nvram_plist *props, bhnd_nvram_plist *options, + void *outp, size_t *olen); int bhnd_nvram_data_probe(bhnd_nvram_data_class *cls, struct bhnd_nvram_io *io); @@ -110,12 +115,6 @@ bhnd_nvram_data_class *bhnd_nvram_data_get_class(struct bhnd_nvram_data *nv); size_t bhnd_nvram_data_count(struct bhnd_nvram_data *nv); -int bhnd_nvram_data_size(struct bhnd_nvram_data *nv, - size_t *size); - -int bhnd_nvram_data_serialize(struct bhnd_nvram_data *nv, - void *buf, size_t *len); - bhnd_nvram_plist *bhnd_nvram_data_options(struct bhnd_nvram_data *nv); uint32_t bhnd_nvram_data_caps(struct bhnd_nvram_data *nv); Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data.c @@ -65,6 +65,54 @@ } /** + * Return the class-level capability flags (@see BHND_NVRAM_DATA_CAP_*) for + * of @p cls. + * + * @param cls The NVRAM class. + */ +uint32_t +bhnd_nvram_data_class_caps(bhnd_nvram_data_class *cls) +{ + return (cls->caps); +} + +/** + * Serialize all NVRAM properties in @p plist using @p cls's NVRAM data + * format, writing the result to @p outp. + * + * @param cls The NVRAM data class to be used to perform + * serialization. + * @param props The raw property values to be serialized to + * @p outp, in serialization order. + * @param options Serialization options for @p cls, or NULL. + * @param[out] outp On success, the serialed NVRAM data will be + * written to this buffer. This argment may be + * NULL if the value is not desired. + * @param[in,out] olen The capacity of @p buf. On success, will be set + * to the actual length of the serialized data. + * + * @retval 0 success + * + * @retval ENOMEM If @p outp is non-NULL and a buffer of @p olen is too + * small to hold the serialized data. + * @retval EINVAL If a property value required by @p cls is not found in + * @p plist. + * @retval EFTYPE If a property value in @p plist cannot be represented + * as the data type required by @p cls. + * @retval ERANGE If a property value in @p plist would would overflow + * (or underflow) the data type required by @p cls. + * @retval non-zero If serialization otherwise fails, a regular unix error + * code will be returned. + */ +int +bhnd_nvram_data_serialize(bhnd_nvram_data_class *cls, + bhnd_nvram_plist *props, bhnd_nvram_plist *options, void *outp, + size_t *olen) +{ + return (cls->op_serialize(cls, props, options, outp, olen)); +} + +/** * Probe to see if this NVRAM data class class supports the data mapped by the * given I/O context, returning a BHND_NVRAM_DATA_PROBE probe result. * @@ -293,51 +341,6 @@ } /** - * Compute the size of the serialized form of @p nv. - * - * Serialization may be performed via bhnd_nvram_data_serialize(). - * - * @param nv The NVRAM data to be queried. - * @param[out] len On success, will be set to the computed size. - * - * @retval 0 success - * @retval non-zero if computing the serialized size otherwise fails, a - * regular unix error code will be returned. - */ -int -bhnd_nvram_data_size(struct bhnd_nvram_data *nv, size_t *len) -{ - return (nv->cls->op_size(nv, len)); -} - -/** - * Serialize the NVRAM data to @p buf, using the NVRAM data class' native - * format. - * - * The resulting serialization may be reparsed with @p nv's BHND NVRAM data - * class. - * - * @param nv The NVRAM data to be serialized. - * @param[out] buf On success, the serialed NVRAM data will be - * written to this buffer. This argment may be - * NULL if the value is not desired. - * @param[in,out] len The capacity of @p buf. On success, will be set - * to the actual length of the serialized data. - * - * @retval 0 success - * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too - * small to hold the serialized data. - * @retval non-zero If serialization otherwise fails, a regular unix error - * code will be returned. - */ -int -bhnd_nvram_data_serialize(struct bhnd_nvram_data *nv, - void *buf, size_t *len) -{ - return (nv->cls->op_serialize(nv, buf, len)); -} - -/** * Return the capability flags (@see BHND_NVRAM_DATA_CAP_*) for @p nv. * * @param nv The NVRAM data to be queried. Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c @@ -129,7 +129,8 @@ size_t count; /**< total variable count */ }; -BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", sizeof(struct bhnd_nvram_bcm)) +BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS, + sizeof(struct bhnd_nvram_bcm)) static int bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io) @@ -146,6 +147,190 @@ return (BHND_NVRAM_DATA_PROBE_DEFAULT); } +static int +bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, size_t *olen) +{ + struct bhnd_nvram_bcmhdr hdr; + bhnd_nvram_prop *prop; + size_t limit, nbytes; + uint32_t sdram_ncdl; + uint16_t sdram_init, sdram_cfg, sdram_refresh; + uint8_t bcm_ver, crc8; + int error; + + /* Determine output byte limit */ + if (outp != NULL) + limit = *olen; + else + limit = 0; + + /* Fetch required header variables */ +#define PROPS_GET_HDRVAR(_name, _dest, _type) do { \ + const char *name = BCM_NVRAM_ ## _name ## _VAR; \ + if (!bhnd_nvram_plist_contains(props, name)) { \ + BHND_NV_LOG("missing required property: %s\n", \ + name); \ + return (EFTYPE); \ + } \ + \ + error = bhnd_nvram_plist_get_encoded(props, name, \ + (_dest), sizeof(*(_dest)), \ + BHND_NVRAM_TYPE_ ##_type); \ + if (error) { \ + BHND_NV_LOG("error reading required header " \ + "%s property: %d\n", name, error); \ + return (EFTYPE); \ + } \ +} while (0) + + PROPS_GET_HDRVAR(SDRAM_NCDL, &sdram_ncdl, UINT32); + PROPS_GET_HDRVAR(CFG0_SDRAM_INIT, &sdram_init, UINT16); + PROPS_GET_HDRVAR(CFG1_SDRAM_CFG, &sdram_cfg, UINT16); + PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH, &sdram_refresh, UINT16); + +#undef PROPS_GET_HDRVAR + + /* Fetch BCM nvram version from options */ + if (options != NULL && + bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION)) + { + error = bhnd_nvram_plist_get_uint8(options, + BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver); + if (error) { + BHND_NV_LOG("error reading %s uint8 option value: %d\n", + BCM_NVRAM_ENCODE_OPT_VERSION, error); + return (EINVAL); + } + } else { + bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT; + } + + /* Construct our header */ + hdr = (struct bhnd_nvram_bcmhdr) { + .magic = htole32(BCM_NVRAM_MAGIC), + .size = 0, + .cfg0 = 0, + .cfg1 = 0, + .sdram_ncdl = htole32(sdram_ncdl) + }; + + hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0); + hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver); + hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT, + htole16(sdram_init)); + + hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG, + htole16(sdram_cfg)); + hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH, + htole16(sdram_refresh)); + + /* Write the header */ + nbytes = sizeof(hdr); + if (limit >= nbytes) + memcpy(outp, &hdr, sizeof(hdr)); + + /* Write all properties */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { + const char *name; + char *p; + size_t prop_limit; + size_t name_len, value_len; + + if (outp == NULL || limit < nbytes) { + p = NULL; + prop_limit = 0; + } else { + p = ((char *)outp) + nbytes; + prop_limit = limit - nbytes; + } + + /* Fetch and write name + '=' to output */ + name = bhnd_nvram_prop_name(prop); + name_len = strlen(name) + 1; + + if (prop_limit > name_len) { + memcpy(p, name, name_len - 1); + p[name_len - 1] = '='; + + prop_limit -= name_len; + p += name_len; + } else { + prop_limit = 0; + p = NULL; + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < name_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += name_len; + + /* Attempt to write NUL-terminated value to output */ + value_len = prop_limit; + error = bhnd_nvram_prop_encode(prop, p, &value_len, + BHND_NVRAM_TYPE_STRING); + + /* If encoding failed for any reason other than ENOMEM (which + * we'll detect and report after encoding all properties), + * return immediately */ + if (error && error != ENOMEM) { + BHND_NV_LOG("error serializing %s to required type " + "%s: %d\n", name, + bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), + error); + return (error); + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < value_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += value_len; + } + + /* Write terminating '\0' */ + if (limit > nbytes) + *((char *)outp + nbytes) = '\0'; + + if (nbytes == SIZE_MAX) + return (EFTYPE); /* would overflow size_t */ + else + nbytes++; + + /* Update header length; this must fit within the header's 32-bit size + * field */ + if (nbytes <= UINT32_MAX) { + hdr.size = (uint32_t)nbytes; + } else { + BHND_NV_LOG("size %zu exceeds maximum supported size of %u " + "bytes\n", nbytes, UINT32_MAX); + return (EFTYPE); + } + + /* Provide required length */ + *olen = nbytes; + if (limit < *olen) { + if (outp == NULL) + return (0); + + return (ENOMEM); + } + + /* Calculate the CRC value */ + BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size")); + crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP, + nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); + + /* Update CRC and write the finalized header */ + BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size")); + hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8); + memcpy(outp, &hdr, sizeof(hdr)); + + return (0); +} + /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * @@ -411,127 +596,6 @@ return (bcm->opts); } -static int -bhnd_nvram_bcm_size(struct bhnd_nvram_data *nv, size_t *size) -{ - return (bhnd_nvram_bcm_serialize(nv, NULL, size)); -} - -static int -bhnd_nvram_bcm_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) -{ - struct bhnd_nvram_bcm *bcm; - struct bhnd_nvram_bcmhdr hdr; - void *cookiep; - const char *name; - size_t nbytes, limit; - uint8_t crc; - int error; - - bcm = (struct bhnd_nvram_bcm *)nv; - nbytes = 0; - - /* Save the output buffer limit */ - if (buf == NULL) - limit = 0; - else - limit = *len; - - /* Reserve space for the NVRAM header */ - nbytes += sizeof(struct bhnd_nvram_bcmhdr); - - /* Write all variables to the output buffer */ - cookiep = NULL; - while ((name = bhnd_nvram_data_next(nv, &cookiep))) { - uint8_t *outp; - size_t olen; - size_t name_len, val_len; - - if (limit > nbytes) { - outp = (uint8_t *)buf + nbytes; - olen = limit - nbytes; - } else { - outp = NULL; - olen = 0; - } - - /* Determine length of variable name */ - name_len = strlen(name) + 1; - - /* Write the variable name and '=' delimiter */ - if (olen >= name_len) { - /* Copy name */ - memcpy(outp, name, name_len - 1); - - /* Append '=' */ - *(outp + name_len - 1) = '='; - } - - /* Adjust byte counts */ - if (SIZE_MAX - name_len < nbytes) - return (ERANGE); - - nbytes += name_len; - - /* Reposition output */ - if (limit > nbytes) { - outp = (uint8_t *)buf + nbytes; - olen = limit - nbytes; - } else { - outp = NULL; - olen = 0; - } - - /* Coerce to NUL-terminated C string, writing to the output - * buffer (or just calculating the length if outp is NULL) */ - val_len = olen; - error = bhnd_nvram_data_getvar(nv, cookiep, outp, &val_len, - BHND_NVRAM_TYPE_STRING); - - if (error && error != ENOMEM) - return (error); - - /* Adjust byte counts */ - if (SIZE_MAX - val_len < nbytes) - return (ERANGE); - - nbytes += val_len; - } - - /* Write terminating NUL */ - if (nbytes < limit) - *((uint8_t *)buf + nbytes) = '\0'; - nbytes++; - - /* Provide actual size */ - *len = nbytes; - if (buf == NULL || nbytes > limit) { - if (buf != NULL) - return (ENOMEM); - - return (0); - } - - /* Fetch current NVRAM header */ - if ((error = bhnd_nvram_io_read(bcm->data, 0x0, &hdr, sizeof(hdr)))) - return (error); - - /* Update values covered by CRC and write to output buffer */ - hdr.size = htole32(*len); - memcpy(buf, &hdr, sizeof(hdr)); - - /* Calculate new CRC */ - crc = bhnd_nvram_crc8((uint8_t *)buf + BCM_NVRAM_CRC_SKIP, - *len - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL); - - /* Update header with valid CRC */ - hdr.cfg0 &= ~BCM_NVRAM_CFG0_CRC_MASK; - hdr.cfg0 |= (crc << BCM_NVRAM_CFG0_CRC_SHIFT); - memcpy(buf, &hdr, sizeof(hdr)); - - return (0); -} - static uint32_t bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv) { Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmraw.c @@ -72,7 +72,7 @@ }; BHND_NVRAM_DATA_CLASS_DEFN(bcmraw, "Broadcom (RAW)", - sizeof(struct bhnd_nvram_bcmraw)) + BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_bcmraw)) static int bhnd_nvram_bcmraw_probe(struct bhnd_nvram_io *io) @@ -132,6 +132,103 @@ return (BHND_NVRAM_DATA_PROBE_MAYBE + 1); } +static int +bhnd_nvram_bcmraw_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, size_t *olen) +{ + bhnd_nvram_prop *prop; + size_t limit, nbytes; + int error; + + /* Determine output byte limit */ + if (outp != NULL) + limit = *olen; + else + limit = 0; + + nbytes = 0; + + /* Write all properties */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { + const char *name; + char *p; + size_t prop_limit; + size_t name_len, value_len; + + if (outp == NULL || limit < nbytes) { + p = NULL; + prop_limit = 0; + } else { + p = ((char *)outp) + nbytes; + prop_limit = limit - nbytes; + } + + /* Fetch and write name + '=' to output */ + name = bhnd_nvram_prop_name(prop); + name_len = strlen(name) + 1; + + if (prop_limit > name_len) { + memcpy(p, name, name_len - 1); + p[name_len - 1] = '='; + + prop_limit -= name_len; + p += name_len; + } else { + prop_limit = 0; + p = NULL; + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < name_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += name_len; + + /* Attempt to write NUL-terminated value to output */ + value_len = prop_limit; + error = bhnd_nvram_prop_encode(prop, p, &value_len, + BHND_NVRAM_TYPE_STRING); + + /* If encoding failed for any reason other than ENOMEM (which + * we'll detect and report after encoding all properties), + * return immediately */ + if (error && error != ENOMEM) { + BHND_NV_LOG("error serializing %s to required type " + "%s: %d\n", name, + bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), + error); + return (error); + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < value_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += value_len; + } + + /* Write terminating '\0' */ + if (limit > nbytes) + *((char *)outp + nbytes) = '\0'; + + if (nbytes == SIZE_MAX) + return (EFTYPE); /* would overflow size_t */ + else + nbytes++; + + /* Provide required length */ + *olen = nbytes; + if (limit < *olen) { + if (outp == NULL) + return (0); + + return (ENOMEM); + } + + return (0); +} + /** * Initialize @p bcm with the provided NVRAM data mapped by @p src. * @@ -249,85 +346,18 @@ bhnd_nv_free(bcm->data); } -static size_t -bhnd_nvram_bcmraw_count(struct bhnd_nvram_data *nv) -{ - struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv; - - return (bcm->count); -} - static bhnd_nvram_plist * bhnd_nvram_bcmraw_options(struct bhnd_nvram_data *nv) { return (NULL); } -static int -bhnd_nvram_bcmraw_size(struct bhnd_nvram_data *nv, size_t *size) -{ - return (bhnd_nvram_bcmraw_serialize(nv, NULL, size)); -} - -static int -bhnd_nvram_bcmraw_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) +static size_t +bhnd_nvram_bcmraw_count(struct bhnd_nvram_data *nv) { - struct bhnd_nvram_bcmraw *bcm; - char * const p = (char *)buf; - size_t limit; - size_t offset; - - bcm = (struct bhnd_nvram_bcmraw *)nv; - - /* Save the output buffer limit */ - if (buf == NULL) - limit = 0; - else - limit = *len; - - /* The serialized form will be exactly the length - * of our backing buffer representation */ - *len = bcm->size; - - /* Skip serialization if not requested, or report ENOMEM if - * buffer is too small */ - if (buf == NULL) { - return (0); - } else if (*len > limit) { - return (ENOMEM); - } - - /* Write all variables to the output buffer */ - memcpy(buf, bcm->data, *len); - - /* Rewrite all '\0' delimiters back to '=' */ - offset = 0; - while (offset < bcm->size) { - size_t name_len, value_len; - - name_len = strlen(p + offset); - - /* EOF? */ - if (name_len == 0) { - BHND_NV_ASSERT(*(p + offset) == '\0', - ("no NUL terminator")); - - offset++; - break; - } - - /* Rewrite 'name\0' to 'name=' */ - offset += name_len; - BHND_NV_ASSERT(*(p + offset) == '\0', ("incorrect offset")); - - *(p + offset) = '='; - offset++; - - value_len = strlen(p + offset); - offset += value_len + 1; - } + struct bhnd_nvram_bcmraw *bcm = (struct bhnd_nvram_bcmraw *)nv; - return (0); + return (bcm->count); } static uint32_t Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmreg.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmreg.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_bcmreg.h @@ -32,9 +32,13 @@ #ifndef _BHND_NVRAM_BHND_NVRAM_BCMREG_H_ #define _BHND_NVRAM_BHND_NVRAM_BCMREG_H_ -#define BCM_NVRAM_GET_BITS(_value, _field) \ +#define BCM_NVRAM_GET_BITS(_value, _field) \ ((_value & _field ## _MASK) >> _field ## _SHIFT) +#define BCM_NVRAM_SET_BITS(_value, _field, _bits) \ + ((_value & ~(_field ## _MASK)) | \ + (((_bits) << _field ## _SHIFT) & _field ## _MASK)) + /* BCM NVRAM header fields */ #define BCM_NVRAM_MAGIC 0x48534C46 /* 'FLSH' */ #define BCM_NVRAM_VERSION 1 @@ -45,6 +49,7 @@ #define BCM_NVRAM_CFG0_CRC_SHIFT 0 #define BCM_NVRAM_CFG0_VER_MASK 0x0000FF00 #define BCM_NVRAM_CFG0_VER_SHIFT 8 +#define BCM_NVRAM_CFG0_VER_DEFAULT 1 /* default version */ #define BCM_NVRAM_CFG0_SDRAM_INIT_FIELD cfg0 #define BCM_NVRAM_CFG0_SDRAM_INIT_MASK 0xFFFF0000 Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c @@ -69,7 +69,7 @@ }; BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text", - sizeof(struct bhnd_nvram_btxt)) + BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt)) /** Minimal identification header */ union bhnd_nvram_btxt_ident { @@ -124,6 +124,100 @@ return (BHND_NVRAM_DATA_PROBE_MAYBE); } +static int +bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, size_t *olen) +{ + bhnd_nvram_prop *prop; + size_t limit, nbytes; + int error; + + /* Determine output byte limit */ + if (outp != NULL) + limit = *olen; + else + limit = 0; + + nbytes = 0; + + /* Write all properties */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { + const char *name; + char *p; + size_t prop_limit; + size_t name_len, value_len; + + if (outp == NULL || limit < nbytes) { + p = NULL; + prop_limit = 0; + } else { + p = ((char *)outp) + nbytes; + prop_limit = limit - nbytes; + } + + /* Fetch and write 'name=' to output */ + name = bhnd_nvram_prop_name(prop); + name_len = strlen(name) + 1; + + if (prop_limit > name_len) { + memcpy(p, name, name_len - 1); + p[name_len - 1] = '='; + + prop_limit -= name_len; + p += name_len; + } else { + prop_limit = 0; + p = NULL; + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < name_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += name_len; + + /* Write NUL-terminated value to output, rewrite NUL as + * '\n' record delimiter */ + value_len = prop_limit; + error = bhnd_nvram_prop_encode(prop, p, &value_len, + BHND_NVRAM_TYPE_STRING); + if (p != NULL && error == 0) { + /* Replace trailing '\0' with newline */ + BHND_NV_ASSERT(value_len > 0, ("string length missing " + "minimum required trailing NUL")); + + *(p + (value_len - 1)) = '\n'; + } else if (error && error != ENOMEM) { + /* If encoding failed for any reason other than ENOMEM + * (which we'll detect and report after encoding all + * properties), return immediately */ + BHND_NV_LOG("error serializing %s to required type " + "%s: %d\n", name, + bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), + error); + return (error); + } + + /* Advance byte count */ + if (SIZE_MAX - nbytes < value_len) + return (EFTYPE); /* would overflow size_t */ + + nbytes += value_len; + } + + /* Provide required length */ + *olen = nbytes; + if (limit < *olen) { + if (outp == NULL) + return (0); + + return (ENOMEM); + } + + return (0); +} + /** * Initialize @p btxt with the provided board text data mapped by @p src. * @@ -261,52 +355,6 @@ return (NULL); } -static int -bhnd_nvram_btxt_size(struct bhnd_nvram_data *nv, size_t *size) -{ - struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv; - - /* The serialized form will be identical in length - * to our backing buffer representation */ - *size = bhnd_nvram_io_getsize(btxt->data); - return (0); -} - -static int -bhnd_nvram_btxt_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) -{ - struct bhnd_nvram_btxt *btxt; - size_t limit; - int error; - - btxt = (struct bhnd_nvram_btxt *)nv; - - limit = *len; - - /* Provide actual output size */ - if ((error = bhnd_nvram_data_size(nv, len))) - return (error); - - if (buf == NULL) { - return (0); - } else if (limit < *len) { - return (ENOMEM); - } - - /* Copy our internal representation to the output buffer */ - if ((error = bhnd_nvram_io_read(btxt->data, 0x0, buf, *len))) - return (error); - - /* Restore the original key=value format, rewriting all '\0' - * key\0value delimiters back to '=' */ - for (char *p = buf; (size_t)(p - (char *)buf) < *len; p++) { - if (*p == '\0') - *p = '='; - } - - return (0); -} - static uint32_t bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv) { Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c @@ -49,8 +49,9 @@ #include #endif /* _KERNEL */ -#include "bhnd_nvram_private.h" +#include "bhnd_nvram_map.h" +#include "bhnd_nvram_private.h" #include "bhnd_nvram_datavar.h" #include "bhnd_nvram_data_spromvar.h" @@ -62,44 +63,45 @@ * 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 const bhnd_sprom_layout *bhnd_nvram_sprom_get_layout(uint8_t sromrev); + +static int bhnd_nvram_sprom_ident( + struct bhnd_nvram_io *io, + const bhnd_sprom_layout **ident, + struct bhnd_nvram_io **shadow); + +static int bhnd_nvram_sprom_write_var( + bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry, + bhnd_nvram_val *value, + struct bhnd_nvram_io *io); + +static int bhnd_nvram_sprom_write_offset( + const struct bhnd_nvram_vardefn *var, + struct bhnd_nvram_io *data, + bhnd_nvram_type type, size_t offset, + uint32_t mask, int8_t shift, + uint32_t value); + +static int bhnd_nvram_sprom_read_offset( + const struct bhnd_nvram_vardefn *var, + struct bhnd_nvram_io *data, + bhnd_nvram_type type, size_t offset, + uint32_t mask, int8_t shift, + uint32_t *value); -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__) +static bool bhnd_sprom_is_external_immutable( + const char *name); -#define SPROM_COOKIE_TO_NVRAM(_cookie) \ - bhnd_nvram_get_vardefn(((struct sprom_opcode_idx *)_cookie)->vid) +BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM", + BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_sprom)) + +#define SPROM_COOKIE_TO_VID(_cookie) \ + (((struct bhnd_sprom_opcode_idx_entry *)(_cookie))->vid) + +#define SPROM_COOKIE_TO_NVRAM_VAR(_cookie) \ + bhnd_nvram_get_vardefn(SPROM_COOKIE_TO_VID(_cookie)) /** * Read the magic value from @p io, and verify that it matches @@ -118,7 +120,7 @@ */ static int bhnd_nvram_sprom_check_magic(struct bhnd_nvram_io *io, - const struct bhnd_sprom_layout *layout, uint16_t *magic) + const bhnd_sprom_layout *layout, uint16_t *magic) { int error; @@ -162,7 +164,7 @@ */ static int bhnd_nvram_sprom_ident(struct bhnd_nvram_io *io, - const struct bhnd_sprom_layout **ident, struct bhnd_nvram_io **shadow) + const bhnd_sprom_layout **ident, struct bhnd_nvram_io **shadow) { struct bhnd_nvram_io *buf; uint8_t crc; @@ -185,13 +187,13 @@ /* 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; + const 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); @@ -295,9 +297,9 @@ static int bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io) { - const struct bhnd_sprom_layout *layout; - struct bhnd_nvram_io *shadow; - int error; + const 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))) @@ -309,117 +311,435 @@ return (BHND_NVRAM_DATA_PROBE_DEFAULT); } + +/** + * Return the SPROM layout definition for the given @p sromrev, or NULL if + * not found. + */ +static const bhnd_sprom_layout * +bhnd_nvram_sprom_get_layout(uint8_t sromrev) +{ + /* Find matching SPROM layout definition */ + for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) { + if (bhnd_sprom_layouts[i].rev == sromrev) + return (&bhnd_sprom_layouts[i]); + } + + /* Not found */ + return (NULL); +} + +/** + * Serialize a SPROM variable. + * + * @param state The SPROM opcode state describing the layout of @p io. + * @param entry The variable's SPROM opcode index entry. + * @param value The value to encode to @p io as per @p entry. + * @param io I/O context to which @p value should be written, or NULL + * if no output should be produced. This may be used to validate + * values prior to write. + * + * @retval 0 success + * @retval EFTYPE If value coercion from @p value to the type required by + * @p entry is unsupported. + * @retval ERANGE If value coercion from @p value would overflow + * (or underflow) the type required by @p entry. + * @retval non-zero If serialization otherwise fails, a regular unix error + * code will be returned. + */ static int -bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) +bhnd_nvram_sprom_write_var(bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry, bhnd_nvram_val *value, + struct bhnd_nvram_io *io) { - struct bhnd_nvram_sprom *sp; - size_t num_vars; + const struct bhnd_nvram_vardefn *var; + uint32_t u32[BHND_SPROM_ARRAY_MAXLEN]; + bhnd_nvram_type itype, var_base_type; + size_t ipos, ilen, nelem; int error; - sp = (struct bhnd_nvram_sprom *)nv; + /* Fetch variable definition and the native element type */ + var = bhnd_nvram_get_vardefn(entry->vid); + BHND_NV_ASSERT(var != NULL, ("missing variable definition")); - /* Identify the SPROM input data */ - if ((error = bhnd_nvram_sprom_ident(io, &sp->layout, &sp->data))) - goto failed; + var_base_type = bhnd_nvram_base_type(var->type); - /* Initialize SPROM binding eval state */ - if ((error = sprom_opcode_state_init(&sp->state, sp->layout))) - goto failed; + /* Fetch the element count from the SPROM variable layout definition */ + if ((error = bhnd_sprom_opcode_parse_var(state, entry))) + return (error); - /* 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; + nelem = state->var.nelem; + BHND_NV_ASSERT(nelem <= var->nelem, ("SPROM nelem=%zu exceeds maximum " + "NVRAM nelem=%hhu", nelem, var->nelem)); + + /* Promote the data to a common 32-bit representation */ + if (bhnd_nvram_is_signed_type(var_base_type)) + itype = BHND_NVRAM_TYPE_INT32_ARRAY; + else + itype = BHND_NVRAM_TYPE_UINT32_ARRAY; - /* 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; - } + /* Calculate total size of the 32-bit promoted representation */ + if ((ilen = bhnd_nvram_value_size(NULL, 0, itype, nelem)) == 0) { + /* Variable-width types are unsupported */ + BHND_NV_LOG("invalid %s SPROM variable type %d\n", + var->name, var->type); + return (EFTYPE); + } + + /* The native representation must fit within our scratch array */ + if (ilen > sizeof(u32)) { + BHND_NV_LOG("error encoding '%s', SPROM_ARRAY_MAXLEN " + "incorrect\n", var->name); + return (EFTYPE); + } - /* We limit the SPROM index representations to the minimal - * type widths capable of covering all known layouts */ + /* Initialize our common 32-bit value representation */ + if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) { + /* No value provided; can this variable be encoded as missing + * by setting all bits to one? */ + if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) { + BHND_NV_LOG("missing required property: %s\n", + var->name); + return (EINVAL); + } - /* Save SPROM image offset */ - if (sp->state.offset > UINT16_MAX) { - SPROM_OP_BAD(&sp->state, - "cannot index large offset %u\n", sp->state.offset); + /* Set all bits */ + memset(u32, 0xFF, ilen); + } else { + bhnd_nvram_val bcm_val; + const void *var_ptr; + bhnd_nvram_type var_type, raw_type; + size_t var_len, enc_nelem; + + /* Try to coerce the value to the native variable format. */ + error = bhnd_nvram_val_convert_init(&bcm_val, var->fmt, value, + BHND_NVRAM_VAL_DYNAMIC|BHND_NVRAM_VAL_BORROW_DATA); + if (error) { + BHND_NV_LOG("error converting input type %s to %s " + "format\n", + bhnd_nvram_type_name(bhnd_nvram_val_type(value)), + bhnd_nvram_val_fmt_name(var->fmt)); + return (error); } - 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); + var_ptr = bhnd_nvram_val_bytes(&bcm_val, &var_len, &var_type); + + /* + * Promote to a common 32-bit representation. + * + * We must use the raw type to interpret the input data as its + * underlying integer representation -- otherwise, coercion + * would attempt to parse the input as its complex + * representation. + * + * For example, direct CHAR -> UINT32 coercion would attempt to + * parse the character as a decimal integer, rather than + * promoting the raw UTF8 byte value to a 32-bit value. + */ + raw_type = bhnd_nvram_raw_type(var_type); + error = bhnd_nvram_value_coerce(var_ptr, var_len, raw_type, + u32, &ilen, itype); + + /* Clean up temporary value representation */ + bhnd_nvram_val_release(&bcm_val); + + /* Report coercion failure */ + if (error) { + BHND_NV_LOG("error promoting %s to %s: %d\n", + bhnd_nvram_type_name(var_type), + bhnd_nvram_type_name(itype), error); + return (error); } - 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); + /* Encoded element count must match SPROM's definition */ + error = bhnd_nvram_value_nelem(u32, ilen, itype, &enc_nelem); + if (error) + return (error); + + if (enc_nelem != nelem) { + const char *type_name; + + type_name = bhnd_nvram_type_name(var_base_type); + BHND_NV_LOG("invalid %s property value '%s[%zu]': " + "required %s[%zu]", var->name, type_name, + enc_nelem, type_name, nelem); + return (EFTYPE); } - 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; + /* + * Seek to the start of the variable's SPROM layout definition and + * iterate over all bindings. + */ + if ((error = bhnd_sprom_opcode_seek(state, entry))) { + BHND_NV_LOG("variable seek failed: %d\n", error); + return (error); + } + + ipos = 0; + while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) { + bhnd_sprom_opcode_bind *binding; + bhnd_sprom_opcode_var *binding_var; + size_t offset; + uint32_t skip_out_bytes; + + BHND_NV_ASSERT( + state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN, + ("invalid var state")); + BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state")); + + binding_var = &state->var; + binding = &state->var.bind; + + /* Calculate output skip bytes for this binding. + * + * Skip directions are defined in terms of decoding, and + * reversed when encoding. */ + skip_out_bytes = binding->skip_in; + error = bhnd_sprom_opcode_apply_scale(state, &skip_out_bytes); + if (error) + return (error); + + /* Bind */ + offset = state->offset; + for (size_t i = 0; i < binding->count; i++) { + if (ipos >= nelem) { + BHND_NV_LOG("input skip %u positioned %zu " + "beyond nelem %zu\n", binding->skip_out, + ipos, nelem); + return (EINVAL); + } + + /* Write next offset */ + if (io != NULL) { + error = bhnd_nvram_sprom_write_offset(var, io, + binding_var->base_type, + offset, + binding_var->mask, + binding_var->shift, + u32[ipos]); + if (error) + return (error); + } + + /* Adjust output position; this was already verified to + * not overflow/underflow during SPROM opcode + * evaluation */ + if (binding->skip_in_negative) { + offset -= skip_out_bytes; + } else { + offset += skip_out_bytes; + } + + /* Skip advancing input if additional bindings are + * required to fully encode intv */ + if (binding->skip_out == 0) + continue; + + /* Advance input 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; + } } - /* Sort index by variable ID, ascending */ - qsort(sp->idx, sp->num_idx, sizeof(sp->idx[0]), sprom_sort_idx); + /* 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); return (0); +} + +static int +bhnd_nvram_sprom_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, size_t *olen) +{ + bhnd_sprom_opcode_state state; + struct bhnd_nvram_io *io; + bhnd_nvram_prop *prop; + bhnd_sprom_opcode_idx_entry *entry; + const bhnd_sprom_layout *layout; + size_t limit; + uint8_t crc; + uint8_t sromrev; + int error; + + limit = *olen; + layout = NULL; + io = NULL; + + /* Fetch sromrev property */ + if (!bhnd_nvram_plist_contains(props, BHND_NVAR_SROMREV)) { + BHND_NV_LOG("missing required property: %s\n", + BHND_NVAR_SROMREV); + return (EINVAL); + } + + error = bhnd_nvram_plist_get_uint8(props, BHND_NVAR_SROMREV, &sromrev); + if (error) { + BHND_NV_LOG("error reading sromrev property: %d\n", error); + return (EFTYPE); + } + + /* Find SPROM layout definition */ + if ((layout = bhnd_nvram_sprom_get_layout(sromrev)) == NULL) { + BHND_NV_LOG("unsupported sromrev: %hhu\n", sromrev); + return (EFTYPE); + } + + /* Provide required size to caller */ + *olen = layout->size; + if (outp == NULL) + return (0); + else if (limit < *olen) + return (ENOMEM); + + /* Initialize SPROM layout interpreter */ + if ((error = bhnd_sprom_opcode_init(&state, layout))) { + BHND_NV_LOG("error initializing opcode state: %d\n", error); + return (ENXIO); + } + + /* Check for unsupported properties */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { + const char *name; + + /* Fetch the corresponding SPROM layout index entry */ + name = bhnd_nvram_prop_name(prop); + entry = bhnd_sprom_opcode_index_find(&state, name); + if (entry == NULL) { + BHND_NV_LOG("property '%s' unsupported by sromrev " + "%hhu\n", name, layout->rev); + error = EINVAL; + goto finished; + } + } + + /* Zero-initialize output */ + memset(outp, 0, *olen); + + /* Allocate wrapping I/O context for output buffer */ + io = bhnd_nvram_ioptr_new(outp, *olen, *olen, BHND_NVRAM_IOPTR_RDWR); + if (io == NULL) { + error = ENOMEM; + goto finished; + } + + /* + * Serialize all SPROM variable data. + */ + entry = NULL; + while ((entry = bhnd_sprom_opcode_index_next(&state, entry)) != NULL) { + const struct bhnd_nvram_vardefn *var; + bhnd_nvram_val *val; + + var = bhnd_nvram_get_vardefn(entry->vid); + BHND_NV_ASSERT(var != NULL, ("missing variable definition")); + + /* Fetch prop; will be NULL if unavailable */ + prop = bhnd_nvram_plist_get_prop(props, var->name); + if (prop != NULL) { + val = bhnd_nvram_prop_val(prop); + } else { + val = BHND_NVRAM_VAL_NULL; + } + + /* Attempt to serialize the property value to the appropriate + * offset within the output buffer */ + error = bhnd_nvram_sprom_write_var(&state, entry, val, io); + if (error) { + BHND_NV_LOG("error serializing %s to required type " + "%s: %d\n", var->name, + bhnd_nvram_type_name(var->type), error); + + /* ENOMEM is reserved for signaling that the output + * buffer capacity is insufficient */ + if (error == ENOMEM) + error = EINVAL; + + goto finished; + } + } + + /* + * Write magic value, if any. + */ + if (!(layout->flags & SPROM_LAYOUT_MAGIC_NONE)) { + uint16_t magic; + + magic = htole16(layout->magic_value); + error = bhnd_nvram_io_write(io, layout->magic_offset, &magic, + sizeof(magic)); + if (error) { + BHND_NV_LOG("error writing magic value: %d\n", error); + goto finished; + } + } + + /* Calculate the CRC over all SPROM data, not including the CRC byte. */ + crc = ~bhnd_nvram_crc8(outp, layout->crc_offset, + BHND_NVRAM_CRC8_INITIAL); + + /* Write the checksum. */ + error = bhnd_nvram_io_write(io, layout->crc_offset, &crc, sizeof(crc)); + if (error) { + BHND_NV_LOG("error writing CRC value: %d\n", error); + goto finished; + } -failed: - if (sp->data != NULL) - bhnd_nvram_io_free(sp->data); + /* + * Success! + */ + error = 0; - if (sp->idx != NULL) - bhnd_nv_free(sp->idx); +finished: + bhnd_sprom_opcode_fini(&state); + + if (io != NULL) + bhnd_nvram_io_free(io); return (error); } -/* sort function for sprom_opcode_idx values */ static int -sprom_sort_idx(const void *lhs, const void *rhs) +bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io) { - const struct sprom_opcode_idx *l, *r; + struct bhnd_nvram_sprom *sp; + int error; - l = lhs; - r = rhs; + 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 = bhnd_sprom_opcode_init(&sp->state, sp->layout))) + goto failed; - if (l->vid < r->vid) - return (-1); - if (l->vid > r->vid) - return (1); return (0); + +failed: + if (sp->data != NULL) + bhnd_nvram_io_free(sp->data); + + return (error); } 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); -} -static bhnd_nvram_plist * -bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv) -{ - return (NULL); + bhnd_sprom_opcode_fini(&sp->state); + bhnd_nvram_io_free(sp->data); } size_t @@ -429,41 +749,10 @@ 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) +static bhnd_nvram_plist * +bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv) { - 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)); + return (NULL); } static uint32_t @@ -476,39 +765,17 @@ 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; + bhnd_sprom_opcode_idx_entry *entry; 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); + /* Find next index entry that is not disabled by virtue of IGNALL1 */ + entry = *cookiep; + while ((entry = bhnd_sprom_opcode_index_next(&sp->state, entry))) { + /* Update cookiep and fetch variable definition */ + *cookiep = entry; + var = SPROM_COOKIE_TO_NVRAM_VAR(*cookiep); /* We might need to parse the variable's value to determine * whether it should be treated as unset */ @@ -521,7 +788,6 @@ if (error) { BHND_NV_ASSERT(error == ENOENT, ("unexpected " "error parsing variable: %d", error)); - continue; } } @@ -534,165 +800,215 @@ 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; + bhnd_sprom_opcode_idx_entry *entry; 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)); + entry = bhnd_sprom_opcode_index_find(&sp->state, name); + return (entry); } /** - * 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); - } + * Write @p value of @p type to the SPROM @p data at @p offset, applying + * @p mask and @p shift, and OR with the existing data. + * + * @param var The NVRAM variable definition. + * @param data The SPROM data to be modified. + * @param type The type to write at @p offset. + * @param offset The data offset to be written. + * @param mask The mask to be applied to @p value after shifting. + * @param shift The shift to be applied to @p value; if positive, a left + * shift will be applied, if negative, a right shift (this is the reverse of the + * decoding behavior) + * @param value The value to be written. The parsed value will be OR'd with the + * current contents of @p data at @p offset. + */ +static int +bhnd_nvram_sprom_write_offset(const struct bhnd_nvram_vardefn *var, + struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, + uint32_t mask, int8_t shift, uint32_t value) +{ + union bhnd_nvram_sprom_storage scratch; + int error; -#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; \ +#define NV_WRITE_INT(_widen, _repr, _swap) do { \ + /* Narrow the 32-bit representation */ \ + scratch._repr[1] = (_widen)value; \ + \ + /* Shift and mask the new value */ \ + if (shift > 0) \ + scratch._repr[1] <<= shift; \ + else if (shift < 0) \ + scratch._repr[1] >>= -shift; \ + scratch._repr[1] &= mask; \ + \ + /* Swap to output byte order */ \ + scratch._repr[1] = _swap(scratch._repr[1]); \ + \ + /* Fetch the current value */ \ + error = bhnd_nvram_io_read(data, offset, \ + &scratch._repr[0], sizeof(scratch._repr[0])); \ + if (error) { \ + BHND_NV_LOG("error reading %s SPROM offset " \ + "%#zx: %d\n", var->name, offset, error); \ + return (EFTYPE); \ + } \ + \ + /* Mask and set our new value's bits in the current \ + * value */ \ + if (shift >= 0) \ + scratch._repr[0] &= ~_swap(mask << shift); \ + else if (shift < 0) \ + scratch._repr[0] &= ~_swap(mask >> (-shift)); \ + scratch._repr[0] |= scratch._repr[1]; \ + \ + /* Perform write */ \ + error = bhnd_nvram_io_write(data, offset, \ + &scratch._repr[0], sizeof(scratch._repr[0])); \ + if (error) { \ + BHND_NV_LOG("error writing %s SPROM offset " \ + "%#zx: %d\n", var->name, offset, error); \ + return (EFTYPE); \ + } \ } 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, ); + NV_WRITE_INT(uint32_t, u8, ); break; case BHND_NVRAM_TYPE_UINT16: - NV_PARSE_INT(uint16_t, u16, u32, le16toh); + NV_WRITE_INT(uint32_t, u16, htole16); break; case BHND_NVRAM_TYPE_UINT32: - NV_PARSE_INT(uint32_t, u32, u32, le32toh); + NV_WRITE_INT(uint32_t, u32, htole32); break; case BHND_NVRAM_TYPE_INT8: - NV_PARSE_INT(int8_t, s8, s32, ); + NV_WRITE_INT(int32_t, i8, ); break; case BHND_NVRAM_TYPE_INT16: - NV_PARSE_INT(int16_t, s16, s32, le16toh); + NV_WRITE_INT(int32_t, i16, htole16); break; case BHND_NVRAM_TYPE_INT32: - NV_PARSE_INT(int32_t, s32, s32, le32toh); + NV_WRITE_INT(int32_t, i32, htole32); break; case BHND_NVRAM_TYPE_CHAR: - NV_PARSE_INT(uint8_t, u8, u32, ); + NV_WRITE_INT(uint32_t, u8, ); 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); } +#undef NV_WRITE_INT return (0); } /** - * Common variable decoding; fetches and decodes variable to @p val, - * using @p storage for actual data storage. + * Read the value of @p type from the SPROM @p data at @p offset, apply @p mask + * and @p shift, and OR with the existing @p value. * - * The returned @p val instance will hold a borrowed reference to @p storage, - * and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond - * the lifetime of @p storage. - * - * The caller is responsible for releasing any allocated value state - * via bhnd_nvram_val_release(). - */ -static int -bhnd_nvram_sprom_getvar_common(struct bhnd_nvram_data *nv, void *cookiep, - union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val) -{ + * @param var The NVRAM variable definition. + * @param data The SPROM data to be decoded. + * @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(const struct bhnd_nvram_vardefn *var, + struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset, + uint32_t mask, int8_t shift, uint32_t *value) +{ + union bhnd_nvram_sprom_storage scratch; + int error; + +#define NV_PARSE_INT(_widen, _repr, _swap) do { \ + /* Perform read */ \ + error = bhnd_nvram_io_read(data, offset, \ + &scratch._repr[0], sizeof(scratch._repr[0])); \ + if (error) { \ + BHND_NV_LOG("error reading %s SPROM offset " \ + "%#zx: %d\n", var->name, offset, error); \ + return (EFTYPE); \ + } \ + \ + /* Swap to host byte order */ \ + scratch._repr[0] = _swap(scratch._repr[0]); \ + \ + /* Mask and shift the value */ \ + scratch._repr[0] &= mask; \ + if (shift > 0) { \ + scratch. _repr[0] >>= shift; \ + } else if (shift < 0) { \ + scratch. _repr[0] <<= -shift; \ + } \ + \ + /* Widen to 32-bit representation and OR with current \ + * value */ \ + (*value) |= (_widen)scratch._repr[0]; \ +} while(0) + + /* Apply mask/shift and widen to a common 32bit representation */ + switch (type) { + case BHND_NVRAM_TYPE_UINT8: + NV_PARSE_INT(uint32_t, u8, ); + break; + case BHND_NVRAM_TYPE_UINT16: + NV_PARSE_INT(uint32_t, u16, le16toh); + break; + case BHND_NVRAM_TYPE_UINT32: + NV_PARSE_INT(uint32_t, u32, le32toh); + break; + case BHND_NVRAM_TYPE_INT8: + NV_PARSE_INT(int32_t, i8, ); + break; + case BHND_NVRAM_TYPE_INT16: + NV_PARSE_INT(int32_t, i16, le16toh); + break; + case BHND_NVRAM_TYPE_INT32: + NV_PARSE_INT(int32_t, i32, le32toh); + break; + case BHND_NVRAM_TYPE_CHAR: + NV_PARSE_INT(uint32_t, u8, ); + break; + default: + BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type); + return (EFTYPE); + } +#undef NV_PARSE_INT + + return (0); +} + +/** + * Common variable decoding; fetches and decodes variable to @p val, + * using @p storage for actual data storage. + * + * The returned @p val instance will hold a borrowed reference to @p storage, + * and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond + * the lifetime of @p storage. + * + * The caller is responsible for releasing any allocated value state + * via bhnd_nvram_val_release(). + */ +static int +bhnd_nvram_sprom_getvar_common(struct bhnd_nvram_data *nv, void *cookiep, + union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val) +{ struct bhnd_nvram_sprom *sp; - struct sprom_opcode_idx *entry; + bhnd_sprom_opcode_idx_entry *entry; const struct bhnd_nvram_vardefn *var; union bhnd_nvram_sprom_storage *inp; bhnd_nvram_type var_btype; - union bhnd_nvram_sprom_intv intv; + uint32_t intv; size_t ilen, ipos, iwidth; size_t nelem; bool all_bits_set; @@ -704,7 +1020,7 @@ BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep")); /* Fetch canonical variable definition */ - var = SPROM_COOKIE_TO_NVRAM(cookiep); + var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep); BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); /* @@ -714,7 +1030,7 @@ * canonical NVRAM variable definition, but some SPROM layouts may * define a smaller element count. */ - if ((error = sprom_opcode_parse_var(&sp->state, entry))) { + if ((error = bhnd_sprom_opcode_parse_var(&sp->state, entry))) { BHND_NV_LOG("variable evaluation failed: %d\n", error); return (error); } @@ -754,25 +1070,25 @@ /* * Decode the SPROM data, iteratively decoding up to nelem values. */ - if ((error = sprom_opcode_state_seek(&sp->state, entry))) { + if ((error = bhnd_sprom_opcode_seek(&sp->state, entry))) { BHND_NV_LOG("variable seek failed: %d\n", error); return (error); } ipos = 0; - intv.u32 = 0x0; + intv = 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; + while ((error = bhnd_sprom_opcode_next_binding(&sp->state)) == 0) { + bhnd_sprom_opcode_bind *binding; + bhnd_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, @@ -791,7 +1107,8 @@ /* Calculate input skip bytes for this binding */ skip_in_bytes = binding->skip_in; - error = sprom_opcode_apply_scale(&sp->state, &skip_in_bytes); + error = bhnd_sprom_opcode_apply_scale(&sp->state, + &skip_in_bytes); if (error) return (error); @@ -800,7 +1117,7 @@ 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, + error = bhnd_nvram_sprom_read_offset(var, sp->data, binding_var->base_type, offset, binding_var->mask, @@ -822,7 +1139,7 @@ else if (binding_var->shift < 0) all1 <<= -binding_var->shift; - if ((intv.u32 & all1) != all1) + if ((intv & all1) != all1) all_bits_set = false; } @@ -855,14 +1172,13 @@ /* Perform coercion of the array element */ nbyte = iwidth; - error = bhnd_nvram_value_coerce(&intv.u32, - sizeof(intv.u32), intv_type, ptr, &nbyte, - var_btype); + error = bhnd_nvram_value_coerce(&intv, sizeof(intv), + intv_type, ptr, &nbyte, var_btype); if (error) return (error); /* Clear temporary state */ - intv.u32 = 0x0; + intv = 0x0; /* Advance output position */ if (SIZE_MAX - binding->skip_out < ipos) { @@ -897,7 +1213,7 @@ bhnd_nvram_sprom_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1, void *cookiep2) { - struct sprom_opcode_idx_entry *e1, *e2; + struct bhnd_sprom_opcode_idx_entry *e1, *e2; e1 = cookiep1; e2 = cookiep2; @@ -971,7 +1287,7 @@ BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep")); - var = SPROM_COOKIE_TO_NVRAM(cookiep); + var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep); BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep)); return (var->name); @@ -981,1094 +1297,85 @@ bhnd_nvram_sprom_filter_setvar(struct bhnd_nvram_data *nv, const char *name, bhnd_nvram_val *value, bhnd_nvram_val **result) { - // XXX TODO - return (ENXIO); -} - -static int -bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) -{ - // XXX TODO - return (ENXIO); -} - -/** - * 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) -{ + struct bhnd_nvram_sprom *sp; const struct bhnd_nvram_vardefn *var; + bhnd_sprom_opcode_idx_entry *entry; + bhnd_nvram_val *spval; int error; - BHND_NV_ASSERT(state->var_state == SPROM_OPCODE_VAR_STATE_NONE, - ("overwrite of open variable definition")); + sp = (struct bhnd_nvram_sprom *)nv; - /* Locate the variable definition */ - if ((var = bhnd_nvram_get_vardefn(vid)) == NULL) { - SPROM_OP_BAD(state, "unknown variable ID: %zu\n", vid); + /* Is this an externally immutable variable name? */ + if (bhnd_sprom_is_external_immutable(name)) return (EINVAL); - } - /* Update vid and var state */ - state->vid = vid; - state->var_state = SPROM_OPCODE_VAR_STATE_OPEN; + /* Variable must be defined in our SPROM layout */ + if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL) + return (ENOENT); - /* Initialize default variable record values */ - memset(&state->var, 0x0, sizeof(state->var)); + var = bhnd_nvram_get_vardefn(entry->vid); + BHND_NV_ASSERT(var != NULL, ("missing variable definition")); - /* Set initial base type */ - if ((error = sprom_opcode_set_type(state, var->type))) + /* Value must be convertible to the native variable type */ + error = bhnd_nvram_val_convert_new(&spval, var->fmt, value, + BHND_NVRAM_VAL_DYNAMIC); + if (error) return (error); - /* Set default array length */ - if ((error = sprom_opcode_set_nelem(state, var->nelem))) + /* Value must be encodeable by our SPROM layout */ + error = bhnd_nvram_sprom_write_var(&sp->state, entry, spval, NULL); + if (error) { + bhnd_nvram_val_release(spval); 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; + /* Success. Transfer our ownership of the converted value to the + * caller */ + *result = spval; 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) +bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name) { - 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; + struct bhnd_nvram_sprom *sp; + const struct bhnd_nvram_vardefn *var; + bhnd_sprom_opcode_idx_entry *entry; - if ((error = sprom_opcode_apply_scale(state, opval))) - return (error); + sp = (struct bhnd_nvram_sprom *)nv; - 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); + /* Is this an externally immutable variable name? */ + if (bhnd_sprom_is_external_immutable(name)) 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; + /* Variable must be defined in our SPROM layout */ + if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL) + return (ENOENT); + var = bhnd_nvram_get_vardefn(entry->vid); - case SPROM_OPCODE_VAR_STATE_DONE: - /* Previously completed variable definition. Discard variable - * state */ - return (sprom_opcode_clear_var(state)); - } + /* Variable must be capable of representing a NULL/deleted value. + * + * Since SPROM's layout is fixed, this requires IGNALL -- if + * all bits are set, an IGNALL variable is treated as unset. */ + if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) + return (EINVAL); - /* 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. + * Return true if @p name represents a special immutable variable name + * (e.g. sromrev) that cannot be updated in an SPROM existing image. * - * @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. + * @param name The name to check. */ -static int -sprom_opcode_parse_var(struct sprom_opcode_state *state, - struct sprom_opcode_idx *indexed) +static bool +bhnd_sprom_is_external_immutable(const char *name) { - uint8_t opcode; - int error; - - /* Seek to entry */ - if ((error = sprom_opcode_state_seek(state, indexed))) - return (error); + /* The layout revision is immutable and cannot be changed */ + if (strcmp(name, BHND_NVAR_SROMREV) == 0) + return (true); - /* 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); + return (false); } Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom_subr.c @@ -0,0 +1,1366 @@ +/*- + * Copyright (c) 2015-2016 Landon Fuller + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include + +#ifdef _KERNEL +#include +#include +#else /* !_KERNEL */ +#include +#include +#include +#include +#endif /* _KERNEL */ + +#include "bhnd_nvram_private.h" +#include "bhnd_nvram_data_spromvar.h" + +static int bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs); +static int bhnd_nvram_opcode_idx_vid_compare(const void *key, + const void *rhs); + +static int bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state); +static int bhnd_sprom_opcode_next_var(bhnd_sprom_opcode_state *state); + +static int bhnd_sprom_opcode_set_type(bhnd_sprom_opcode_state *state, + bhnd_nvram_type type); + +static int bhnd_sprom_opcode_set_var(bhnd_sprom_opcode_state *state, + size_t vid); +static int bhnd_sprom_opcode_clear_var(bhnd_sprom_opcode_state *state); + +static int bhnd_sprom_opcode_flush_bind(bhnd_sprom_opcode_state *state); + +static int bhnd_sprom_opcode_read_opval32(bhnd_sprom_opcode_state *state, + uint8_t type, uint32_t *opval); + +static int bhnd_sprom_opcode_step(bhnd_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__) + +/** + * 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. + */ +int +bhnd_sprom_opcode_init(bhnd_sprom_opcode_state *state, + const struct bhnd_sprom_layout *layout) +{ + bhnd_sprom_opcode_idx_entry *idx; + size_t num_vars, num_idx; + int error; + + idx = NULL; + + state->layout = layout; + state->idx = NULL; + state->num_idx = 0; + + /* Initialize interpretation state */ + if ((error = bhnd_sprom_opcode_reset(state))) + return (error); + + /* Allocate and populate our opcode index */ + num_idx = state->layout->num_vars; + idx = bhnd_nv_calloc(num_idx, sizeof(*idx)); + if (idx == NULL) + return (ENOMEM); + + for (num_vars = 0; num_vars < num_idx; num_vars++) { + size_t opcodes; + + /* Seek to next entry */ + if ((error = bhnd_sprom_opcode_next_var(state))) { + SPROM_OP_BAD(state, "error reading expected variable " + "entry: %d\n", error); + bhnd_nv_free(idx); + return (error); + } + + /* We limit the SPROM index representations to the minimal + * type widths capable of covering all known layouts */ + + /* Save SPROM image offset */ + if (state->offset > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large offset %u\n", + state->offset); + bhnd_nv_free(idx); + return (ENXIO); + } + idx[num_vars].offset = state->offset; + + /* Save current variable ID */ + if (state->vid > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large vid %zu\n", + state->vid); + bhnd_nv_free(idx); + return (ENXIO); + } + idx[num_vars].vid = state->vid; + + /* Save opcode position */ + opcodes = (state->input - state->layout->bindings); + if (opcodes > UINT16_MAX) { + SPROM_OP_BAD(state, "cannot index large opcode offset " + "%zu\n", opcodes); + bhnd_nv_free(idx); + return (ENXIO); + } + idx[num_vars].opcodes = opcodes; + } + + /* Should have reached end of binding table; next read must return + * ENOENT */ + if ((error = bhnd_sprom_opcode_next_var(state)) != ENOENT) { + BHND_NV_LOG("expected EOF parsing binding table: %d\n", error); + bhnd_nv_free(idx); + return (ENXIO); + } + + /* Reset interpretation state */ + if ((error = bhnd_sprom_opcode_reset(state))) { + bhnd_nv_free(idx); + return (error); + } + + /* Make index available to opcode state evaluation */ + qsort(idx, num_idx, sizeof(idx[0]), bhnd_sprom_opcode_sort_idx); + + state->idx = idx; + state->num_idx = num_idx; + + 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 +bhnd_sprom_opcode_reset(bhnd_sprom_opcode_state *state) +{ + memset(&state->var, 0, sizeof(state->var)); + + state->input = state->layout->bindings; + state->offset = 0; + state->vid = 0; + state->var_state = SPROM_OPCODE_VAR_STATE_NONE; + bit_set(state->revs, state->layout->rev); + + return (0); +} + +/** + * Free any resources associated with @p state. + * + * @param state An opcode state previously successfully initialized with + * bhnd_sprom_opcode_init(). + */ +void +bhnd_sprom_opcode_fini(bhnd_sprom_opcode_state *state) +{ + bhnd_nv_free(state->idx); +} + + +/** + * Sort function used to prepare our index for querying; sorts + * bhnd_sprom_opcode_idx_entry values by variable ID, ascending. + */ +static int +bhnd_sprom_opcode_sort_idx(const void *lhs, const void *rhs) +{ + const bhnd_sprom_opcode_idx_entry *l, *r; + + l = lhs; + r = rhs; + + if (l->vid < r->vid) + return (-1); + if (l->vid > r->vid) + return (1); + return (0); +} + +/** + * Binary search comparison function used by bhnd_sprom_opcode_index_find(); + * searches bhnd_sprom_opcode_idx_entry values by variable ID, ascending. + */ +static int +bhnd_nvram_opcode_idx_vid_compare(const void *key, const void *rhs) +{ + const bhnd_sprom_opcode_idx_entry *entry; + size_t vid; + + vid = *(const size_t *)key; + entry = rhs; + + if (vid < entry->vid) + return (-1); + if (vid > entry->vid) + return (1); + + return (0); +} + +/** + * Locate an index entry for the variable with @p name, or NULL if not found. + * + * @param state The opcode state to be queried. + * @param name The name to search for. + * + * @retval non-NULL If @p name is found, its index entry value will be + * returned. + * @retval NULL If @p name is not found. + */ +bhnd_sprom_opcode_idx_entry * +bhnd_sprom_opcode_index_find(bhnd_sprom_opcode_state *state, const char *name) +{ + const struct bhnd_nvram_vardefn *var; + size_t vid; + + /* 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, state->idx, state->num_idx, sizeof(state->idx[0]), + bhnd_nvram_opcode_idx_vid_compare)); +} + + +/** + * Iterate over all index entries in @p state. + * + * @param state The opcode state to be iterated. + * @param[in,out] prev An entry previously returned by + * bhnd_sprom_opcode_index_next(), or a NULL value + * to begin iteration. + * + * @return Returns the next index entry name, or NULL if all entries have + * been iterated. + */ +bhnd_sprom_opcode_idx_entry * +bhnd_sprom_opcode_index_next(bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *prev) +{ + size_t idxpos; + + /* Get next index position */ + if (prev == NULL) { + idxpos = 0; + } else { + /* Determine current position */ + idxpos = (size_t)(prev - state->idx); + BHND_NV_ASSERT(idxpos < state->num_idx, + ("invalid index %zu", idxpos)); + + /* Advance to next entry */ + idxpos++; + } + + /* Check for EOF */ + if (idxpos == state->num_idx) + return (NULL); + + return (&state->idx[idxpos]); +} + +/** + * Reset SPROM opcode evaluation state and seek to the @p entry's position. + * + * @param state The opcode state to be reset. + * @param entry The indexed entry to which we'll seek the opcode state. + */ +int +bhnd_sprom_opcode_seek(bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry) +{ + int error; + + BHND_NV_ASSERT(entry->opcodes < state->layout->bindings_size, + ("index entry references invalid opcode position")); + + /* Reset state */ + if ((error = bhnd_sprom_opcode_reset(state))) + return (error); + + /* Seek to the indexed sprom opcode offset */ + state->input = state->layout->bindings + entry->opcodes; + + /* Restore the indexed sprom data offset and VID */ + state->offset = entry->offset; + + /* Restore the indexed sprom variable ID */ + if ((error = bhnd_sprom_opcode_set_var(state, entry->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 +bhnd_sprom_opcode_set_revs(bhnd_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 = bhnd_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 +bhnd_sprom_opcode_set_mask(bhnd_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 +bhnd_sprom_opcode_set_shift(bhnd_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 +bhnd_sprom_opcode_set_bind(bhnd_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 = bhnd_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 +bhnd_sprom_opcode_flush_bind(bhnd_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 = bhnd_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 +bhnd_sprom_opcode_set_type(bhnd_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 +bhnd_sprom_opcode_clear_var(bhnd_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 +bhnd_sprom_opcode_set_nelem(bhnd_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 +bhnd_sprom_opcode_set_var(bhnd_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 = bhnd_sprom_opcode_set_type(state, var->type))) + return (error); + + /* Set default array length */ + if ((error = bhnd_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 +bhnd_sprom_opcode_end_var(bhnd_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. + */ +int +bhnd_sprom_opcode_apply_scale(bhnd_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 +bhnd_sprom_opcode_read_opval32(bhnd_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 = bhnd_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 +bhnd_sprom_opcode_matches_layout_rev(bhnd_sprom_opcode_state *state) +{ + return (bit_test(state->revs, state->layout->rev)); +} + +/** + * When evaluating @p state and @p opcode, rewrite @p opcode based on the + * current evaluation state. + * + * This allows the insertion of implicit opcodes into interpretation of the + * opcode stream. + * + * If @p opcode is rewritten, it should be returned from + * bhnd_sprom_opcode_step() instead of the opcode parsed from @p state's opcode + * stream. + * + * If @p opcode remains unmodified, then bhnd_sprom_opcode_step() should + * proceed to standard evaluation. + */ +static int +bhnd_sprom_opcode_rewrite_opcode(bhnd_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 = bhnd_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 = bhnd_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 (bhnd_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 +bhnd_sprom_opcode_step(bhnd_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 = bhnd_sprom_opcode_flush_bind(state))) + return (error); + + /* Insert local opcode based on current state? */ + rewrite = *opcode; + if ((error = bhnd_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 (!bhnd_sprom_opcode_matches_layout_rev(state)) + continue; + + return (0); + } + + /* Advance input */ + state->input++; + + switch (op) { + case SPROM_OPCODE_VAR_IMM: + if ((error = bhnd_sprom_opcode_set_var(state, immd))) + return (error); + break; + + case SPROM_OPCODE_VAR_REL_IMM: + error = bhnd_sprom_opcode_set_var(state, + state->vid + immd); + if (error) + return (error); + break; + + case SPROM_OPCODE_VAR: + error = bhnd_sprom_opcode_read_opval32(state, immd, + &val); + if (error) + return (error); + + if ((error = bhnd_sprom_opcode_set_var(state, val))) + return (error); + + break; + + case SPROM_OPCODE_VAR_END: + if ((error = bhnd_sprom_opcode_end_var(state))) + return (error); + break; + + case SPROM_OPCODE_NELEM: + immd = *state->input; + if ((error = bhnd_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 = bhnd_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 = bhnd_sprom_opcode_set_bind(state, count, + skip_in, skip_in_negative, skip_out); + if (error) + return (error); + break; + } + + case SPROM_OPCODE_REV_IMM: + error = bhnd_sprom_opcode_set_revs(state, immd, immd); + if (error) + 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 = bhnd_sprom_opcode_set_revs(state, rstart, rend); + if (error) + return (error); + + /* Advance input */ + state->input++; + break; + } + case SPROM_OPCODE_MASK_IMM: + if ((error = bhnd_sprom_opcode_set_mask(state, immd))) + return (error); + break; + + case SPROM_OPCODE_MASK: + error = bhnd_sprom_opcode_read_opval32(state, immd, + &val); + if (error) + return (error); + + if ((error = bhnd_sprom_opcode_set_mask(state, val))) + return (error); + break; + + case SPROM_OPCODE_SHIFT_IMM: + error = bhnd_sprom_opcode_set_shift(state, immd * 2); + if (error) + 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 = bhnd_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 */ + error = bhnd_sprom_opcode_apply_scale(state, &val); + if (error) + 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 = bhnd_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 = bhnd_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 (bhnd_sprom_opcode_matches_layout_rev(state)) + return (0); + } + + /* End of opcode stream */ + return (ENOENT); +} + +/** + * Reset SPROM opcode evaluation state, seek to the @p entry's position, + * and perform complete evaluation of the variable's opcodes. + * + * @param state The opcode state to be to be evaluated. + * @param entry The indexed variable location. + * + * @retval 0 success + * @retval non-zero If evaluation fails, a regular unix error code will be + * returned. + */ +int +bhnd_sprom_opcode_parse_var(bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry) +{ + uint8_t opcode; + int error; + + /* Seek to entry */ + if ((error = bhnd_sprom_opcode_seek(state, entry))) + return (error); + + /* Parse full variable definition */ + while ((error = bhnd_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 +bhnd_sprom_opcode_next_var(bhnd_sprom_opcode_state *state) +{ + uint8_t opcode; + int error; + + /* Step until we hit a variable opcode */ + while ((error = bhnd_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. + */ +int +bhnd_sprom_opcode_next_binding(bhnd_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 = bhnd_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); +} Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_spromvar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_spromvar.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_spromvar.h @@ -44,12 +44,43 @@ #include "bhnd_nvram_io.h" /** The maximum number of array elements encoded in a single SPROM variable */ -#define SPROM_ARRAY_MAXLEN 12 +#define BHND_SPROM_ARRAY_MAXLEN 12 + +typedef struct bhnd_sprom_opcode_state bhnd_sprom_opcode_state; +typedef struct bhnd_sprom_opcode_bind bhnd_sprom_opcode_bind; +typedef struct bhnd_sprom_opcode_var bhnd_sprom_opcode_var; +typedef struct bhnd_sprom_opcode_idx_entry bhnd_sprom_opcode_idx_entry; + +int bhnd_sprom_opcode_init( + bhnd_sprom_opcode_state *state, + const bhnd_sprom_layout *layout); +void bhnd_sprom_opcode_fini( + bhnd_sprom_opcode_state *state); + +bhnd_sprom_opcode_idx_entry *bhnd_sprom_opcode_index_find( + bhnd_sprom_opcode_state *state, + const char *name); +bhnd_sprom_opcode_idx_entry *bhnd_sprom_opcode_index_next( + bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *prev); + +int bhnd_sprom_opcode_parse_var( + bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry); + +int bhnd_sprom_opcode_seek( + bhnd_sprom_opcode_state *state, + bhnd_sprom_opcode_idx_entry *entry); +int bhnd_sprom_opcode_next_binding( + bhnd_sprom_opcode_state *state); +int bhnd_sprom_opcode_apply_scale( + bhnd_sprom_opcode_state *state, + uint32_t *value); /** * SPROM opcode per-bind evaluation state. */ -struct sprom_opcode_bind { +struct bhnd_sprom_opcode_bind { uint8_t count; uint32_t skip_in; /**< input element skips */ bool skip_in_negative; /**< skip_in should be subtracted */ @@ -59,15 +90,15 @@ /** * SPROM opcode per-variable evaluation state. */ -struct sprom_opcode_var { - uint8_t nelem; /**< variable array length */ - uint32_t mask; /**< current bind input mask */ - int8_t shift; /**< current bind input shift */ - bhnd_nvram_type base_type; /**< current bind input type */ - uint32_t scale; /**< current scale to apply to scaled encodings */ - struct sprom_opcode_bind bind; /**< current bind state */ - bool have_bind; /**< if bind state is defined */ - size_t bind_total; /**< total count of bind operations performed */ +struct bhnd_sprom_opcode_var { + uint8_t nelem; /**< variable array length */ + uint32_t mask; /**< current bind input mask */ + int8_t shift; /**< current bind input shift */ + bhnd_nvram_type base_type; /**< current bind input type */ + uint32_t scale; /**< current scale to apply to scaled encodings */ + bhnd_sprom_opcode_bind bind; /**< current bind state */ + bool have_bind; /**< if bind state is defined */ + size_t bind_total; /**< total count of bind operations performed */ }; /** @@ -80,13 +111,16 @@ SPROM_OPCODE_VAR_STATE_NONE = 1, /**< no variable entry available */ SPROM_OPCODE_VAR_STATE_OPEN = 2, /**< currently parsing a variable entry */ SPROM_OPCODE_VAR_STATE_DONE = 3 /**< full variable entry has been parsed */ -} sprom_opcode_var_state; +} bhnd_sprom_opcode_var_state; /** * SPROM opcode evaluation state */ -struct sprom_opcode_state { - const struct bhnd_sprom_layout *layout; /**< SPROM layout */ +struct bhnd_sprom_opcode_state { + const bhnd_sprom_layout *layout; /**< SPROM layout */ + + bhnd_sprom_opcode_idx_entry *idx; /**< variable index (NULL during initialization) */ + size_t num_idx; /**< variable index entry count */ /** Current SPROM revision range */ bitstr_t bit_decl(revs, SPROM_OP_REV_MAX); @@ -98,14 +132,14 @@ size_t vid; /**< Variable ID */ /* State reset after end of each variable definition */ - struct sprom_opcode_var var; /**< variable record (if any) */ - sprom_opcode_var_state var_state; /**< variable record state */ + bhnd_sprom_opcode_var var; /**< variable record (if any) */ + bhnd_sprom_opcode_var_state var_state; /**< variable record state */ }; /** * SPROM opcode variable index entry */ -struct sprom_opcode_idx { +struct bhnd_sprom_opcode_idx_entry { uint16_t vid; /**< SPROM variable ID */ uint16_t offset; /**< SPROM input offset */ uint16_t opcodes; /**< SPROM opcode offset */ @@ -118,33 +152,23 @@ * variable. */ union bhnd_nvram_sprom_storage { - uint8_t u8[SPROM_ARRAY_MAXLEN]; - uint16_t u16[SPROM_ARRAY_MAXLEN]; - uint32_t u32[SPROM_ARRAY_MAXLEN]; - int8_t i8[SPROM_ARRAY_MAXLEN]; - int16_t i16[SPROM_ARRAY_MAXLEN]; - int32_t i32[SPROM_ARRAY_MAXLEN]; - char ch[SPROM_ARRAY_MAXLEN]; -}; - -/** - * SPROM common integer value representation. - */ -union bhnd_nvram_sprom_intv { - uint32_t u32; - int32_t s32; + uint8_t u8[BHND_SPROM_ARRAY_MAXLEN]; + uint16_t u16[BHND_SPROM_ARRAY_MAXLEN]; + uint32_t u32[BHND_SPROM_ARRAY_MAXLEN]; + int8_t i8[BHND_SPROM_ARRAY_MAXLEN]; + int16_t i16[BHND_SPROM_ARRAY_MAXLEN]; + int32_t i32[BHND_SPROM_ARRAY_MAXLEN]; + char ch[BHND_SPROM_ARRAY_MAXLEN]; }; /** * SPROM data class instance state. */ struct bhnd_nvram_sprom { - struct bhnd_nvram_data nv; /**< common instance state */ - struct bhnd_nvram_io *data; /**< backing SPROM image */ - const struct bhnd_sprom_layout *layout; /**< layout definition */ - struct sprom_opcode_state state; /**< opcode eval state */ - struct sprom_opcode_idx *idx; /**< opcode index entries */ - size_t num_idx; /**< opcode index entry count */ + struct bhnd_nvram_data nv; /**< common instance state */ + struct bhnd_nvram_io *data; /**< backing SPROM image */ + const bhnd_sprom_layout *layout; /**< layout definition */ + bhnd_sprom_opcode_state state; /**< opcode eval state */ }; #endif /* _BHND_NVRAM_BHND_NVRAM_SPROMVAR_H_ */ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c @@ -62,7 +62,8 @@ size_t count; /**< variable count */ }; -BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", sizeof(struct bhnd_nvram_tlv)) +BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS, + sizeof(struct bhnd_nvram_tlv)) /** Minimal TLV_ENV record header */ struct bhnd_nvram_tlv_env_hdr { @@ -163,6 +164,123 @@ return (BHND_NVRAM_DATA_PROBE_DEFAULT); } +static int +bhnd_nvram_tlv_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, size_t *olen) +{ + bhnd_nvram_prop *prop; + size_t limit, nbytes; + int error; + + /* Determine output byte limit */ + if (outp != NULL) + limit = *olen; + else + limit = 0; + + nbytes = 0; + + /* Write all properties */ + prop = NULL; + while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) { + struct bhnd_nvram_tlv_env env; + const char *name; + uint8_t *p; + size_t name_len, value_len; + size_t rec_size; + + env.hdr.tag = NVRAM_TLV_TYPE_ENV; + env.hdr.size = sizeof(env.flags); + env.flags = 0x0; + + /* Fetch name value and add to record length */ + name = bhnd_nvram_prop_name(prop); + name_len = strlen(name) + 1 /* '=' */; + + if (UINT8_MAX - env.hdr.size < name_len) { + BHND_NV_LOG("%s name exceeds maximum TLV record " + "length\n", name); + return (EFTYPE); /* would overflow TLV size */ + } + + env.hdr.size += name_len; + + /* Add string value to record length */ + error = bhnd_nvram_prop_encode(prop, NULL, &value_len, + BHND_NVRAM_TYPE_STRING); + if (error) { + BHND_NV_LOG("error serializing %s to required type " + "%s: %d\n", name, + bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING), + error); + return (error); + } + + if (UINT8_MAX - env.hdr.size < value_len) { + BHND_NV_LOG("%s value exceeds maximum TLV record " + "length\n", name); + return (EFTYPE); /* would overflow TLV size */ + } + + env.hdr.size += value_len; + + /* Calculate total record size */ + rec_size = sizeof(env.hdr) + env.hdr.size; + if (SIZE_MAX - nbytes < rec_size) + return (EFTYPE); /* would overflow size_t */ + + /* Calculate our output pointer */ + if (nbytes > limit || limit - nbytes < rec_size) { + /* buffer is full; cannot write */ + p = NULL; + } else { + p = (uint8_t *)outp + nbytes; + } + + /* Write to output */ + if (p != NULL) { + memcpy(p, &env, sizeof(env)); + p += sizeof(env); + + memcpy(p, name, name_len - 1); + p[name_len - 1] = '='; + p += name_len; + + error = bhnd_nvram_prop_encode(prop, p, &value_len, + BHND_NVRAM_TYPE_STRING); + if (error) { + BHND_NV_LOG("error serializing %s to required " + "type %s: %d\n", name, + bhnd_nvram_type_name( + BHND_NVRAM_TYPE_STRING), + error); + return (error); + } + } + + nbytes += rec_size; + } + + /* Write terminating END record */ + if (limit > nbytes) + *((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END; + + if (nbytes == SIZE_MAX) + return (EFTYPE); /* would overflow size_t */ + nbytes++; + + /* Provide required length */ + *olen = nbytes; + if (limit < *olen) { + if (outp == NULL) + return (0); + + return (ENOMEM); + } + + return (0); +} + /** * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src. * @@ -256,91 +374,13 @@ return (tlv->count); } + static bhnd_nvram_plist * bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv) { return (NULL); } -static int -bhnd_nvram_tlv_size(struct bhnd_nvram_data *nv, size_t *size) -{ - /* Let the serialization implementation calculate the length */ - return (bhnd_nvram_data_serialize(nv, NULL, size)); -} - -static int -bhnd_nvram_tlv_serialize(struct bhnd_nvram_data *nv, void *buf, size_t *len) -{ - struct bhnd_nvram_tlv *tlv; - size_t limit; - size_t next; - uint8_t tag; - int error; - - tlv = (struct bhnd_nvram_tlv *)nv; - - /* Save the buffer capacity */ - if (buf == NULL) - limit = 0; - else - limit = *len; - - /* Write all of our TLV records to the output buffer (or just - * calculate the buffer size that would be required) */ - next = 0; - do { - struct bhnd_nvram_tlv_env *env; - uint8_t *p; - size_t name_len; - size_t rec_offset, rec_size; - - /* Parse the TLV record */ - error = bhnd_nvram_tlv_next_record(tlv->data, &next, - &rec_offset, &tag); - if (error) - return (error); - - rec_size = next - rec_offset; - - /* Calculate our output pointer */ - if (rec_offset > limit || limit - rec_offset < rec_size) { - /* buffer is full; cannot write */ - p = NULL; - } else { - p = (uint8_t *)buf + rec_offset; - } - - /* If not writing, nothing further to do for this record */ - if (p == NULL) - continue; - - /* Copy to the output buffer */ - error = bhnd_nvram_io_read(tlv->data, rec_offset, p, rec_size); - if (error) - return (error); - - /* All further processing is TLV_ENV-specific */ - if (tag != NVRAM_TLV_TYPE_ENV) - continue; - - /* Restore the original key=value format, rewriting '\0' - * delimiter back to '=' */ - env = (struct bhnd_nvram_tlv_env *)p; - name_len = strlen(env->envp); /* skip variable name */ - *(env->envp + name_len) = '='; /* set '=' */ - } while (tag != NVRAM_TLV_TYPE_END); - - /* The 'next' offset should now point at EOF, and represents - * the total length of the serialized output. */ - *len = next; - - if (buf != NULL && limit < *len) - return (ENOMEM); - - return (0); -} - static uint32_t bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv) { Index: head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_datavar.h @@ -55,6 +55,13 @@ /** @see bhnd_nvram_data_probe() */ typedef int (bhnd_nvram_data_op_probe)(struct bhnd_nvram_io *io); +/** @see bhnd_nvram_data_serialize() */ +typedef int (bhnd_nvram_data_op_serialize)( + bhnd_nvram_data_class *cls, + bhnd_nvram_plist *props, + bhnd_nvram_plist *options, void *outp, + size_t *olen); + /** @see bhnd_nvram_data_new() */ typedef int (bhnd_nvram_data_op_new)(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io); @@ -66,15 +73,6 @@ /** @see bhnd_nvram_data_count() */ typedef size_t (bhnd_nvram_data_op_count)(struct bhnd_nvram_data *nv); -/** @see bhnd_nvram_data_size() */ -typedef int (bhnd_nvram_data_op_size)(struct bhnd_nvram_data *nv, - size_t *len); - -/** @see bhnd_nvram_data_serialize() */ -typedef int (bhnd_nvram_data_op_serialize)( - struct bhnd_nvram_data *nv, void *buf, - size_t *len); - /** @see bhnd_nvram_data_options() */ typedef bhnd_nvram_plist*(bhnd_nvram_data_op_options)( struct bhnd_nvram_data *nv); @@ -129,14 +127,14 @@ */ struct bhnd_nvram_data_class { const char *desc; /**< description */ + uint32_t caps; /**< capabilities (BHND_NVRAM_DATA_CAP_*) */ size_t size; /**< instance size */ bhnd_nvram_data_op_probe *op_probe; + bhnd_nvram_data_op_serialize *op_serialize; bhnd_nvram_data_op_new *op_new; bhnd_nvram_data_op_free *op_free; bhnd_nvram_data_op_count *op_count; - bhnd_nvram_data_op_size *op_size; - bhnd_nvram_data_op_serialize *op_serialize; bhnd_nvram_data_op_options *op_options; bhnd_nvram_data_op_caps *op_caps; bhnd_nvram_data_op_next *op_next; @@ -186,11 +184,10 @@ */ #define BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, _macro) \ _macro(_cname, probe) \ + _macro(_cname, serialize) \ _macro(_cname, new) \ _macro(_cname, free) \ _macro(_cname, count) \ - _macro(_cname, size) \ - _macro(_cname, serialize) \ _macro(_cname, options) \ _macro(_cname, caps) \ _macro(_cname, next) \ @@ -207,12 +204,13 @@ * Define a bhnd_nvram_data_class with class name @p _n and description * @p _desc, and register with bhnd_nvram_data_class_set. */ -#define BHND_NVRAM_DATA_CLASS_DEFN(_cname, _desc, _size) \ +#define BHND_NVRAM_DATA_CLASS_DEFN(_cname, _desc, _caps, _size) \ BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, \ BHND_NVRAM_DATA_CLASS_DECL_METHOD) \ \ struct bhnd_nvram_data_class bhnd_nvram_## _cname ## _class = { \ .desc = (_desc), \ + .caps = (_caps), \ .size = (_size), \ BHND_NVRAM_DATA_CLASS_ITER_METHODS(_cname, \ BHND_NVRAM_DATA_CLASS_ASSIGN_METHOD) \ Index: head/sys/dev/bhnd/nvram/bhnd_nvram_private.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_private.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_private.h @@ -314,7 +314,7 @@ bhnd_nvram_type type; /**< variable type */ uint8_t nelem; /**< element count, or 1 if not an array-typed variable */ - const bhnd_nvram_val_fmt *fmt; /**< value format, or NULL */ + const bhnd_nvram_val_fmt *fmt; /**< value format */ uint32_t flags; /**< flags (BHND_NVRAM_VF_*) */ }; @@ -327,19 +327,20 @@ /** * SPROM layout descriptor. */ -struct bhnd_sprom_layout { +typedef struct bhnd_sprom_layout { size_t size; /**< SPROM image size, in bytes */ uint8_t rev; /**< SPROM revision */ uint8_t flags; /**< layout flags (SPROM_LAYOUT_*) */ size_t srev_offset; /**< offset to SROM revision */ size_t magic_offset; /**< offset to magic value */ uint16_t magic_value; /**< expected magic value */ + size_t crc_offset; /**< offset to crc8 value */ const uint8_t *bindings; /**< SPROM binding opcode table */ size_t bindings_size; /**< SPROM binding opcode table size */ uint16_t num_vars; /**< total number of variables defined for this layout by the binding table */ -}; +} bhnd_sprom_layout; /* * SPROM layout descriptions generated from nvram_map. Index: head/sys/dev/bhnd/nvram/bhnd_nvram_store.h =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_store.h +++ head/sys/dev/bhnd/nvram/bhnd_nvram_store.h @@ -76,6 +76,9 @@ bhnd_nvram_plist **props, bhnd_nvram_plist **options, uint32_t flags); +int bhnd_nvram_store_serialize(struct bhnd_nvram_store *store, + const char *path, struct bhnd_nvram_io **data, uint32_t flags); + int bhnd_nvram_store_getvar(struct bhnd_nvram_store *sc, const char *name, void *outp, size_t *olen, bhnd_nvram_type otype); int bhnd_nvram_store_setvar(struct bhnd_nvram_store *sc, const char *name, Index: head/sys/dev/bhnd/nvram/bhnd_nvram_store.c =================================================================== --- head/sys/dev/bhnd/nvram/bhnd_nvram_store.c +++ head/sys/dev/bhnd/nvram/bhnd_nvram_store.c @@ -998,6 +998,90 @@ } /** + * Encode all NVRAM properties at @p path, using the @p store's current NVRAM + * data format. + * + * @param sc The NVRAM store instance. + * @param path The NVRAM path to export, or NULL to select the root + * path. + * @param[out] data On success, will be set to the newly serialized value. + * The caller is responsible for freeing this value + * via bhnd_nvram_io_free(). + * @param flags Export flags. See BHND_NVSTORE_EXPORT_*. + * + * @retval 0 success + * @retval EINVAL If @p flags is invalid. + * @retval ENOENT The requested path was not found. + * @retval ENOMEM If allocation fails. + * @retval non-zero If serialization of @p path otherwise fails, a regular + * unix error code will be returned. + */ +int +bhnd_nvram_store_serialize(struct bhnd_nvram_store *sc, const char *path, + struct bhnd_nvram_io **data, uint32_t flags) +{ + bhnd_nvram_plist *props; + bhnd_nvram_plist *options; + bhnd_nvram_data_class *cls; + struct bhnd_nvram_io *io; + void *outp; + size_t olen; + int error; + + props = NULL; + options = NULL; + io = NULL; + + /* Perform requested export */ + error = bhnd_nvram_store_export(sc, path, &cls, &props, &options, + flags); + if (error) + return (error); + + /* Determine serialized size */ + error = bhnd_nvram_data_serialize(cls, props, options, NULL, &olen); + if (error) + goto failed; + + /* Allocate output buffer */ + if ((io = bhnd_nvram_iobuf_empty(olen, olen)) == NULL) { + error = ENOMEM; + goto failed; + } + + /* Fetch write pointer */ + if ((error = bhnd_nvram_io_write_ptr(io, 0, &outp, olen, NULL))) + goto failed; + + /* Perform serialization */ + error = bhnd_nvram_data_serialize(cls, props, options, outp, &olen); + if (error) + goto failed; + + if ((error = bhnd_nvram_io_setsize(io, olen))) + goto failed; + + /* Success */ + bhnd_nvram_plist_release(props); + bhnd_nvram_plist_release(options); + + *data = io; + return (0); + +failed: + if (props != NULL) + bhnd_nvram_plist_release(props); + + if (options != NULL) + bhnd_nvram_plist_release(options); + + if (io != NULL) + bhnd_nvram_io_free(io); + + return (error); +} + +/** * Read an NVRAM variable. * * @param sc The NVRAM parser state. Index: head/sys/dev/bhnd/tools/nvram_map_gen.awk =================================================================== --- head/sys/dev/bhnd/tools/nvram_map_gen.awk +++ head/sys/dev/bhnd/tools/nvram_map_gen.awk @@ -253,14 +253,19 @@ # Value Formats Fmt = class_new("Fmt") class_add_prop(Fmt, p_name, "name") - class_add_prop(Fmt, p_symbol, "const") + class_add_prop(Fmt, p_symbol, "symbol") + class_add_prop(Fmt, p_array_fmt, "array_fmt") FmtHex = fmt_new("hex", "bhnd_nvram_val_bcm_hex_fmt") FmtDec = fmt_new("decimal", "bhnd_nvram_val_bcm_decimal_fmt") FmtMAC = fmt_new("macaddr", "bhnd_nvram_val_bcm_macaddr_fmt") FmtLEDDC = fmt_new("leddc", "bhnd_nvram_val_bcm_leddc_fmt") + FmtCharArray = fmt_new("char_array", "bhnd_nvram_val_char_array_fmt") + FmtChar = fmt_new("char", "bhnd_nvram_val_char_array_fmt", + FmtCharArray) FmtStr = fmt_new("string", "bhnd_nvram_val_bcm_string_fmt") + # User-specifiable value formats ValueFormats = map_new() map_set(ValueFormats, get(FmtHex, p_name), FmtHex) map_set(ValueFormats, get(FmtDec, p_name), FmtDec) @@ -315,7 +320,7 @@ "BHND_NVRAM_TYPE_INT32_ARRAY", FmtDec, UInt32Max, 6, 22) Char = type_new("char", 1, 1, "BHND_NVRAM_TYPE_CHAR", - "BHND_NVRAM_TYPE_CHAR_ARRAY", FmtStr, UInt8Max, 8, 24) + "BHND_NVRAM_TYPE_CHAR_ARRAY", FmtChar, UInt8Max, 8, 24) BaseTypes = map_new() map_set(BaseTypes, get(UInt8, p_name), UInt8) @@ -634,7 +639,7 @@ # Write a top-level bhnd_sprom_layout entry for the given revision # and layout definition function write_data_srom_layout(layout, revision, _flags, _size, - _sromcrc, _crc_seg, + _sromcrc, _crc_seg, _crc_off, _sromsig, _sig_seg, _sig_offset, _sig_value, _sromrev, _rev_seg, _rev_off, _num_vars) @@ -648,7 +653,8 @@ "cannot compute total size") } else { _crc_seg = srom_entry_get_single_segment(_sromcrc) - _size = get(_crc_seg, p_offset) + _crc_off = get(_crc_seg, p_offset) + _size = _crc_off _size += get(get(_crc_seg, p_type), p_width) } @@ -703,6 +709,8 @@ emit(".magic_value = 0,\n") } + emit(".crc_offset = " _crc_off ",\n") + emit(".bindings = " srom_layout_get_variable_name(layout) ",\n") emit(".bindings_size = nitems(" \ srom_layout_get_variable_name(layout) "),\n") @@ -1741,6 +1749,9 @@ if (class == null) return (0) + if (prop_id == null) + return (0) + # Check class<->prop cache if ((class, prop_id) in _g_class_prop_cache) return (1) @@ -2538,9 +2549,17 @@ } # Return the default fmt for a given type instance -function type_get_default_fmt(type, _base) { +function type_get_default_fmt(type, _base, _fmt, _array_fmt) { _base = type_get_base(type) - return (get(_base, p_default_fmt)) + _fmt = get(_base, p_default_fmt) + + if (obj_is_instanceof(type, ArrayType)) { + _array_fmt = get(_fmt, p_array_fmt) + if (_array_fmt != null) + _fmt = _array_fmt + } + + return (_fmt) } # Return a string representation of the given type @@ -2641,11 +2660,14 @@ } # Create a new Fmt instance -function fmt_new(name, symbol, _obj) { +function fmt_new(name, symbol, array_fmt, _obj) { _obj = obj_new(Fmt) set(_obj, p_name, name) set(_obj, p_symbol, symbol) + if (array_fmt != null) + set(_obj, p_array_fmt, array_fmt) + return (_obj) } Index: head/sys/modules/bhnd/Makefile =================================================================== --- head/sys/modules/bhnd/Makefile +++ head/sys/modules/bhnd/Makefile @@ -32,6 +32,7 @@ bhnd_nvram_data_bcmraw.c \ bhnd_nvram_data_btxt.c \ bhnd_nvram_data_sprom.c \ + bhnd_nvram_data_sprom_subr.c \ bhnd_nvram_data_tlv.c \ bhnd_nvram_io.c \ bhnd_nvram_iobuf.c \