/*- * 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 #include #include #include #include #include #include #include "bhnd_nvram_common.h" #include "bhnd_sprom_parservar.h" /* * BHND SPROM Parser * * Provides identification, decoding, and encoding of BHND SPROM data. */ static int sprom_direct_read(struct bhnd_sprom *sc, size_t offset, void *buf, size_t nbytes, uint8_t *crc); static int sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size, uint8_t *crc); static int sprom_populate_shadow(struct bhnd_sprom *sc); static int sprom_get_var_defn(struct bhnd_sprom *sc, const char *name, const struct bhnd_nvram_vardefn **var, const struct bhnd_sprom_vardefn **sprom, size_t *size, size_t *nelem, bhnd_nvram_type req_type); static char sprom_get_delim_char(struct bhnd_sprom *sc, bhnd_nvram_sfmt sfmt); /* SPROM revision is always located at the second-to-last byte */ #define SPROM_REV(_sc) SPROM_READ_1((_sc), (_sc)->sp_size - 2) /* SPROM CRC is always located at the last byte */ #define SPROM_CRC_OFF(_sc) SPROM_CRC_LEN(_sc) /* SPROM CRC covers all but the final CRC byte */ #define SPROM_CRC_LEN(_sc) ((_sc)->sp_size - 1) /* SPROM shadow I/O (with byte-order translation) */ #define SPROM_READ_1(_sc, _off) SPROM_READ_ENC_1(_sc, _off) #define SPROM_READ_2(_sc, _off) le16toh(SPROM_READ_ENC_2(_sc, _off)) #define SPROM_READ_4(_sc, _off) le32toh(SPROM_READ_ENC_4(_sc, _off)) #define SPROM_WRITE_1(_sc, _off, _v) SPROM_WRITE_ENC_1(_sc, _off, (_v)) #define SPROM_WRITE_2(_sc, _off, _v) SPROM_WRITE_ENC_2(_sc, _off, \ htole16(_v)) #define SPROM_WRITE_4(_sc, _off, _v) SPROM_WRITE_ENC_4(_sc, _off, \ htole32(_v)) /* SPROM shadow I/O (without byte-order translation) */ #define SPROM_READ_ENC_1(_sc, _off) (*(uint8_t *)((_sc)->sp_shadow + _off)) #define SPROM_READ_ENC_2(_sc, _off) (*(uint16_t *)((_sc)->sp_shadow + _off)) #define SPROM_READ_ENC_4(_sc, _off) (*(uint32_t *)((_sc)->sp_shadow + _off)) #define SPROM_WRITE_ENC_1(_sc, _off, _v) \ *((uint8_t *)((_sc)->sp_shadow + _off)) = (_v) #define SPROM_WRITE_ENC_2(_sc, _off, _v) \ *((uint16_t *)((_sc)->sp_shadow + _off)) = (_v) #define SPROM_WRITE_ENC_4(_sc, _off, _v) \ *((uint32_t *)((_sc)->sp_shadow + _off)) = (_v) /* Call @p _next macro with the C type, widened (signed or unsigned) 32-bit C * type, width, and min/max values associated with @p _dtype */ #define SPROM_SWITCH_TYPE(_dtype, _next, ...) \ do { \ switch (_dtype) { \ case BHND_NVRAM_TYPE_UINT8: \ _next (uint8_t, uint32_t, 1, 0, \ UINT8_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_UINT16: \ _next (uint16_t, uint32_t, 2, 0, \ UINT16_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_UINT32: \ _next (uint32_t, uint32_t, 4, 0, \ UINT32_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_INT8: \ _next (int8_t, int32_t, 1, \ INT8_MIN, INT8_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_INT16: \ _next (int16_t, int32_t, 2, \ INT16_MIN, INT16_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_INT32: \ _next (int32_t, int32_t, 4, \ INT32_MIN, INT32_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_CHAR: \ _next (char, int32_t, 1, \ CHAR_MIN, CHAR_MAX, ## __VA_ARGS__); \ break; \ case BHND_NVRAM_TYPE_CSTR: \ panic("%s: BHND_NVRAM_TYPE_CSTR unhandled", \ __FUNCTION__); \ break; \ } \ } while (0) /* Verify the range of _val of (_stype) within _type */ #define SPROM_VERIFY_RANGE(_type, _widen, _width, _min, _max, _val, \ _stype) \ do { \ if (BHND_NVRAM_SIGNED_TYPE(_stype)) { \ int32_t sval = (int32_t) (_val); \ if (sval > (_max) || sval < (_min)) \ return (ERANGE); \ } else { \ if ((_val) > (_max)) \ return (ERANGE); \ } \ } while(0) /* * Table of supported SPROM image formats, sorted by image size, ascending. */ #define SPROM_FMT(_sz, _revmin, _revmax, _sig) \ { SPROM_SZ_ ## _sz, _revmin, _revmax, \ SPROM_SIG_ ## _sig ## _OFF, \ SPROM_SIG_ ## _sig } static const struct sprom_fmt { size_t size; uint8_t rev_min; uint8_t rev_max; size_t sig_offset; uint16_t sig_req; } sprom_fmts[] = { SPROM_FMT(R1_3, 1, 3, NONE), SPROM_FMT(R4_8_9, 4, 4, R4), SPROM_FMT(R4_8_9, 8, 9, R8_9), SPROM_FMT(R10, 10, 10, R10), SPROM_FMT(R11, 11, 11, R11) }; /** * Identify the SPROM format at @p offset within @p r, verify the CRC, * and allocate a local shadow copy of the SPROM data. * * After successful initialization, @p r will not be accessed; any pin * configuration required for SPROM access may be reset. * * @param[out] sprom On success, will be initialized with shadow of the SPROM * data. * @param r An active resource mapping the SPROM data. * @param offset Offset of the SPROM data within @p resource. */ int bhnd_sprom_init(struct bhnd_sprom *sprom, struct bhnd_resource *r, bus_size_t offset) { bus_size_t res_size; int error; sprom->dev = rman_get_device(r->res); sprom->sp_res = r; sprom->sp_res_off = offset; /* Determine maximum possible SPROM image size */ res_size = rman_get_size(r->res); if (offset >= res_size) return (EINVAL); sprom->sp_size_max = MIN(res_size - offset, SPROM_SZ_MAX); /* Allocate and populate SPROM shadow */ sprom->sp_size = 0; sprom->sp_capacity = sprom->sp_size_max; sprom->sp_shadow = malloc(sprom->sp_capacity, M_BHND_NVRAM, M_NOWAIT); if (sprom->sp_shadow == NULL) return (ENOMEM); /* Read and identify SPROM image */ if ((error = sprom_populate_shadow(sprom))) return (error); return (0); } /** * Release all resources held by @p sprom. * * @param sprom A SPROM instance previously initialized via bhnd_sprom_init(). */ void bhnd_sprom_fini(struct bhnd_sprom *sprom) { free(sprom->sp_shadow, M_BHND_NVRAM); } /* Perform a read using a SPROM offset descriptor, safely widening the result * to its 32-bit representation before assigning it to @p _dest. */ #define SPROM_GETVAR_READ(_type, _widen, _width, _min, _max, _sc, _off, \ _dest) \ do { \ _type _v = (_type)SPROM_READ_ ## _width(_sc, _off->offset); \ if (_off->shift > 0) { \ _v >>= _off->shift; \ } else if (off->shift < 0) { \ _v <<= -_off->shift; \ } \ \ if (_off->cont) \ _dest |= ((uint32_t) (_widen) _v) & _off->mask; \ else \ _dest = ((uint32_t) (_widen) _v) & _off->mask; \ } while(0) /* Emit a value read using a SPROM offset descriptor, narrowing the * result output representation. */ #define SPROM_GETVAR_WRITE(_type, _widen, _width, _min, _max, _off, \ _src, _buf) \ do { \ _type _v = (_type) (_widen) _src; \ *((_type *)_buf) = _v; \ } while(0) /* String format a value read using a SPROM offset descriptor */ #define SPROM_GETVAR_SNPRINTF(_type, _widen, _width, _min, _max, _src, \ _buf, _remain, _fmt, _nwrite) \ do { \ _nwrite = snprintf(_buf, _remain, _fmt, (_type) (_widen) _src); \ } while(0) /** * Read a SPROM variable, performing conversion to host byte order. * * @param sc The SPROM parser state. * @param name The SPROM variable name. * @param[out] buf On success, the requested value 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 size of the requested value. * @param type The requested data type to be written to @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval ENOMEM If @p buf is non-NULL and a buffer of @p len is too * small to hold the requested value. * @retval non-zero If reading @p name otherwise fails, a regular unix * error code will be returned. */ int bhnd_sprom_getvar(struct bhnd_sprom *sc, const char *name, void *buf, size_t *len, bhnd_nvram_type type) { const struct bhnd_nvram_vardefn *nv; const struct bhnd_sprom_vardefn *sv; void *outp; size_t all1_offs; size_t req_size, nelem; size_t str_remain; char str_delim; uint32_t val; int error; error = sprom_get_var_defn(sc, name, &nv, &sv, &req_size, &nelem, type); if (error) return (error); outp = buf; str_remain = 0; str_delim = '\0'; if (type != BHND_NVRAM_TYPE_CSTR) { /* Provide required size */ if (outp == NULL) { *len = req_size; return (0); } /* Check (and update) target buffer len */ if (*len < req_size) return (ENOMEM); else *len = req_size; } else { /* String length calculation requires performing * the actual string formatting */ KASSERT(req_size == 0, ("req_size set for variable-length type")); /* If caller is querying length, the len argument * may be uninitialized */ if (outp != NULL) str_remain = *len; /* Fetch delimiter for the variable's string format */ str_delim = sprom_get_delim_char(sc, nv->sfmt); } /* Read data */ all1_offs = 0; val = 0; for (size_t i = 0; i < sv->num_offsets; i++) { const struct bhnd_sprom_offset *off; off = &sv->offsets[i]; KASSERT(!off->cont || i > 0, ("cont marked on first offset")); /* If not a continuation, advance the output buffer; if * a C string, this requires appending a delimiter character */ if (i > 0 && !off->cont) { size_t width = bhnd_nvram_type_width(type); /* Non-fixed width types (such as CSTR) will have a 0 * width value */ if (width != 0) { KASSERT(outp != NULL, ("NULL output buffer")); outp = ((uint8_t *)outp) + width; } /* Append CSTR delim, if necessary */ if (type == BHND_NVRAM_TYPE_CSTR && str_delim != '\0' && i != 0) { if (outp != NULL && str_remain >= 1) { *((char *)outp) = str_delim; outp = ((char *)outp + 1); /* Drop outp reference if we hit 0 */ if (str_remain-- == 0) outp = NULL; } if (SIZE_MAX - 1 < req_size) return (EFTYPE); /* too long */ req_size++; } } /* Read the value, widening to a common uint32 * representation */ SPROM_SWITCH_TYPE(off->type, SPROM_GETVAR_READ, sc, off, val); /* If IGNALL1, record whether value has all bits set. */ if (nv->flags & BHND_NVRAM_VF_IGNALL1) { uint32_t all1; all1 = off->mask; if (off->shift > 0) all1 >>= off->shift; else if (off->shift < 0) all1 <<= -off->shift; if ((val & all1) == all1) all1_offs++; } /* Skip writing if additional continuations remain */ if (i+1 < sv->num_offsets && sv->offsets[i].cont) continue; /* Perform write */ if (type == BHND_NVRAM_TYPE_CSTR) { const char *fmtstr; int written; fmtstr = bhnd_nvram_type_fmt(off->type, nv->sfmt, i); if (fmtstr == NULL) { device_printf(sc->dev, "no NVRAM format string " "for '%s' (type=%d)\n", name, off->type); return (EOPNOTSUPP); } SPROM_SWITCH_TYPE(off->type, SPROM_GETVAR_SNPRINTF, val, outp, str_remain, fmtstr, written); if (written <= 0) return (EFTYPE); /* Calculate remaining capacity, drop outp reference * if we hit 0 -- otherwise, advance the buffer * position */ if (written >= str_remain) { str_remain = 0; outp = NULL; } else { str_remain -= written; if (outp != NULL) outp = (char *)outp + written; } /* Add additional bytes to total length */ if (SIZE_MAX - written < req_size) return (EFTYPE); /* string too long */ req_size += written; } else { /* Verify range */ SPROM_SWITCH_TYPE(type, SPROM_VERIFY_RANGE, val, off->type); /* Write the value, narrowing to the appropriate output * width. */ SPROM_SWITCH_TYPE(type, SPROM_GETVAR_WRITE, off, val, outp); } } /* Should value should be treated as uninitialized? */ if (nv->flags & BHND_NVRAM_VF_IGNALL1 && all1_offs == sv->num_offsets) return (ENOENT); /* If this is a C string request, we need to provide the computed * length. */ if (type == BHND_NVRAM_TYPE_CSTR) { /* Account for final trailing NUL */ if (SIZE_MAX - 1 < req_size) return (EFTYPE); /* string too long */ req_size++; /* Return an error if a too-small output buffer was provided */ if (buf != NULL && *len < req_size) { *len = req_size; return (ENOMEM); } *len = req_size; } return (0); } /* Perform a read of a variable offset from _src, safely widening the result * to its 32-bit representation before assigning it to @p _dest. */ #define SPROM_SETVAR_READ(_type, _widen, _width, _min, _max, _off, \ _src, _dest) \ do { \ _type _v = *(const _type *)_src; \ if (_off->shift > 0) { \ _v <<= _off->shift; \ } else if (off->shift < 0) { \ _v >>= -_off->shift; \ } \ _dest = ((uint32_t) (_widen) _v) & _off->mask; \ } while(0) /* Emit a value read using a SPROM offset descriptor, narrowing the * result output representation and, if necessary, OR'ing it with the * previously read value from @p _buf. */ #define SPROM_SETVAR_WRITE(_type, _widen, _width, _min, _max, _sc, \ _off, _src) \ do { \ _type _v = (_type) (_widen) _src; \ if (_off->cont) \ _v |= SPROM_READ_ ## _width(_sc, _off->offset); \ SPROM_WRITE_ ## _width(_sc, _off->offset, _v); \ } while(0) /** * Set a local value for a SPROM variable, performing conversion to SPROM byte * order. * * The new value will be written to the backing SPROM shadow. * * @param sc The SPROM parser state. * @param name The SPROM variable name. * @param[out] buf The new value. * @param[in,out] len The size of @p buf. * @param type The data type of @p buf. * * @retval 0 success * @retval ENOENT The requested variable was not found. * @retval EINVAL If @p len does not match the expected variable size. */ int bhnd_sprom_setvar(struct bhnd_sprom *sc, const char *name, const void *buf, size_t len, bhnd_nvram_type type) { const struct bhnd_nvram_vardefn *nv; const struct bhnd_sprom_vardefn *sv; size_t req_size, nelem; int error; uint8_t crc; error = sprom_get_var_defn(sc, name, &nv, &sv, &req_size, &nelem, type); if (error) return (error); /* String parsing is currently unsupported */ if (type == BHND_NVRAM_TYPE_CSTR) return (EOPNOTSUPP); /* Provide required size */ if (len != req_size) return (EINVAL); /* Write data */ for (size_t i = 0; i < sv->num_offsets; i++) { const struct bhnd_sprom_offset *off; uint32_t val; off = &sv->offsets[i]; KASSERT(!off->cont || i > 0, ("cont marked on first offset")); /* If not a continuation, advance the input pointer */ if (i > 0 && !off->cont) { buf = ((const uint8_t *)buf) + bhnd_nvram_type_width(sv->offsets[i-1].type); } /* Read the value, widening to a common uint32 * representation */ SPROM_SWITCH_TYPE(nv->type, SPROM_SETVAR_READ, off, buf, val); /* Verify range */ SPROM_SWITCH_TYPE(nv->type, SPROM_VERIFY_RANGE, val, type); /* Write the value, narrowing to the appropriate output * width. */ SPROM_SWITCH_TYPE(off->type, SPROM_SETVAR_WRITE, sc, off, val); } /* Update CRC */ crc = ~bhnd_nvram_crc8(sc->sp_shadow, SPROM_CRC_LEN(sc), BHND_NVRAM_CRC8_INITIAL); SPROM_WRITE_1(sc, SPROM_CRC_OFF(sc), crc); return (0); } /* Read and identify the SPROM image by incrementally performing * read + CRC of all supported image formats */ static int sprom_populate_shadow(struct bhnd_sprom *sc) { const struct sprom_fmt *fmt; int error; uint16_t sig; uint8_t srom_rev; uint8_t crc; crc = BHND_NVRAM_CRC8_INITIAL; /* Identify the SPROM revision (and populate the SPROM shadow) */ for (size_t i = 0; i < nitems(sprom_fmts); i++) { fmt = &sprom_fmts[i]; /* Read image data and check CRC */ if ((error = sprom_extend_shadow(sc, fmt->size, &crc))) return (error); /* Skip on invalid CRC */ if (crc != BHND_NVRAM_CRC8_VALID) continue; /* Fetch SROM revision */ srom_rev = SPROM_REV(sc); /* Early sromrev 1 devices (specifically some BCM440x enet * cards) are reported to have been incorrectly programmed * with a revision of 0x10. */ if (fmt->size == SPROM_SZ_R1_3 && srom_rev == 0x10) srom_rev = 0x1; /* Verify revision range */ if (srom_rev < fmt->rev_min || srom_rev > fmt->rev_max) continue; /* Verify signature (if any) */ sig = SPROM_SIG_NONE; if (fmt->sig_offset != SPROM_SIG_NONE_OFF) sig = SPROM_READ_2(sc, fmt->sig_offset); if (sig != fmt->sig_req) { device_printf(sc->dev, "invalid sprom %hhu signature: 0x%hx " "(expected 0x%hx)\n", srom_rev, sig, fmt->sig_req); return (EINVAL); } /* Identified */ sc->sp_rev = srom_rev; return (0); } /* identification failed */ device_printf(sc->dev, "unrecognized SPROM format\n"); return (EINVAL); } /* * Extend the shadowed SPROM buffer to image_size, reading any required * data from the backing SPROM resource and updating the CRC. */ static int sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size, uint8_t *crc) { int error; KASSERT(image_size >= sc->sp_size, (("shadow truncation unsupported"))); /* Verify the request fits within our shadow buffer */ if (image_size > sc->sp_capacity) return (ENOSPC); /* Skip no-op requests */ if (sc->sp_size == image_size) return (0); /* Populate the extended range */ error = sprom_direct_read(sc, sc->sp_size, sc->sp_shadow + sc->sp_size, image_size - sc->sp_size, crc); if (error) return (error); sc->sp_size = image_size; return (0); } /** * Read nbytes at the given offset from the backing SPROM resource, and * update the CRC. */ static int sprom_direct_read(struct bhnd_sprom *sc, size_t offset, void *buf, size_t nbytes, uint8_t *crc) { bus_size_t res_offset; uint16_t *p; KASSERT(nbytes % sizeof(uint16_t) == 0, ("unaligned sprom size")); KASSERT(offset % sizeof(uint16_t) == 0, ("unaligned sprom offset")); /* Check for read overrun */ if (offset >= sc->sp_size_max || sc->sp_size_max - offset < nbytes) { device_printf(sc->dev, "requested SPROM read would overrun\n"); return (EINVAL); } /* Perform read and update CRC */ p = (uint16_t *)buf; res_offset = sc->sp_res_off + offset; bhnd_bus_read_region_stream_2(sc->sp_res, res_offset, p, (nbytes / sizeof(uint16_t))); *crc = bhnd_nvram_crc8(p, nbytes, *crc); return (0); } /** * Locate the variable and SPROM revision-specific definitions * for variable with @p name. */ static int sprom_get_var_defn(struct bhnd_sprom *sc, const char *name, const struct bhnd_nvram_vardefn **var, const struct bhnd_sprom_vardefn **sprom, size_t *size, size_t *nelem, bhnd_nvram_type req_type) { /* Find variable definition */ *var = bhnd_nvram_find_vardefn(name); if (*var == NULL) return (ENOENT); /* Find revision-specific SPROM definition */ for (size_t i = 0; i < (*var)->num_sp_defs; i++) { const struct bhnd_sprom_vardefn *sp = &(*var)->sp_defs[i]; if (sc->sp_rev < sp->compat.first) continue; if (sc->sp_rev > sp->compat.last) continue; /* Found */ *sprom = sp; /* Calculate element count and total size, in bytes */ *nelem = 0; for (size_t j = 0; j < sp->num_offsets; j++) if (!sp->offsets[j].cont) *nelem += 1; *size = bhnd_nvram_type_width(req_type) * (*nelem); return (0); } /* Not supported by this SPROM revision */ return (ENOENT); } /** * Return the array element delimiter for @p sfmt, or '\0' if none. */ static char sprom_get_delim_char(struct bhnd_sprom *sc, bhnd_nvram_sfmt sfmt) { switch (sfmt) { case BHND_NVRAM_SFMT_HEX: case BHND_NVRAM_SFMT_DEC: return (','); case BHND_NVRAM_SFMT_CCODE: case BHND_NVRAM_SFMT_LEDDC: return ('\0'); case BHND_NVRAM_SFMT_MACADDR: return (':'); default: device_printf(sc->dev, "unknown NVRAM string format: %d\n", sfmt); return (','); } }