]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/bhnd/nvram/bhnd_sprom_parser.c
Add kernel interfaces to call EFI Runtime Services.
[FreeBSD/FreeBSD.git] / sys / dev / bhnd / nvram / bhnd_sprom_parser.c
1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/param.h>
34 #include <sys/bus.h>
35 #include <sys/endian.h>
36 #include <sys/limits.h>
37 #include <sys/rman.h>
38 #include <sys/systm.h>
39
40 #include <machine/bus.h>
41 #include <machine/resource.h>
42
43 #include "bhnd_nvram_common.h"
44
45 #include "bhnd_sprom_parservar.h"
46
47 /*
48  * BHND SPROM Parser
49  * 
50  * Provides identification, decoding, and encoding of BHND SPROM data.
51  */
52
53 static int               sprom_direct_read(struct bhnd_sprom *sc, size_t offset,
54                              void *buf, size_t nbytes, uint8_t *crc);
55 static int               sprom_extend_shadow(struct bhnd_sprom *sc,
56                              size_t image_size, uint8_t *crc);
57 static int               sprom_populate_shadow(struct bhnd_sprom *sc);
58
59 static int               sprom_get_var_defn(struct bhnd_sprom *sc,
60                              const char *name,
61                              const struct bhnd_nvram_vardefn **var,
62                              const struct bhnd_sprom_vardefn **sprom,
63                              size_t *size, size_t *nelem,
64                              bhnd_nvram_type req_type);
65
66 static char              sprom_get_delim_char(struct bhnd_sprom *sc,
67                              bhnd_nvram_sfmt sfmt);
68
69 /* SPROM revision is always located at the second-to-last byte */
70 #define SPROM_REV(_sc)          SPROM_READ_1((_sc), (_sc)->sp_size - 2)
71
72 /* SPROM CRC is always located at the last byte */
73 #define SPROM_CRC_OFF(_sc)      SPROM_CRC_LEN(_sc)
74
75 /* SPROM CRC covers all but the final CRC byte */
76 #define SPROM_CRC_LEN(_sc)      ((_sc)->sp_size - 1)
77
78 /* SPROM shadow I/O (with byte-order translation) */
79 #define SPROM_READ_1(_sc, _off)         SPROM_READ_ENC_1(_sc, _off)
80 #define SPROM_READ_2(_sc, _off)         le16toh(SPROM_READ_ENC_2(_sc, _off))
81 #define SPROM_READ_4(_sc, _off)         le32toh(SPROM_READ_ENC_4(_sc, _off))
82
83 #define SPROM_WRITE_1(_sc, _off, _v)    SPROM_WRITE_ENC_1(_sc, _off, (_v))
84 #define SPROM_WRITE_2(_sc, _off, _v)    SPROM_WRITE_ENC_2(_sc, _off,    \
85     htole16(_v))
86 #define SPROM_WRITE_4(_sc, _off, _v)    SPROM_WRITE_ENC_4(_sc, _off,    \
87     htole32(_v))
88
89 /* SPROM shadow I/O (without byte-order translation) */
90 #define SPROM_READ_ENC_1(_sc, _off)     (*(uint8_t *)((_sc)->sp_shadow + _off))
91 #define SPROM_READ_ENC_2(_sc, _off)     (*(uint16_t *)((_sc)->sp_shadow + _off))
92 #define SPROM_READ_ENC_4(_sc, _off)     (*(uint32_t *)((_sc)->sp_shadow + _off))
93
94 #define SPROM_WRITE_ENC_1(_sc, _off, _v)        \
95         *((uint8_t *)((_sc)->sp_shadow + _off)) = (_v)
96 #define SPROM_WRITE_ENC_2(_sc, _off, _v)        \
97         *((uint16_t *)((_sc)->sp_shadow + _off)) = (_v)
98 #define SPROM_WRITE_ENC_4(_sc, _off, _v)        \
99         *((uint32_t *)((_sc)->sp_shadow + _off)) = (_v)
100         
101 /* Call @p _next macro with the C type, widened (signed or unsigned) 32-bit C
102  * type, width, and min/max values associated with @p _dtype */
103 #define SPROM_SWITCH_TYPE(_dtype, _next, ...)                           \
104 do {                                                                    \
105         switch (_dtype) {                                               \
106         case BHND_NVRAM_TYPE_UINT8:                                     \
107                 _next (uint8_t,         uint32_t,       1,      0,      \
108                     UINT8_MAX, ## __VA_ARGS__);                         \
109                 break;                                                  \
110         case BHND_NVRAM_TYPE_UINT16:                                    \
111                 _next (uint16_t,        uint32_t,       2,      0,      \
112                     UINT16_MAX, ## __VA_ARGS__);                        \
113                 break;                                                  \
114         case BHND_NVRAM_TYPE_UINT32:                                    \
115                 _next (uint32_t,        uint32_t,       4,      0,      \
116                     UINT32_MAX, ## __VA_ARGS__);                        \
117                 break;                                                  \
118         case BHND_NVRAM_TYPE_INT8:                                      \
119                 _next (int8_t,          int32_t,        1,              \
120                     INT8_MIN,   INT8_MAX, ## __VA_ARGS__);              \
121                 break;                                                  \
122         case BHND_NVRAM_TYPE_INT16:                                     \
123                 _next (int16_t,         int32_t,        2,              \
124                     INT16_MIN,  INT16_MAX, ## __VA_ARGS__);             \
125                 break;                                                  \
126         case BHND_NVRAM_TYPE_INT32:                                     \
127                 _next (int32_t,         int32_t,        4,              \
128                     INT32_MIN,  INT32_MAX, ## __VA_ARGS__);             \
129                 break;                                                  \
130         case BHND_NVRAM_TYPE_CHAR:                                      \
131                 _next (char,            int32_t,        1,              \
132                     CHAR_MIN,   CHAR_MAX, ## __VA_ARGS__);              \
133                 break;                                                  \
134         case BHND_NVRAM_TYPE_CSTR:                                      \
135                 panic("%s: BHND_NVRAM_TYPE_CSTR unhandled",             \
136                     __FUNCTION__);                                      \
137                 break;                                                  \
138         }                                                               \
139 } while (0)
140
141
142 /* Verify the range of _val of (_stype) within _type */
143 #define SPROM_VERIFY_RANGE(_type, _widen, _width, _min, _max, _val,     \
144     _stype)                                                             \
145 do {                                                                    \
146         if (BHND_NVRAM_SIGNED_TYPE(_stype)) {                           \
147                 int32_t sval = (int32_t) (_val);                        \
148                 if (sval > (_max) || sval < (_min))                     \
149                         return (ERANGE);                                \
150         } else {                                                        \
151                 if ((_val) > (_max))                                    \
152                         return (ERANGE);                                \
153         }                                                               \
154 } while(0)
155
156 /*
157  * Table of supported SPROM image formats, sorted by image size, ascending.
158  */
159 #define SPROM_FMT(_sz, _revmin, _revmax, _sig)  \
160         { SPROM_SZ_ ## _sz, _revmin, _revmax,   \
161             SPROM_SIG_ ## _sig ## _OFF,         \
162             SPROM_SIG_ ## _sig }
163
164 static const struct sprom_fmt {
165         size_t          size;
166         uint8_t         rev_min;
167         uint8_t         rev_max;
168         size_t          sig_offset;
169         uint16_t        sig_req;
170 } sprom_fmts[] = {
171         SPROM_FMT(R1_3,         1, 3,   NONE),
172         SPROM_FMT(R4_8_9,       4, 4,   R4),
173         SPROM_FMT(R4_8_9,       8, 9,   R8_9),
174         SPROM_FMT(R10,          10, 10, R10),
175         SPROM_FMT(R11,          11, 11, R11)
176 };
177
178 /**
179  * Identify the SPROM format at @p offset within @p r, verify the CRC,
180  * and allocate a local shadow copy of the SPROM data.
181  * 
182  * After successful initialization, @p r will not be accessed; any pin
183  * configuration required for SPROM access may be reset.
184  * 
185  * @param[out] sprom On success, will be initialized with shadow of the SPROM
186  * data. 
187  * @param r An active resource mapping the SPROM data.
188  * @param offset Offset of the SPROM data within @p resource.
189  */
190 int
191 bhnd_sprom_init(struct bhnd_sprom *sprom, struct bhnd_resource *r,
192     bus_size_t offset)
193 {
194         bus_size_t       res_size;
195         int              error;
196
197         sprom->dev = rman_get_device(r->res);
198         sprom->sp_res = r;
199         sprom->sp_res_off = offset;
200
201         /* Determine maximum possible SPROM image size */
202         res_size = rman_get_size(r->res);
203         if (offset >= res_size)
204                 return (EINVAL);
205
206         sprom->sp_size_max = MIN(res_size - offset, SPROM_SZ_MAX); 
207
208         /* Allocate and populate SPROM shadow */
209         sprom->sp_size = 0;
210         sprom->sp_capacity = sprom->sp_size_max;
211         sprom->sp_shadow = malloc(sprom->sp_capacity, M_BHND_NVRAM, M_NOWAIT);
212         if (sprom->sp_shadow == NULL)
213                 return (ENOMEM);
214
215         /* Read and identify SPROM image */
216         if ((error = sprom_populate_shadow(sprom)))
217                 return (error);
218
219         return (0);
220 }
221
222 /**
223  * Release all resources held by @p sprom.
224  * 
225  * @param sprom A SPROM instance previously initialized via bhnd_sprom_init().
226  */
227 void
228 bhnd_sprom_fini(struct bhnd_sprom *sprom)
229 {
230         free(sprom->sp_shadow, M_BHND_NVRAM);
231 }
232
233 /* Perform a read using a SPROM offset descriptor, safely widening the result
234  * to its 32-bit representation before assigning it to @p _dest. */
235 #define SPROM_GETVAR_READ(_type, _widen, _width, _min, _max, _sc, _off, \
236     _dest)                                                              \
237 do {                                                                    \
238         _type _v = (_type)SPROM_READ_ ## _width(_sc, _off->offset);     \
239         if (_off->shift > 0) {                                          \
240                 _v >>= _off->shift;                                     \
241         } else if (off->shift < 0) {                                    \
242                 _v <<= -_off->shift;                                    \
243         }                                                               \
244                                                                         \
245         if (_off->cont)                                                 \
246                 _dest |= ((uint32_t) (_widen) _v) & _off->mask;         \
247         else                                                            \
248                 _dest = ((uint32_t) (_widen) _v) & _off->mask;          \
249 } while(0)
250
251 /* Emit a value read using a SPROM offset descriptor, narrowing the
252  * result output representation. */
253 #define SPROM_GETVAR_WRITE(_type, _widen, _width, _min, _max, _off,     \
254     _src, _buf)                                                         \
255 do {                                                                    \
256         _type _v = (_type) (_widen) _src;                               \
257         *((_type *)_buf) = _v;                                          \
258 } while(0)
259
260 /* String format a value read using a SPROM offset descriptor */
261 #define SPROM_GETVAR_SNPRINTF(_type, _widen, _width, _min, _max, _src,  \
262     _buf, _remain, _fmt, _nwrite)                                       \
263 do {                                                                    \
264         _nwrite = snprintf(_buf, _remain, _fmt, (_type) (_widen) _src); \
265 } while(0)
266
267 /**
268  * Read a SPROM variable, performing conversion to host byte order.
269  *
270  * @param               sc      The SPROM parser state.
271  * @param               name    The SPROM variable name.
272  * @param[out]          buf     On success, the requested value will be written
273  *                              to this buffer. This argment may be NULL if
274  *                              the value is not desired.
275  * @param[in,out]       len     The capacity of @p buf. On success, will be set
276  *                              to the actual size of the requested value.
277  * @param               type    The requested data type to be written to @p buf.
278  *
279  * @retval 0            success
280  * @retval ENOENT       The requested variable was not found.
281  * @retval ENOMEM       If @p buf is non-NULL and a buffer of @p len is too
282  *                      small to hold the requested value.
283  * @retval non-zero     If reading @p name otherwise fails, a regular unix
284  *                      error code will be returned.
285  */
286 int
287 bhnd_sprom_getvar(struct bhnd_sprom *sc, const char *name, void *buf,
288     size_t *len, bhnd_nvram_type type)
289 {
290         const struct bhnd_nvram_vardefn *nv;
291         const struct bhnd_sprom_vardefn *sv;
292         void                            *outp;
293         size_t                           all1_offs;
294         size_t                           req_size, nelem;
295         size_t                           str_remain;
296         char                             str_delim;
297         uint32_t                         val;
298         int                              error;
299
300         error = sprom_get_var_defn(sc, name, &nv, &sv, &req_size, &nelem, type);
301         if (error)
302                 return (error);
303
304         outp = buf;
305         str_remain = 0;
306         str_delim = '\0';
307
308
309         if (type != BHND_NVRAM_TYPE_CSTR) {
310                 /* Provide required size */
311                 if (outp == NULL) {
312                         *len = req_size;
313                         return (0);
314                 }
315
316                 /* Check (and update) target buffer len */
317                 if (*len < req_size)
318                         return (ENOMEM);
319                 else
320                         *len = req_size;
321         } else {
322                 /* String length calculation requires performing 
323                  * the actual string formatting */
324                 KASSERT(req_size == 0,
325                     ("req_size set for variable-length type"));
326
327                 /* If caller is querying length, the len argument 
328                  * may be uninitialized */
329                 if (outp != NULL)
330                         str_remain = *len;
331
332                 /* Fetch delimiter for the variable's string format */
333                 str_delim = sprom_get_delim_char(sc, nv->sfmt);
334         }
335
336         /* Read data */
337         all1_offs = 0;
338         val = 0;
339         for (size_t i = 0; i < sv->num_offsets; i++) {
340                 const struct bhnd_sprom_offset  *off;
341
342                 off = &sv->offsets[i];          
343                 KASSERT(!off->cont || i > 0, ("cont marked on first offset"));
344
345                 /* If not a continuation, advance the output buffer; if
346                  * a C string, this requires appending a delimiter character */
347                 if (i > 0 && !off->cont) {
348                         size_t width = bhnd_nvram_type_width(type);
349
350                         /* Non-fixed width types (such as CSTR) will have a 0
351                          * width value */
352                         if (width != 0) {
353                                 KASSERT(outp != NULL, ("NULL output buffer"));
354                                 outp = ((uint8_t *)outp) + width;
355                         }
356
357                         /* Append CSTR delim, if necessary */
358                         if (type == BHND_NVRAM_TYPE_CSTR && 
359                             str_delim != '\0' &&
360                             i != 0)
361                         {
362                                 if (outp != NULL && str_remain >= 1) {
363                                         *((char *)outp) = str_delim;
364                                         outp = ((char *)outp + 1);
365
366                                         /* Drop outp reference if we hit 0 */
367                                         if (str_remain-- == 0)
368                                                 outp = NULL;
369                                 }
370
371                                 if (SIZE_MAX - 1 < req_size)
372                                         return (EFTYPE); /* too long */
373                                 req_size++;
374                         }
375                 }
376
377                 /* Read the value, widening to a common uint32
378                  * representation */
379                 SPROM_SWITCH_TYPE(off->type, SPROM_GETVAR_READ, sc, off, val);
380
381                 /* If IGNALL1, record whether value has all bits set. */
382                 if (nv->flags & BHND_NVRAM_VF_IGNALL1) {
383                         uint32_t all1;
384
385                         all1 = off->mask;
386                         if (off->shift > 0)
387                                 all1 >>= off->shift;
388                         else if (off->shift < 0)
389                                 all1 <<= -off->shift;
390
391                         if ((val & all1) == all1)
392                                 all1_offs++;
393                 }
394
395                 /* Skip writing if additional continuations remain */
396                 if (i+1 < sv->num_offsets && sv->offsets[i].cont)
397                         continue;
398
399                 /* Perform write */
400                 if (type == BHND_NVRAM_TYPE_CSTR) {
401                         const char      *fmtstr;
402                         int              written;
403
404                         fmtstr = bhnd_nvram_type_fmt(off->type, nv->sfmt, i);
405                         if (fmtstr == NULL) {
406                                 device_printf(sc->dev, "no NVRAM format string "
407                                     "for '%s' (type=%d)\n", name, off->type);
408                                 return (EOPNOTSUPP);
409                         }
410
411                         SPROM_SWITCH_TYPE(off->type, SPROM_GETVAR_SNPRINTF, val,
412                             outp, str_remain, fmtstr, written);
413
414                         if (written <= 0)
415                                 return (EFTYPE);
416
417                         /* Calculate remaining capacity, drop outp reference
418                          * if we hit 0 -- otherwise, advance the buffer
419                          * position */
420                         if (written >= str_remain) {
421                                 str_remain = 0;
422                                 outp = NULL;
423                         } else {
424                                 str_remain -= written;
425                                 if (outp != NULL)
426                                         outp = (char *)outp + written;
427                         }
428
429                         /* Add additional bytes to total length */
430                         if (SIZE_MAX - written < req_size)
431                                 return (EFTYPE); /* string too long */
432                         req_size += written;
433                 } else {
434                         /* Verify range */
435                         SPROM_SWITCH_TYPE(type, SPROM_VERIFY_RANGE, val,
436                             off->type);
437
438                         /* Write the value, narrowing to the appropriate output
439                          * width. */
440                         SPROM_SWITCH_TYPE(type, SPROM_GETVAR_WRITE, off, val,
441                             outp);
442                 }
443         }
444
445         /* Should value should be treated as uninitialized? */
446         if (nv->flags & BHND_NVRAM_VF_IGNALL1 && all1_offs == sv->num_offsets)
447                 return (ENOENT);
448
449         /* If this is a C string request, we need to provide the computed
450          * length. */
451         if (type == BHND_NVRAM_TYPE_CSTR) {
452                 /* Account for final trailing NUL */
453                 if (SIZE_MAX - 1 < req_size)
454                         return (EFTYPE); /* string too long */
455                 req_size++;
456
457                 /* Return an error if a too-small output buffer was provided */
458                 if (buf != NULL && *len < req_size) {
459                         *len = req_size;
460                         return (ENOMEM);
461                 }
462
463                 *len = req_size;
464         }
465
466         return (0);
467 }
468
469 /* Perform a read of a variable offset from _src, safely widening the result
470  * to its 32-bit representation before assigning it to @p _dest. */
471 #define SPROM_SETVAR_READ(_type, _widen, _width, _min, _max, _off,      \
472     _src, _dest)                                                        \
473 do {                                                                    \
474         _type _v = *(const _type *)_src;                                \
475         if (_off->shift > 0) {                                          \
476                 _v <<= _off->shift;                                     \
477         } else if (off->shift < 0) {                                    \
478                 _v >>= -_off->shift;                                    \
479         }                                                               \
480         _dest = ((uint32_t) (_widen) _v) & _off->mask;                  \
481 } while(0)
482
483
484 /* Emit a value read using a SPROM offset descriptor, narrowing the
485  * result output representation and, if necessary, OR'ing it with the
486  * previously read value from @p _buf. */
487 #define SPROM_SETVAR_WRITE(_type, _widen, _width, _min, _max, _sc,      \
488     _off, _src)                                                         \
489 do {                                                                    \
490         _type _v = (_type) (_widen) _src;                               \
491         if (_off->cont)                                                 \
492                 _v |= SPROM_READ_ ## _width(_sc, _off->offset);         \
493         SPROM_WRITE_ ## _width(_sc, _off->offset, _v);                  \
494 } while(0)
495
496 /**
497  * Set a local value for a SPROM variable, performing conversion to SPROM byte
498  * order.
499  * 
500  * The new value will be written to the backing SPROM shadow.
501  * 
502  * @param               sc      The SPROM parser state.
503  * @param               name    The SPROM variable name.
504  * @param[out]          buf     The new value.
505  * @param[in,out]       len     The size of @p buf.
506  * @param               type    The data type of @p buf.
507  *
508  * @retval 0            success
509  * @retval ENOENT       The requested variable was not found.
510  * @retval EINVAL       If @p len does not match the expected variable size.
511  */
512 int
513 bhnd_sprom_setvar(struct bhnd_sprom *sc, const char *name, const void *buf,
514     size_t len, bhnd_nvram_type type)
515 {
516         const struct bhnd_nvram_vardefn *nv;
517         const struct bhnd_sprom_vardefn *sv;
518         size_t                           req_size, nelem;
519         int                              error;
520         uint8_t                          crc;
521
522         error = sprom_get_var_defn(sc, name, &nv, &sv, &req_size, &nelem, type);
523         if (error)
524                 return (error);
525
526         /* String parsing is currently unsupported */
527         if (type == BHND_NVRAM_TYPE_CSTR)
528                 return (EOPNOTSUPP);
529
530         /* Provide required size */
531         if (len != req_size)
532                 return (EINVAL);
533
534         /* Write data */
535         for (size_t i = 0; i < sv->num_offsets; i++) {
536                 const struct bhnd_sprom_offset  *off;
537                 uint32_t                         val;
538                 
539                 off = &sv->offsets[i];          
540                 KASSERT(!off->cont || i > 0, ("cont marked on first offset"));
541
542                 /* If not a continuation, advance the input pointer */
543                 if (i > 0 && !off->cont) {
544                         buf = ((const uint8_t *)buf) +
545                             bhnd_nvram_type_width(sv->offsets[i-1].type);
546                 }
547
548                 /* Read the value, widening to a common uint32
549                  * representation */
550                 SPROM_SWITCH_TYPE(nv->type, SPROM_SETVAR_READ, off, buf, val);
551
552                 /* Verify range */
553                 SPROM_SWITCH_TYPE(nv->type, SPROM_VERIFY_RANGE, val, type);
554
555                 /* Write the value, narrowing to the appropriate output
556                  * width. */
557                 SPROM_SWITCH_TYPE(off->type, SPROM_SETVAR_WRITE, sc, off, val);
558         }
559
560         /* Update CRC */
561         crc = ~bhnd_nvram_crc8(sc->sp_shadow, SPROM_CRC_LEN(sc),
562             BHND_NVRAM_CRC8_INITIAL);
563         SPROM_WRITE_1(sc, SPROM_CRC_OFF(sc), crc);
564
565         return (0);
566 }
567
568 /* Read and identify the SPROM image by incrementally performing
569  * read + CRC of all supported image formats */
570 static int
571 sprom_populate_shadow(struct bhnd_sprom *sc)
572 {
573         const struct sprom_fmt  *fmt;
574         int                      error;
575         uint16_t                 sig;
576         uint8_t                  srom_rev;
577         uint8_t                  crc;
578
579         crc = BHND_NVRAM_CRC8_INITIAL;
580
581         /* Identify the SPROM revision (and populate the SPROM shadow) */
582         for (size_t i = 0; i < nitems(sprom_fmts); i++) {
583                 fmt = &sprom_fmts[i];
584
585                 /* Read image data and check CRC */
586                 if ((error = sprom_extend_shadow(sc, fmt->size, &crc)))
587                         return (error);
588
589                 /* Skip on invalid CRC */
590                 if (crc != BHND_NVRAM_CRC8_VALID)
591                         continue;
592
593                 /* Fetch SROM revision */
594                 srom_rev = SPROM_REV(sc);
595
596                 /* Early sromrev 1 devices (specifically some BCM440x enet
597                  * cards) are reported to have been incorrectly programmed
598                  * with a revision of 0x10. */
599                 if (fmt->size == SPROM_SZ_R1_3 && srom_rev == 0x10)
600                         srom_rev = 0x1;
601
602                 /* Verify revision range */
603                 if (srom_rev < fmt->rev_min || srom_rev > fmt->rev_max)
604                         continue;
605
606                 /* Verify signature (if any) */
607                 sig = SPROM_SIG_NONE;
608                 if (fmt->sig_offset != SPROM_SIG_NONE_OFF)
609                         sig = SPROM_READ_2(sc, fmt->sig_offset);
610                 
611                 if (sig != fmt->sig_req) {
612                         device_printf(sc->dev,
613                             "invalid sprom %hhu signature: 0x%hx "
614                             "(expected 0x%hx)\n",
615                             srom_rev, sig, fmt->sig_req);
616                         return (EINVAL);
617                 }
618
619                 /* Identified */
620                 sc->sp_rev = srom_rev;
621                 return (0);
622         }
623
624         /* identification failed */
625         device_printf(sc->dev, "unrecognized SPROM format\n");
626         return (EINVAL);
627 }
628
629 /*
630  * Extend the shadowed SPROM buffer to image_size, reading any required
631  * data from the backing SPROM resource and updating the CRC.
632  */
633 static int
634 sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size,
635     uint8_t *crc)
636 {
637         int     error;
638
639         KASSERT(image_size >= sc->sp_size, (("shadow truncation unsupported")));
640
641         /* Verify the request fits within our shadow buffer */
642         if (image_size > sc->sp_capacity)
643                 return (ENOSPC);
644
645         /* Skip no-op requests */
646         if (sc->sp_size == image_size)
647                 return (0);
648
649         /* Populate the extended range */
650         error = sprom_direct_read(sc, sc->sp_size, sc->sp_shadow + sc->sp_size,
651              image_size - sc->sp_size, crc);
652         if (error)
653                 return (error);
654
655         sc->sp_size = image_size;
656         return (0);
657 }
658
659 /**
660  * Read nbytes at the given offset from the backing SPROM resource, and
661  * update the CRC.
662  */
663 static int
664 sprom_direct_read(struct bhnd_sprom *sc, size_t offset, void *buf,
665     size_t nbytes, uint8_t *crc)
666 {
667         bus_size_t       res_offset;
668         uint16_t        *p;
669
670         KASSERT(nbytes % sizeof(uint16_t) == 0, ("unaligned sprom size"));
671         KASSERT(offset % sizeof(uint16_t) == 0, ("unaligned sprom offset"));
672
673         /* Check for read overrun */
674         if (offset >= sc->sp_size_max || sc->sp_size_max - offset < nbytes) {
675                 device_printf(sc->dev, "requested SPROM read would overrun\n");
676                 return (EINVAL);
677         }
678
679         /* Perform read and update CRC */
680         p = (uint16_t *)buf;
681         res_offset = sc->sp_res_off + offset;
682
683         bhnd_bus_read_region_stream_2(sc->sp_res, res_offset, p,
684             (nbytes / sizeof(uint16_t)));
685         *crc = bhnd_nvram_crc8(p, nbytes, *crc);
686
687         return (0);
688 }
689
690
691 /**
692  * Locate the variable and SPROM revision-specific definitions
693  * for variable with @p name.
694  */
695 static int
696 sprom_get_var_defn(struct bhnd_sprom *sc, const char *name,
697     const struct bhnd_nvram_vardefn **var,
698     const struct bhnd_sprom_vardefn **sprom,
699     size_t *size, size_t *nelem, bhnd_nvram_type req_type)
700 {
701         /* Find variable definition */
702         *var = bhnd_nvram_find_vardefn(name);
703         if (*var == NULL)
704                 return (ENOENT);
705
706         /* Find revision-specific SPROM definition */
707         for (size_t i = 0; i < (*var)->num_sp_defs; i++) {
708                 const struct bhnd_sprom_vardefn *sp = &(*var)->sp_defs[i];
709
710                 if (sc->sp_rev < sp->compat.first)
711                         continue;
712                 
713                 if (sc->sp_rev > sp->compat.last)
714                         continue;
715
716                 /* Found */
717                 *sprom = sp;
718
719                 /* Calculate element count and total size, in bytes */
720                 *nelem = 0;
721                 for (size_t j = 0; j < sp->num_offsets; j++)
722                         if (!sp->offsets[j].cont)
723                                 *nelem += 1;
724
725                 *size = bhnd_nvram_type_width(req_type) * (*nelem);
726                 return (0);
727         }
728
729         /* Not supported by this SPROM revision */
730         return (ENOENT);
731 }
732
733 /**
734  * Return the array element delimiter for @p sfmt, or '\0' if none.
735  */
736 static char
737 sprom_get_delim_char(struct bhnd_sprom *sc, bhnd_nvram_sfmt sfmt)
738 {
739         switch (sfmt) {
740         case BHND_NVRAM_SFMT_HEX:
741         case BHND_NVRAM_SFMT_DEC:
742                 return (',');
743
744         case BHND_NVRAM_SFMT_CCODE:
745         case BHND_NVRAM_SFMT_LEDDC:
746                 return ('\0');
747
748         case BHND_NVRAM_SFMT_MACADDR:
749                 return (':');
750
751         default:
752                 device_printf(sc->dev, "unknown NVRAM string format: %d\n",
753                     sfmt);
754                 return (',');
755         }
756 }