Changeset View
Changeset View
Standalone View
Standalone View
head/sys/dev/bhnd/nvram/bhnd_sprom_parser.c
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org> | |||||
* All rights reserved. | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer, | |||||
* without modification. | |||||
* 2. Redistributions in binary form must reproduce at minimum a disclaimer | |||||
* similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any | |||||
* redistribution must be conditioned upon including a substantially | |||||
* similar Disclaimer requirement for further binary redistribution. | |||||
* | |||||
* NO WARRANTY | |||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |||||
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |||||
* LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY | |||||
* AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL | |||||
* THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, | |||||
* OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER | |||||
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |||||
* THE POSSIBILITY OF SUCH DAMAGES. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/param.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/endian.h> | |||||
#include <sys/limits.h> | |||||
#include <sys/rman.h> | |||||
#include <sys/systm.h> | |||||
#include <machine/bus.h> | |||||
#include <machine/resource.h> | |||||
#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 (','); | |||||
} | |||||
} |