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