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