]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c
Import tzdata 2018c
[FreeBSD/FreeBSD.git] / sys / dev / bhnd / nvram / bhnd_nvram_data_bcm.c
1 /*-
2  * Copyright (c) 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/endian.h>
35
36 #ifdef _KERNEL
37
38 #include <sys/bus.h>
39 #include <sys/ctype.h>
40 #include <sys/malloc.h>
41 #include <sys/systm.h>
42
43 #else /* !_KERNEL */
44
45 #include <ctype.h>
46 #include <stdint.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50
51 #endif /* _KERNEL */
52
53 #include "bhnd_nvram_private.h"
54
55 #include "bhnd_nvram_datavar.h"
56
57 #include "bhnd_nvram_data_bcmreg.h"
58 #include "bhnd_nvram_data_bcmvar.h"
59
60 /*
61  * Broadcom NVRAM data class.
62  * 
63  * The Broadcom NVRAM NUL-delimited ASCII format is used by most
64  * Broadcom SoCs.
65  * 
66  * The NVRAM data is encoded as a standard header, followed by series of
67  * NUL-terminated 'key=value' strings; the end of the stream is denoted
68  * by a single extra NUL character.
69  */
70
71 struct bhnd_nvram_bcm;
72
73 static struct bhnd_nvram_bcm_hvar       *bhnd_nvram_bcm_gethdrvar(
74                                              struct bhnd_nvram_bcm *bcm,
75                                              const char *name);
76 static struct bhnd_nvram_bcm_hvar       *bhnd_nvram_bcm_to_hdrvar(
77                                              struct bhnd_nvram_bcm *bcm,
78                                              void *cookiep);
79 static size_t                            bhnd_nvram_bcm_hdrvar_index(
80                                              struct bhnd_nvram_bcm *bcm,
81                                              struct bhnd_nvram_bcm_hvar *hvar);
82 /*
83  * Set of BCM NVRAM header values that are required to be mirrored in the
84  * NVRAM data itself.
85  *
86  * If they're not included in the parsed NVRAM data, we need to vend the
87  * header-parsed values with their appropriate keys, and add them in any
88  * updates to the NVRAM data.
89  *
90  * If they're modified in NVRAM, we need to sync the changes with the
91  * the NVRAM header values.
92  */
93 static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = {
94         {
95                 .name   = BCM_NVRAM_CFG0_SDRAM_INIT_VAR,
96                 .type   = BHND_NVRAM_TYPE_UINT16,
97                 .len    = sizeof(uint16_t),
98                 .nelem  = 1,
99         },
100         {
101                 .name   = BCM_NVRAM_CFG1_SDRAM_CFG_VAR,
102                 .type   = BHND_NVRAM_TYPE_UINT16,
103                 .len    = sizeof(uint16_t),
104                 .nelem  = 1,
105         },
106         {
107                 .name   = BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR,
108                 .type   = BHND_NVRAM_TYPE_UINT16,
109                 .len    = sizeof(uint16_t),
110                 .nelem  = 1,
111         },
112         {
113                 .name   = BCM_NVRAM_SDRAM_NCDL_VAR,
114                 .type   = BHND_NVRAM_TYPE_UINT32,
115                 .len    = sizeof(uint32_t),
116                 .nelem  = 1,
117         },
118 };
119
120 /** BCM NVRAM data class instance */
121 struct bhnd_nvram_bcm {
122         struct bhnd_nvram_data           nv;    /**< common instance state */
123         struct bhnd_nvram_io            *data;  /**< backing buffer */
124         bhnd_nvram_plist                *opts;  /**< serialization options */
125
126         /** BCM header values */
127         struct bhnd_nvram_bcm_hvar       hvars[nitems(bhnd_nvram_bcm_hvars)];
128
129         size_t                           count; /**< total variable count */
130 };
131
132 BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS,
133     sizeof(struct bhnd_nvram_bcm))
134
135 static int
136 bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io)
137 {
138         struct bhnd_nvram_bcmhdr        hdr;
139         int                             error;
140
141         if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr))))
142                 return (error);
143
144         if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
145                 return (ENXIO);
146
147         if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io))
148                 return (ENXIO);
149
150         return (BHND_NVRAM_DATA_PROBE_DEFAULT);
151 }
152
153 /**
154  * Parser states for bhnd_nvram_bcm_getvar_direct_common().
155  */
156 typedef enum {
157         BCM_PARSE_KEY_START,
158         BCM_PARSE_KEY_CONT,
159         BCM_PARSE_KEY,
160         BCM_PARSE_NEXT_KEY,
161         BCM_PARSE_VALUE_START,
162         BCM_PARSE_VALUE
163 } bcm_parse_state;
164
165 static int
166 bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name,
167     void *outp, size_t *olen, bhnd_nvram_type otype)
168 {
169         return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype,
170             true));
171 }
172
173 /**
174  * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct().
175  */
176 int
177 bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name,
178     void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header)
179 {
180         struct bhnd_nvram_bcmhdr         hdr;
181         char                             buf[512];
182         bcm_parse_state                  pstate;
183         size_t                           limit, offset;
184         size_t                           buflen, bufpos;
185         size_t                           namelen, namepos;
186         size_t                           vlen;
187         int                              error;
188
189         limit = bhnd_nvram_io_getsize(io);
190         offset = 0;
191
192         /* Fetch and validate the header */
193         if (have_header) {
194                 if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr))))
195                         return (error);
196
197                 if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
198                         return (ENXIO);
199
200                 offset += sizeof(hdr);
201                 limit = bhnd_nv_ummin(le32toh(hdr.size), limit);
202         }
203
204         /* Loop our parser until we find the requested variable, or hit EOF */
205         pstate = BCM_PARSE_KEY_START;
206         buflen = 0;
207         bufpos = 0;
208         namelen = strlen(name);
209         namepos = 0;
210         vlen = 0;
211
212         while ((offset - bufpos) < limit) {
213                 BHND_NV_ASSERT(bufpos <= buflen,
214                     ("buf position invalid (%zu > %zu)", bufpos, buflen));
215                 BHND_NV_ASSERT(buflen <= sizeof(buf),
216                     ("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
217
218                 /* Repopulate our parse buffer? */
219                 if (buflen - bufpos == 0) {
220                         BHND_NV_ASSERT(offset < limit, ("offset overrun"));
221
222                         buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
223                         bufpos = 0;
224
225                         error = bhnd_nvram_io_read(io, offset, buf, buflen);
226                         if (error)
227                                 return (error);
228
229                         offset += buflen;
230                 }
231
232                 switch (pstate) {
233                 case BCM_PARSE_KEY_START:
234                         BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
235
236                         /* An extra '\0' denotes NVRAM EOF */
237                         if (buf[bufpos] == '\0')
238                                 return (ENOENT);
239
240                         /* Reset name matching position */
241                         namepos = 0;
242
243                         /* Start name matching */
244                         pstate = BCM_PARSE_KEY_CONT;
245                         break;
246
247                 case BCM_PARSE_KEY_CONT: {
248                         size_t navail, nleft;
249
250                         nleft = namelen - namepos;
251                         navail = bhnd_nv_ummin(buflen - bufpos, nleft);
252
253                         if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
254                                 /* Matched */
255                                 namepos += navail;
256                                 bufpos += navail;
257
258                                 /* If we've matched the full variable name,
259                                  * look for its trailing delimiter */
260                                 if (namepos == namelen)
261                                         pstate = BCM_PARSE_KEY;
262                         } else {
263                                 /* No match; advance to next entry and restart
264                                  * name matching */
265                                 pstate = BCM_PARSE_NEXT_KEY;
266                         }
267
268                         break;
269                 }
270
271                 case BCM_PARSE_KEY:
272                         BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
273
274                         if (buf[bufpos] == '=') {
275                                 /* Key fully matched; advance past '=' and
276                                  * parse the value */
277                                 bufpos++;
278                                 pstate = BCM_PARSE_VALUE_START;
279                         } else {
280                                 /* No match; advance to next entry and restart
281                                  * name matching */
282                                 pstate = BCM_PARSE_NEXT_KEY;
283                         }
284
285                         break;
286
287                 case BCM_PARSE_NEXT_KEY: {
288                         const char *p;
289
290                         /* Scan for a '\0' terminator */
291                         p = memchr(buf+bufpos, '\0', buflen - bufpos);
292
293                         if (p != NULL) {
294                                 /* Found entry terminator; restart name
295                                  * matching at next entry */
296                                 pstate = BCM_PARSE_KEY_START;
297                                 bufpos = (p - buf) + 1 /* skip '\0' */;
298                         } else {
299                                 /* Consumed full buffer looking for '\0'; 
300                                  * force repopulation of the buffer and
301                                  * retry */
302                                 bufpos = buflen;
303                         }
304
305                         break;
306                 }
307
308                 case BCM_PARSE_VALUE_START: {
309                         const char *p;
310
311                         /* Scan for a '\0' terminator */
312                         p = memchr(buf+bufpos, '\0', buflen - bufpos);
313
314                         if (p != NULL) {
315                                 /* Found entry terminator; parse the value */
316                                 vlen = p - &buf[bufpos];
317                                 pstate = BCM_PARSE_VALUE;
318
319                         } else if (p == NULL && offset == limit) {
320                                 /* Hit EOF without a terminating '\0';
321                                  * treat the entry as implicitly terminated */
322                                 vlen = buflen - bufpos;
323                                 pstate = BCM_PARSE_VALUE;
324
325                         } else if (p == NULL && bufpos > 0) {
326                                 size_t  nread;
327
328                                 /* Move existing value data to start of
329                                  * buffer */
330                                 memmove(buf, buf+bufpos, buflen - bufpos);
331                                 buflen = bufpos;
332                                 bufpos = 0;
333
334                                 /* Populate full buffer to allow retry of
335                                  * value parsing */
336                                 nread = bhnd_nv_ummin(sizeof(buf) - buflen,
337                                     limit - offset);
338
339                                 error = bhnd_nvram_io_read(io, offset,
340                                     buf+buflen, nread);
341                                 if (error)
342                                         return (error);
343
344                                 offset += nread;
345                                 buflen += nread;
346                         } else {
347                                 /* Value exceeds our buffer capacity */
348                                 BHND_NV_LOG("cannot parse value for '%s' "
349                                     "(exceeds %zu byte limit)\n", name,
350                                     sizeof(buf));
351
352                                 return (ENXIO);
353                         }
354
355                         break;
356                 }
357
358                 case BCM_PARSE_VALUE:
359                         BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
360
361                         return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
362                             BHND_NVRAM_TYPE_STRING, outp, olen, otype));
363                 }
364         }
365
366         /* Variable not found */
367         return (ENOENT);
368 }
369
370 static int
371 bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
372     bhnd_nvram_plist *options, void *outp, size_t *olen)
373 {
374         struct bhnd_nvram_bcmhdr         hdr;
375         bhnd_nvram_prop                 *prop;
376         size_t                           limit, nbytes;
377         uint32_t                         sdram_ncdl;
378         uint16_t                         sdram_init, sdram_cfg, sdram_refresh;
379         uint8_t                          bcm_ver, crc8;
380         int                              error;
381
382         /* Determine output byte limit */
383         if (outp != NULL)
384                 limit = *olen;
385         else
386                 limit = 0;
387
388         /* Fetch required header variables */
389 #define PROPS_GET_HDRVAR(_name, _dest, _type)   do {                    \
390                 const char *name = BCM_NVRAM_ ## _name ## _VAR; \
391                 if (!bhnd_nvram_plist_contains(props, name)) {          \
392                         BHND_NV_LOG("missing required property: %s\n",  \
393                             name);                                      \
394                         return (EFTYPE);                                \
395                 }                                                       \
396                                                                         \
397                 error = bhnd_nvram_plist_get_encoded(props, name,       \
398                     (_dest), sizeof(*(_dest)),                          \
399                     BHND_NVRAM_TYPE_ ##_type);                          \
400                 if (error) {                                            \
401                         BHND_NV_LOG("error reading required header "    \
402                             "%s property: %d\n", name, error);          \
403                         return (EFTYPE);                                \
404                 }                                                       \
405 } while (0)
406
407         PROPS_GET_HDRVAR(SDRAM_NCDL,            &sdram_ncdl,    UINT32);
408         PROPS_GET_HDRVAR(CFG0_SDRAM_INIT,       &sdram_init,    UINT16);
409         PROPS_GET_HDRVAR(CFG1_SDRAM_CFG,        &sdram_cfg,     UINT16);
410         PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH,    &sdram_refresh, UINT16);
411
412 #undef  PROPS_GET_HDRVAR
413
414         /* Fetch BCM nvram version from options */
415         if (options != NULL &&
416             bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION))
417         {
418                 error = bhnd_nvram_plist_get_uint8(options,
419                     BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver);
420                 if (error) {
421                         BHND_NV_LOG("error reading %s uint8 option value: %d\n",
422                             BCM_NVRAM_ENCODE_OPT_VERSION, error);
423                         return (EINVAL);
424                 }
425         } else {
426                 bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT;
427         }
428
429         /* Construct our header */
430         hdr = (struct bhnd_nvram_bcmhdr) {
431                 .magic = htole32(BCM_NVRAM_MAGIC),
432                 .size = 0,
433                 .cfg0 = 0,
434                 .cfg1 = 0,
435                 .sdram_ncdl = htole32(sdram_ncdl)
436         };
437
438         hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0);
439         hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver);
440         hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT,
441             htole16(sdram_init));
442         
443         hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG,
444             htole16(sdram_cfg));
445         hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH,
446             htole16(sdram_refresh));
447
448         /* Write the header */
449         nbytes = sizeof(hdr);
450         if (limit >= nbytes)
451                 memcpy(outp, &hdr, sizeof(hdr));
452
453         /* Write all properties */
454         prop = NULL;
455         while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
456                 const char      *name;
457                 char            *p;
458                 size_t           prop_limit;
459                 size_t           name_len, value_len;
460
461                 if (outp == NULL || limit < nbytes) {
462                         p = NULL;
463                         prop_limit = 0;
464                 } else {
465                         p = ((char *)outp) + nbytes;
466                         prop_limit = limit - nbytes;
467                 }
468
469                 /* Fetch and write name + '=' to output */
470                 name = bhnd_nvram_prop_name(prop);
471                 name_len = strlen(name) + 1;
472
473                 if (prop_limit > name_len) {
474                         memcpy(p, name, name_len - 1);
475                         p[name_len - 1] = '=';
476
477                         prop_limit -= name_len;
478                         p += name_len;
479                 } else {
480                         prop_limit = 0;
481                         p = NULL;
482                 }
483
484                 /* Advance byte count */
485                 if (SIZE_MAX - nbytes < name_len)
486                         return (EFTYPE); /* would overflow size_t */
487
488                 nbytes += name_len;
489
490                 /* Attempt to write NUL-terminated value to output */
491                 value_len = prop_limit;
492                 error = bhnd_nvram_prop_encode(prop, p, &value_len,
493                     BHND_NVRAM_TYPE_STRING);
494
495                 /* If encoding failed for any reason other than ENOMEM (which
496                  * we'll detect and report after encoding all properties),
497                  * return immediately */
498                 if (error && error != ENOMEM) {
499                         BHND_NV_LOG("error serializing %s to required type "
500                             "%s: %d\n", name,
501                             bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
502                             error);
503                         return (error);
504                 }
505
506                 /* Advance byte count */
507                 if (SIZE_MAX - nbytes < value_len)
508                         return (EFTYPE); /* would overflow size_t */
509
510                 nbytes += value_len;
511         }
512
513         /* Write terminating '\0' */
514         if (limit > nbytes)
515                 *((char *)outp + nbytes) = '\0';
516
517         if (nbytes == SIZE_MAX)
518                 return (EFTYPE); /* would overflow size_t */
519         else
520                 nbytes++;
521
522         /* Update header length; this must fit within the header's 32-bit size
523          * field */
524         if (nbytes <= UINT32_MAX) {
525                 hdr.size = (uint32_t)nbytes;
526         } else {
527                 BHND_NV_LOG("size %zu exceeds maximum supported size of %u "
528                     "bytes\n", nbytes, UINT32_MAX);
529                 return (EFTYPE);
530         }
531
532         /* Provide required length */
533         *olen = nbytes;
534         if (limit < *olen) {
535                 if (outp == NULL)
536                         return (0);
537
538                 return (ENOMEM);
539         }
540
541         /* Calculate the CRC value */
542         BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size"));
543         crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP,
544             nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
545
546         /* Update CRC and write the finalized header */
547         BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size"));
548         hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8);
549         memcpy(outp, &hdr, sizeof(hdr));
550
551         return (0);
552 }
553
554 /**
555  * Initialize @p bcm with the provided NVRAM data mapped by @p src.
556  * 
557  * @param bcm A newly allocated data instance.
558  */
559 static int
560 bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src)
561 {
562         struct bhnd_nvram_bcmhdr         hdr;
563         uint8_t                         *p;
564         void                            *ptr;
565         size_t                           io_offset, io_size;
566         uint8_t                          crc, valid, bcm_ver;
567         int                              error;
568
569         if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr))))
570                 return (error);
571
572         if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
573                 return (ENXIO);
574
575         /* Fetch the actual NVRAM image size */
576         io_size = le32toh(hdr.size);
577         if (io_size < sizeof(hdr)) {
578                 /* The header size must include the header itself */
579                 BHND_NV_LOG("corrupt header size: %zu\n", io_size);
580                 return (EINVAL);
581         }
582
583         if (io_size > bhnd_nvram_io_getsize(src)) {
584                 BHND_NV_LOG("header size %zu exceeds input size %zu\n",
585                     io_size, bhnd_nvram_io_getsize(src));
586                 return (EINVAL);
587         }
588
589         /* Allocate a buffer large enough to hold the NVRAM image, and
590          * an extra EOF-signaling NUL (on the chance it's missing from the
591          * source data) */
592         if (io_size == SIZE_MAX)
593                 return (ENOMEM);
594
595         bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1);
596         if (bcm->data == NULL)
597                 return (ENOMEM);
598
599         /* Fetch a pointer into our backing buffer and copy in the
600          * NVRAM image. */
601         error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL);
602         if (error)
603                 return (error);
604
605         p = ptr;
606         if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size)))
607                 return (error);
608
609         /* Verify the CRC */
610         valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC);
611         crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP,
612             io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
613
614         if (crc != valid) {
615                 BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, "
616                     "expected=%hhx)\n", crc, valid);
617         }
618
619         /* Populate header variable definitions */
620 #define BCM_READ_HDR_VAR(_name, _dest, _swap) do {              \
621         struct bhnd_nvram_bcm_hvar *data;                               \
622         data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR);             \
623         BHND_NV_ASSERT(data != NULL,                                            \
624             ("no such header variable: " __STRING(_name)));             \
625                                                                         \
626                                                                         \
627         data->value. _dest = _swap(BCM_NVRAM_GET_BITS(                  \
628             hdr. _name ## _FIELD, _name));                              \
629 } while(0)
630
631         BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT,     u16, le16toh);
632         BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG,      u16, le16toh);
633         BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH,  u16, le16toh);
634         BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL,          u32, le32toh);
635
636         _Static_assert(nitems(bcm->hvars) == 4, "missing initialization for"
637             "NVRAM header variable(s)");
638
639 #undef BCM_READ_HDR_VAR
640
641         /* Process the buffer */
642         bcm->count = 0;
643         io_offset = sizeof(hdr);
644         while (io_offset < io_size) {
645                 char            *envp;
646                 const char      *name, *value;
647                 size_t           envp_len;
648                 size_t           name_len, value_len;
649
650                 /* Parse the key=value string */
651                 envp = (char *) (p + io_offset);
652                 envp_len = strnlen(envp, io_size - io_offset);
653                 error = bhnd_nvram_parse_env(envp, envp_len, '=', &name,
654                                              &name_len, &value, &value_len);
655                 if (error) {
656                         BHND_NV_LOG("error parsing envp at offset %#zx: %d\n",
657                             io_offset, error);
658                         return (error);
659                 }
660
661                 /* Insert a '\0' character, replacing the '=' delimiter and
662                  * allowing us to vend references directly to the variable
663                  * name */
664                 *(envp + name_len) = '\0';
665
666                 /* Record any NVRAM variables that mirror our header variables.
667                  * This is a brute-force search -- for the amount of data we're
668                  * operating on, it shouldn't be an issue. */
669                 for (size_t i = 0; i < nitems(bcm->hvars); i++) {
670                         struct bhnd_nvram_bcm_hvar      *hvar;
671                         union bhnd_nvram_bcm_hvar_value  hval;
672                         size_t                           hval_len;
673
674                         hvar = &bcm->hvars[i];
675
676                         /* Already matched? */
677                         if (hvar->envp != NULL)
678                                 continue;
679
680                         /* Name matches? */
681                         if ((strcmp(name, hvar->name)) != 0)
682                                 continue;
683
684                         /* Save pointer to mirrored envp */
685                         hvar->envp = envp;
686
687                         /* Check for stale value */
688                         hval_len = sizeof(hval);
689                         error = bhnd_nvram_value_coerce(value, value_len,
690                             BHND_NVRAM_TYPE_STRING, &hval, &hval_len,
691                             hvar->type);
692                         if (error) {
693                                 /* If parsing fails, we can likely only make
694                                  * things worse by trying to synchronize the
695                                  * variables */
696                                 BHND_NV_LOG("error parsing header variable "
697                                     "'%s=%s': %d\n", name, value, error);
698                         } else if (hval_len != hvar->len) {
699                                 hvar->stale = true;
700                         } else if (memcmp(&hval, &hvar->value, hval_len) != 0) {
701                                 hvar->stale = true;
702                         }
703                 }
704
705                 /* Seek past the value's terminating '\0' */
706                 io_offset += envp_len;
707                 if (io_offset == io_size) {
708                         BHND_NV_LOG("missing terminating NUL at offset %#zx\n",
709                             io_offset);
710                         return (EINVAL);
711                 }
712
713                 if (*(p + io_offset) != '\0') {
714                         BHND_NV_LOG("invalid terminator '%#hhx' at offset "
715                             "%#zx\n", *(p + io_offset), io_offset);
716                         return (EINVAL);
717                 }
718
719                 /* Update variable count */
720                 bcm->count++;
721
722                 /* Seek to the next record */
723                 if (++io_offset == io_size) {
724                         char ch;
725         
726                         /* Hit EOF without finding a terminating NUL
727                          * byte; we need to grow our buffer and append
728                          * it */
729                         io_size++;
730                         if ((error = bhnd_nvram_io_setsize(bcm->data, io_size)))
731                                 return (error);
732
733                         /* Write NUL byte */
734                         ch = '\0';
735                         error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch,
736                             sizeof(ch));
737                         if (error)
738                                 return (error);
739                 }
740
741                 /* Check for explicit EOF (encoded as a single empty NUL
742                  * terminated string) */
743                 if (*(p + io_offset) == '\0')
744                         break;
745         }
746
747         /* Add non-mirrored header variables to total count variable */
748         for (size_t i = 0; i < nitems(bcm->hvars); i++) {
749                 if (bcm->hvars[i].envp == NULL)
750                         bcm->count++;
751         }
752
753         /* Populate serialization options from our header */
754         bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER);
755         error = bhnd_nvram_plist_append_bytes(bcm->opts,
756             BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver),
757             BHND_NVRAM_TYPE_UINT8);
758         if (error)
759                 return (error);
760
761         return (0);
762 }
763
764 static int
765 bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
766 {
767         struct bhnd_nvram_bcm   *bcm;
768         int                      error;
769
770         bcm = (struct bhnd_nvram_bcm *)nv;
771
772         /* Populate default BCM mirrored header variable set */
773         _Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars),
774             "hvar declarations must match bhnd_nvram_bcm_hvars template");
775         memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars));
776
777         /* Allocate (empty) option list, to be populated by
778          * bhnd_nvram_bcm_init() */
779         bcm->opts = bhnd_nvram_plist_new();
780         if (bcm->opts == NULL)
781                 return (ENOMEM);
782
783         /* Parse the BCM input data and initialize our backing
784          * data representation */
785         if ((error = bhnd_nvram_bcm_init(bcm, io))) {
786                 bhnd_nvram_bcm_free(nv);
787                 return (error);
788         }
789
790         return (0);
791 }
792
793 static void
794 bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv)
795 {
796         struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
797
798         if (bcm->data != NULL)
799                 bhnd_nvram_io_free(bcm->data);
800
801         if (bcm->opts != NULL)
802                 bhnd_nvram_plist_release(bcm->opts);
803 }
804
805 size_t
806 bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv)
807 {
808         struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
809         return (bcm->count);
810 }
811
812 static bhnd_nvram_plist *
813 bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv)
814 {
815         struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
816         return (bcm->opts);
817 }
818
819 static uint32_t
820 bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv)
821 {
822         return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
823 }
824
825 static const char *
826 bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep)
827 {
828         struct bhnd_nvram_bcm           *bcm;
829         struct bhnd_nvram_bcm_hvar      *hvar, *hvar_next;
830         const void                      *ptr;
831         const char                      *envp, *basep;
832         size_t                           io_size, io_offset;
833         int                              error;
834
835         bcm = (struct bhnd_nvram_bcm *)nv;
836         
837         io_offset = sizeof(struct bhnd_nvram_bcmhdr);
838         io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset;
839
840         /* Map backing buffer */
841         error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size,
842             NULL);
843         if (error) {
844                 BHND_NV_LOG("error mapping backing buffer: %d\n", error);
845                 return (NULL);
846         }
847
848         basep = ptr;
849
850         /* If cookiep pointers into our header variable array, handle as header
851          * variable iteration. */
852         hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep);
853         if (hvar != NULL) {
854                 size_t idx;
855
856                 /* Advance to next entry, if any */
857                 idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1;
858
859                 /* Find the next header-defined variable that isn't defined in
860                  * the NVRAM data, start iteration there */
861                 for (size_t i = idx; i < nitems(bcm->hvars); i++) {
862                         hvar_next = &bcm->hvars[i];
863                         if (hvar_next->envp != NULL && !hvar_next->stale)
864                                 continue;
865
866                         *cookiep = hvar_next;
867                         return (hvar_next->name);
868                 }
869
870                 /* No further header-defined variables; iteration
871                  * complete */
872                 return (NULL);
873         }
874
875         /* Handle standard NVRAM data iteration */
876         if (*cookiep == NULL) {
877                 /* Start at the first NVRAM data record */
878                 envp = basep;
879         } else {
880                 /* Seek to next record */
881                 envp = *cookiep;
882                 envp += strlen(envp) + 1;       /* key + '\0' */
883                 envp += strlen(envp) + 1;       /* value + '\0' */
884         }
885
886         /*
887          * Skip entries that have an existing header variable entry that takes
888          * precedence over the NVRAM data value.
889          * 
890          * The header's value will be provided when performing header variable
891          * iteration
892          */
893          while ((size_t)(envp - basep) < io_size && *envp != '\0') {
894                 /* Locate corresponding header variable */
895                 hvar = NULL;
896                 for (size_t i = 0; i < nitems(bcm->hvars); i++) {
897                         if (bcm->hvars[i].envp != envp)
898                                 continue;
899
900                         hvar = &bcm->hvars[i];
901                         break;
902                 }
903
904                 /* If no corresponding hvar entry, or the entry does not take
905                  * precedence over this NVRAM value, we can safely return this
906                  * value as-is. */
907                 if (hvar == NULL || !hvar->stale)
908                         break;
909
910                 /* Seek to next record */
911                 envp += strlen(envp) + 1;       /* key + '\0' */
912                 envp += strlen(envp) + 1;       /* value + '\0' */
913          }
914
915         /* On NVRAM data EOF, try switching to header variables */
916         if ((size_t)(envp - basep) == io_size || *envp == '\0') {
917                 /* Find first valid header variable */
918                 for (size_t i = 0; i < nitems(bcm->hvars); i++) {
919                         if (bcm->hvars[i].envp != NULL)
920                                 continue;
921                         
922                         *cookiep = &bcm->hvars[i];
923                         return (bcm->hvars[i].name);
924                 }
925
926                 /* No header variables */
927                 return (NULL);
928         }
929
930         *cookiep = __DECONST(void *, envp);
931         return (envp);
932 }
933
934 static void *
935 bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name)
936 {
937         return (bhnd_nvram_data_generic_find(nv, name));
938 }
939
940 static int
941 bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
942     void *cookiep2)
943 {
944         struct bhnd_nvram_bcm           *bcm;
945         struct bhnd_nvram_bcm_hvar      *hvar1, *hvar2;
946
947         bcm = (struct bhnd_nvram_bcm *)nv;
948
949         hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1);
950         hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2);
951
952         /* Header variables are always ordered below any variables defined
953          * in the BCM data */
954         if (hvar1 != NULL && hvar2 == NULL) {
955                 return (1);     /* hvar follows non-hvar */
956         } else if (hvar1 == NULL && hvar2 != NULL) {
957                 return (-1);    /* non-hvar precedes hvar */
958         }
959
960         /* Otherwise, both cookies are either hvars or non-hvars. We can
961          * safely fall back on pointer order, which will provide a correct
962          * ordering matching the behavior of bhnd_nvram_data_next() for
963          * both cases */
964         if (cookiep1 < cookiep2)
965                 return (-1);
966
967         if (cookiep1 > cookiep2)
968                 return (1);
969
970         return (0);
971 }
972
973 static int
974 bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
975     size_t *len, bhnd_nvram_type type)
976 {
977         return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
978 }
979
980 static int
981 bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
982     bhnd_nvram_val **value)
983 {
984         return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
985 }
986
987 static const void *
988 bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
989     size_t *len, bhnd_nvram_type *type)
990 {
991         struct bhnd_nvram_bcm           *bcm;
992         struct bhnd_nvram_bcm_hvar      *hvar;
993         const char                      *envp;
994
995         bcm = (struct bhnd_nvram_bcm *)nv;
996
997         /* Handle header variables */
998         if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
999                 BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value,
1000                     hvar->len, hvar->type) == 0, ("value misaligned"));
1001
1002                 *type = hvar->type;
1003                 *len = hvar->len;
1004                 return (&hvar->value);
1005         }
1006
1007         /* Cookie points to key\0value\0 -- get the value address */
1008         BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep"));
1009
1010         envp = cookiep;
1011         envp += strlen(envp) + 1;       /* key + '\0' */
1012         *len = strlen(envp) + 1;        /* value + '\0' */
1013         *type = BHND_NVRAM_TYPE_STRING;
1014
1015         return (envp);
1016 }
1017
1018 static const char *
1019 bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
1020 {
1021         struct bhnd_nvram_bcm           *bcm;
1022         struct bhnd_nvram_bcm_hvar      *hvar;
1023
1024         bcm = (struct bhnd_nvram_bcm *)nv;
1025
1026         /* Handle header variables */
1027         if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
1028                 return (hvar->name);
1029         }
1030
1031         /* Cookie points to key\0value\0 */
1032         return (cookiep);
1033 }
1034
1035 static int
1036 bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
1037     bhnd_nvram_val *value, bhnd_nvram_val **result)
1038 {
1039         bhnd_nvram_val  *str;
1040         int              error;
1041
1042         /* Name (trimmed of any path prefix) must be valid */
1043         if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
1044                 return (EINVAL);
1045
1046         /* Value must be bcm-formatted string */
1047         error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
1048             value, BHND_NVRAM_VAL_DYNAMIC);
1049         if (error)
1050                 return (error);
1051
1052         /* Success. Transfer result ownership to the caller. */
1053         *result = str;
1054         return (0);
1055 }
1056
1057 static int
1058 bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
1059 {
1060         /* We permit deletion of any variable */
1061         return (0);
1062 }
1063
1064 /**
1065  * Return the internal BCM data reference for a header-defined variable
1066  * with @p name, or NULL if none exists.
1067  */
1068 static struct bhnd_nvram_bcm_hvar *
1069 bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name)
1070 {
1071         for (size_t i = 0; i < nitems(bcm->hvars); i++) {
1072                 if (strcmp(bcm->hvars[i].name, name) == 0)
1073                         return (&bcm->hvars[i]);
1074         }
1075
1076         /* Not found */
1077         return (NULL);
1078 }
1079
1080 /**
1081  * If @p cookiep references a header-defined variable, return the
1082  * internal BCM data reference. Otherwise, returns NULL.
1083  */
1084 static struct bhnd_nvram_bcm_hvar *
1085 bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep)
1086 {
1087 #ifdef BHND_NVRAM_INVARIANTS                                                                                                                                                                                                                                
1088         uintptr_t base, ptr;
1089 #endif
1090
1091         /* If the cookie falls within the hvar array, it's a
1092          * header variable cookie */
1093         if (nitems(bcm->hvars) == 0)
1094                 return (NULL);
1095
1096         if (cookiep < (void *)&bcm->hvars[0])
1097                 return (NULL);
1098
1099         if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1])
1100                 return (NULL);
1101
1102 #ifdef BHND_NVRAM_INVARIANTS
1103         base = (uintptr_t)bcm->hvars;
1104         ptr = (uintptr_t)cookiep;
1105
1106         BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0,
1107             ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars));
1108 #endif /* INVARIANTS */
1109
1110         return ((struct bhnd_nvram_bcm_hvar *)cookiep);
1111 }
1112
1113 /**
1114  * Return the index of @p hdrvar within @p bcm's backing hvars array.
1115  */
1116 static size_t
1117 bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm,
1118     struct bhnd_nvram_bcm_hvar *hdrvar)
1119 {
1120         BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL,
1121             ("%p is not a valid hdrvar reference", hdrvar));
1122
1123         return (hdrvar - &bcm->hvars[0]);
1124 }