xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_bcm.c (revision 076ad2f836d5f49dc1375f1677335a48fe0d4b82)
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 	return (BHND_NVRAM_DATA_PROBE_DEFAULT);
148 }
149 
150 static int
151 bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
152     bhnd_nvram_plist *options, void *outp, size_t *olen)
153 {
154 	struct bhnd_nvram_bcmhdr	 hdr;
155 	bhnd_nvram_prop			*prop;
156 	size_t				 limit, nbytes;
157 	uint32_t			 sdram_ncdl;
158 	uint16_t			 sdram_init, sdram_cfg, sdram_refresh;
159 	uint8_t				 bcm_ver, crc8;
160 	int				 error;
161 
162 	/* Determine output byte limit */
163 	if (outp != NULL)
164 		limit = *olen;
165 	else
166 		limit = 0;
167 
168 	/* Fetch required header variables */
169 #define	PROPS_GET_HDRVAR(_name, _dest, _type)	do {			\
170 		const char *name = BCM_NVRAM_ ## _name ## _VAR;	\
171 		if (!bhnd_nvram_plist_contains(props, name)) {		\
172 			BHND_NV_LOG("missing required property: %s\n",	\
173 			    name);					\
174 			return (EFTYPE);				\
175 		}							\
176 									\
177 		error = bhnd_nvram_plist_get_encoded(props, name,	\
178 		    (_dest), sizeof(*(_dest)),				\
179 		    BHND_NVRAM_TYPE_ ##_type);				\
180 		if (error) {						\
181 			BHND_NV_LOG("error reading required header "	\
182 			    "%s property: %d\n", name, error);		\
183 			return (EFTYPE);				\
184 		}							\
185 } while (0)
186 
187 	PROPS_GET_HDRVAR(SDRAM_NCDL,		&sdram_ncdl,	UINT32);
188 	PROPS_GET_HDRVAR(CFG0_SDRAM_INIT,	&sdram_init,	UINT16);
189 	PROPS_GET_HDRVAR(CFG1_SDRAM_CFG,	&sdram_cfg,	UINT16);
190 	PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH,	&sdram_refresh,	UINT16);
191 
192 #undef	PROPS_GET_HDRVAR
193 
194 	/* Fetch BCM nvram version from options */
195 	if (options != NULL &&
196 	    bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION))
197 	{
198 		error = bhnd_nvram_plist_get_uint8(options,
199 		    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver);
200 		if (error) {
201 			BHND_NV_LOG("error reading %s uint8 option value: %d\n",
202 			    BCM_NVRAM_ENCODE_OPT_VERSION, error);
203 			return (EINVAL);
204 		}
205 	} else {
206 		bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT;
207 	}
208 
209 	/* Construct our header */
210 	hdr = (struct bhnd_nvram_bcmhdr) {
211 		.magic = htole32(BCM_NVRAM_MAGIC),
212 		.size = 0,
213 		.cfg0 = 0,
214 		.cfg1 = 0,
215 		.sdram_ncdl = htole32(sdram_ncdl)
216 	};
217 
218 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0);
219 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver);
220 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT,
221 	    htole16(sdram_init));
222 
223 	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG,
224 	    htole16(sdram_cfg));
225 	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH,
226 	    htole16(sdram_refresh));
227 
228 	/* Write the header */
229 	nbytes = sizeof(hdr);
230 	if (limit >= nbytes)
231 		memcpy(outp, &hdr, sizeof(hdr));
232 
233 	/* Write all properties */
234 	prop = NULL;
235 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
236 		const char	*name;
237 		char		*p;
238 		size_t		 prop_limit;
239 		size_t		 name_len, value_len;
240 
241 		if (outp == NULL || limit < nbytes) {
242 			p = NULL;
243 			prop_limit = 0;
244 		} else {
245 			p = ((char *)outp) + nbytes;
246 			prop_limit = limit - nbytes;
247 		}
248 
249 		/* Fetch and write name + '=' to output */
250 		name = bhnd_nvram_prop_name(prop);
251 		name_len = strlen(name) + 1;
252 
253 		if (prop_limit > name_len) {
254 			memcpy(p, name, name_len - 1);
255 			p[name_len - 1] = '=';
256 
257 			prop_limit -= name_len;
258 			p += name_len;
259 		} else {
260 			prop_limit = 0;
261 			p = NULL;
262 		}
263 
264 		/* Advance byte count */
265 		if (SIZE_MAX - nbytes < name_len)
266 			return (EFTYPE); /* would overflow size_t */
267 
268 		nbytes += name_len;
269 
270 		/* Attempt to write NUL-terminated value to output */
271 		value_len = prop_limit;
272 		error = bhnd_nvram_prop_encode(prop, p, &value_len,
273 		    BHND_NVRAM_TYPE_STRING);
274 
275 		/* If encoding failed for any reason other than ENOMEM (which
276 		 * we'll detect and report after encoding all properties),
277 		 * return immediately */
278 		if (error && error != ENOMEM) {
279 			BHND_NV_LOG("error serializing %s to required type "
280 			    "%s: %d\n", name,
281 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
282 			    error);
283 			return (error);
284 		}
285 
286 		/* Advance byte count */
287 		if (SIZE_MAX - nbytes < value_len)
288 			return (EFTYPE); /* would overflow size_t */
289 
290 		nbytes += value_len;
291 	}
292 
293 	/* Write terminating '\0' */
294 	if (limit > nbytes)
295 		*((char *)outp + nbytes) = '\0';
296 
297 	if (nbytes == SIZE_MAX)
298 		return (EFTYPE); /* would overflow size_t */
299 	else
300 		nbytes++;
301 
302 	/* Update header length; this must fit within the header's 32-bit size
303 	 * field */
304 	if (nbytes <= UINT32_MAX) {
305 		hdr.size = (uint32_t)nbytes;
306 	} else {
307 		BHND_NV_LOG("size %zu exceeds maximum supported size of %u "
308 		    "bytes\n", nbytes, UINT32_MAX);
309 		return (EFTYPE);
310 	}
311 
312 	/* Provide required length */
313 	*olen = nbytes;
314 	if (limit < *olen) {
315 		if (outp == NULL)
316 			return (0);
317 
318 		return (ENOMEM);
319 	}
320 
321 	/* Calculate the CRC value */
322 	BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size"));
323 	crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP,
324 	    nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
325 
326 	/* Update CRC and write the finalized header */
327 	BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size"));
328 	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8);
329 	memcpy(outp, &hdr, sizeof(hdr));
330 
331 	return (0);
332 }
333 
334 /**
335  * Initialize @p bcm with the provided NVRAM data mapped by @p src.
336  *
337  * @param bcm A newly allocated data instance.
338  */
339 static int
340 bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src)
341 {
342 	struct bhnd_nvram_bcmhdr	 hdr;
343 	uint8_t				*p;
344 	void				*ptr;
345 	size_t				 io_offset, io_size;
346 	uint8_t				 crc, valid, bcm_ver;
347 	int				 error;
348 
349 	if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr))))
350 		return (error);
351 
352 	if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
353 		return (ENXIO);
354 
355 	/* Fetch the actual NVRAM image size */
356 	io_size = le32toh(hdr.size);
357 	if (io_size < sizeof(hdr)) {
358 		/* The header size must include the header itself */
359 		BHND_NV_LOG("corrupt header size: %zu\n", io_size);
360 		return (EINVAL);
361 	}
362 
363 	if (io_size > bhnd_nvram_io_getsize(src)) {
364 		BHND_NV_LOG("header size %zu exceeds input size %zu\n",
365 		    io_size, bhnd_nvram_io_getsize(src));
366 		return (EINVAL);
367 	}
368 
369 	/* Allocate a buffer large enough to hold the NVRAM image, and
370 	 * an extra EOF-signaling NUL (on the chance it's missing from the
371 	 * source data) */
372 	if (io_size == SIZE_MAX)
373 		return (ENOMEM);
374 
375 	bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1);
376 	if (bcm->data == NULL)
377 		return (ENOMEM);
378 
379 	/* Fetch a pointer into our backing buffer and copy in the
380 	 * NVRAM image. */
381 	error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL);
382 	if (error)
383 		return (error);
384 
385 	p = ptr;
386 	if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size)))
387 		return (error);
388 
389 	/* Verify the CRC */
390 	valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC);
391 	crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP,
392 	    io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
393 
394 	if (crc != valid) {
395 		BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, "
396 		    "expected=%hhx)\n", crc, valid);
397 	}
398 
399 	/* Populate header variable definitions */
400 #define	BCM_READ_HDR_VAR(_name, _dest, _swap) do {		\
401 	struct bhnd_nvram_bcm_hvar *data;				\
402 	data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR);		\
403 	BHND_NV_ASSERT(data != NULL,						\
404 	    ("no such header variable: " __STRING(_name)));		\
405 									\
406 									\
407 	data->value. _dest = _swap(BCM_NVRAM_GET_BITS(			\
408 	    hdr. _name ## _FIELD, _name));				\
409 } while(0)
410 
411 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT,	u16, le16toh);
412 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG,	u16, le16toh);
413 	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH,	u16, le16toh);
414 	BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL,		u32, le32toh);
415 
416 	_Static_assert(nitems(bcm->hvars) == 4, "missing initialization for"
417 	    "NVRAM header variable(s)");
418 
419 #undef BCM_READ_HDR_VAR
420 
421 	/* Process the buffer */
422 	bcm->count = 0;
423 	io_offset = sizeof(hdr);
424 	while (io_offset < io_size) {
425 		char		*envp;
426 		const char	*name, *value;
427 		size_t		 envp_len;
428 		size_t		 name_len, value_len;
429 
430 		/* Parse the key=value string */
431 		envp = (char *) (p + io_offset);
432 		envp_len = strnlen(envp, io_size - io_offset);
433 		error = bhnd_nvram_parse_env(envp, envp_len, '=', &name,
434 					     &name_len, &value, &value_len);
435 		if (error) {
436 			BHND_NV_LOG("error parsing envp at offset %#zx: %d\n",
437 			    io_offset, error);
438 			return (error);
439 		}
440 
441 		/* Insert a '\0' character, replacing the '=' delimiter and
442 		 * allowing us to vend references directly to the variable
443 		 * name */
444 		*(envp + name_len) = '\0';
445 
446 		/* Record any NVRAM variables that mirror our header variables.
447 		 * This is a brute-force search -- for the amount of data we're
448 		 * operating on, it shouldn't be an issue. */
449 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
450 			struct bhnd_nvram_bcm_hvar	*hvar;
451 			union bhnd_nvram_bcm_hvar_value	 hval;
452 			size_t				 hval_len;
453 
454 			hvar = &bcm->hvars[i];
455 
456 			/* Already matched? */
457 			if (hvar->envp != NULL)
458 				continue;
459 
460 			/* Name matches? */
461 			if ((strcmp(name, hvar->name)) != 0)
462 				continue;
463 
464 			/* Save pointer to mirrored envp */
465 			hvar->envp = envp;
466 
467 			/* Check for stale value */
468 			hval_len = sizeof(hval);
469 			error = bhnd_nvram_value_coerce(value, value_len,
470 			    BHND_NVRAM_TYPE_STRING, &hval, &hval_len,
471 			    hvar->type);
472 			if (error) {
473 				/* If parsing fails, we can likely only make
474 				 * things worse by trying to synchronize the
475 				 * variables */
476 				BHND_NV_LOG("error parsing header variable "
477 				    "'%s=%s': %d\n", name, value, error);
478 			} else if (hval_len != hvar->len) {
479 				hvar->stale = true;
480 			} else if (memcmp(&hval, &hvar->value, hval_len) != 0) {
481 				hvar->stale = true;
482 			}
483 		}
484 
485 		/* Seek past the value's terminating '\0' */
486 		io_offset += envp_len;
487 		if (io_offset == io_size) {
488 			BHND_NV_LOG("missing terminating NUL at offset %#zx\n",
489 			    io_offset);
490 			return (EINVAL);
491 		}
492 
493 		if (*(p + io_offset) != '\0') {
494 			BHND_NV_LOG("invalid terminator '%#hhx' at offset "
495 			    "%#zx\n", *(p + io_offset), io_offset);
496 			return (EINVAL);
497 		}
498 
499 		/* Update variable count */
500 		bcm->count++;
501 
502 		/* Seek to the next record */
503 		if (++io_offset == io_size) {
504 			char ch;
505 
506 			/* Hit EOF without finding a terminating NUL
507 			 * byte; we need to grow our buffer and append
508 			 * it */
509 			io_size++;
510 			if ((error = bhnd_nvram_io_setsize(bcm->data, io_size)))
511 				return (error);
512 
513 			/* Write NUL byte */
514 			ch = '\0';
515 			error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch,
516 			    sizeof(ch));
517 			if (error)
518 				return (error);
519 		}
520 
521 		/* Check for explicit EOF (encoded as a single empty NUL
522 		 * terminated string) */
523 		if (*(p + io_offset) == '\0')
524 			break;
525 	}
526 
527 	/* Add non-mirrored header variables to total count variable */
528 	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
529 		if (bcm->hvars[i].envp == NULL)
530 			bcm->count++;
531 	}
532 
533 	/* Populate serialization options from our header */
534 	bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER);
535 	error = bhnd_nvram_plist_append_bytes(bcm->opts,
536 	    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver),
537 	    BHND_NVRAM_TYPE_UINT8);
538 	if (error)
539 		return (error);
540 
541 	return (0);
542 }
543 
544 static int
545 bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
546 {
547 	struct bhnd_nvram_bcm	*bcm;
548 	int			 error;
549 
550 	bcm = (struct bhnd_nvram_bcm *)nv;
551 
552 	/* Populate default BCM mirrored header variable set */
553 	_Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars),
554 	    "hvar declarations must match bhnd_nvram_bcm_hvars template");
555 	memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars));
556 
557 	/* Allocate (empty) option list, to be populated by
558 	 * bhnd_nvram_bcm_init() */
559 	bcm->opts = bhnd_nvram_plist_new();
560 	if (bcm->opts == NULL)
561 		return (ENOMEM);
562 
563 	/* Parse the BCM input data and initialize our backing
564 	 * data representation */
565 	if ((error = bhnd_nvram_bcm_init(bcm, io))) {
566 		bhnd_nvram_bcm_free(nv);
567 		return (error);
568 	}
569 
570 	return (0);
571 }
572 
573 static void
574 bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv)
575 {
576 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
577 
578 	if (bcm->data != NULL)
579 		bhnd_nvram_io_free(bcm->data);
580 
581 	if (bcm->opts != NULL)
582 		bhnd_nvram_plist_release(bcm->opts);
583 }
584 
585 size_t
586 bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv)
587 {
588 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
589 	return (bcm->count);
590 }
591 
592 static bhnd_nvram_plist *
593 bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv)
594 {
595 	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
596 	return (bcm->opts);
597 }
598 
599 static uint32_t
600 bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv)
601 {
602 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
603 }
604 
605 static const char *
606 bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep)
607 {
608 	struct bhnd_nvram_bcm		*bcm;
609 	struct bhnd_nvram_bcm_hvar	*hvar, *hvar_next;
610 	const void			*ptr;
611 	const char			*envp, *basep;
612 	size_t				 io_size, io_offset;
613 	int				 error;
614 
615 	bcm = (struct bhnd_nvram_bcm *)nv;
616 
617 	io_offset = sizeof(struct bhnd_nvram_bcmhdr);
618 	io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset;
619 
620 	/* Map backing buffer */
621 	error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size,
622 	    NULL);
623 	if (error) {
624 		BHND_NV_LOG("error mapping backing buffer: %d\n", error);
625 		return (NULL);
626 	}
627 
628 	basep = ptr;
629 
630 	/* If cookiep pointers into our header variable array, handle as header
631 	 * variable iteration. */
632 	hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep);
633 	if (hvar != NULL) {
634 		size_t idx;
635 
636 		/* Advance to next entry, if any */
637 		idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1;
638 
639 		/* Find the next header-defined variable that isn't defined in
640 		 * the NVRAM data, start iteration there */
641 		for (size_t i = idx; i < nitems(bcm->hvars); i++) {
642 			hvar_next = &bcm->hvars[i];
643 			if (hvar_next->envp != NULL && !hvar_next->stale)
644 				continue;
645 
646 			*cookiep = hvar_next;
647 			return (hvar_next->name);
648 		}
649 
650 		/* No further header-defined variables; iteration
651 		 * complete */
652 		return (NULL);
653 	}
654 
655 	/* Handle standard NVRAM data iteration */
656 	if (*cookiep == NULL) {
657 		/* Start at the first NVRAM data record */
658 		envp = basep;
659 	} else {
660 		/* Seek to next record */
661 		envp = *cookiep;
662 		envp += strlen(envp) + 1;	/* key + '\0' */
663 		envp += strlen(envp) + 1;	/* value + '\0' */
664 	}
665 
666 	/*
667 	 * Skip entries that have an existing header variable entry that takes
668 	 * precedence over the NVRAM data value.
669 	 *
670 	 * The header's value will be provided when performing header variable
671 	 * iteration
672 	 */
673 	 while ((size_t)(envp - basep) < io_size && *envp != '\0') {
674 		/* Locate corresponding header variable */
675 		hvar = NULL;
676 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
677 			if (bcm->hvars[i].envp != envp)
678 				continue;
679 
680 			hvar = &bcm->hvars[i];
681 			break;
682 		}
683 
684 		/* If no corresponding hvar entry, or the entry does not take
685 		 * precedence over this NVRAM value, we can safely return this
686 		 * value as-is. */
687 		if (hvar == NULL || !hvar->stale)
688 			break;
689 
690 		/* Seek to next record */
691 		envp += strlen(envp) + 1;	/* key + '\0' */
692 		envp += strlen(envp) + 1;	/* value + '\0' */
693 	 }
694 
695 	/* On NVRAM data EOF, try switching to header variables */
696 	if ((size_t)(envp - basep) == io_size || *envp == '\0') {
697 		/* Find first valid header variable */
698 		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
699 			if (bcm->hvars[i].envp != NULL)
700 				continue;
701 
702 			*cookiep = &bcm->hvars[i];
703 			return (bcm->hvars[i].name);
704 		}
705 
706 		/* No header variables */
707 		return (NULL);
708 	}
709 
710 	*cookiep = __DECONST(void *, envp);
711 	return (envp);
712 }
713 
714 static void *
715 bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name)
716 {
717 	return (bhnd_nvram_data_generic_find(nv, name));
718 }
719 
720 static int
721 bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
722     void *cookiep2)
723 {
724 	struct bhnd_nvram_bcm		*bcm;
725 	struct bhnd_nvram_bcm_hvar	*hvar1, *hvar2;
726 
727 	bcm = (struct bhnd_nvram_bcm *)nv;
728 
729 	hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1);
730 	hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2);
731 
732 	/* Header variables are always ordered below any variables defined
733 	 * in the BCM data */
734 	if (hvar1 != NULL && hvar2 == NULL) {
735 		return (1);	/* hvar follows non-hvar */
736 	} else if (hvar1 == NULL && hvar2 != NULL) {
737 		return (-1);	/* non-hvar precedes hvar */
738 	}
739 
740 	/* Otherwise, both cookies are either hvars or non-hvars. We can
741 	 * safely fall back on pointer order, which will provide a correct
742 	 * ordering matching the behavior of bhnd_nvram_data_next() for
743 	 * both cases */
744 	if (cookiep1 < cookiep2)
745 		return (-1);
746 
747 	if (cookiep1 > cookiep2)
748 		return (1);
749 
750 	return (0);
751 }
752 
753 static int
754 bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
755     size_t *len, bhnd_nvram_type type)
756 {
757 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
758 }
759 
760 static int
761 bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
762     bhnd_nvram_val **value)
763 {
764 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
765 }
766 
767 static const void *
768 bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
769     size_t *len, bhnd_nvram_type *type)
770 {
771 	struct bhnd_nvram_bcm		*bcm;
772 	struct bhnd_nvram_bcm_hvar	*hvar;
773 	const char			*envp;
774 
775 	bcm = (struct bhnd_nvram_bcm *)nv;
776 
777 	/* Handle header variables */
778 	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
779 		BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value,
780 		    hvar->len, hvar->type) == 0, ("value misaligned"));
781 
782 		*type = hvar->type;
783 		*len = hvar->len;
784 		return (&hvar->value);
785 	}
786 
787 	/* Cookie points to key\0value\0 -- get the value address */
788 	BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep"));
789 
790 	envp = cookiep;
791 	envp += strlen(envp) + 1;	/* key + '\0' */
792 	*len = strlen(envp) + 1;	/* value + '\0' */
793 	*type = BHND_NVRAM_TYPE_STRING;
794 
795 	return (envp);
796 }
797 
798 static const char *
799 bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
800 {
801 	struct bhnd_nvram_bcm		*bcm;
802 	struct bhnd_nvram_bcm_hvar	*hvar;
803 
804 	bcm = (struct bhnd_nvram_bcm *)nv;
805 
806 	/* Handle header variables */
807 	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
808 		return (hvar->name);
809 	}
810 
811 	/* Cookie points to key\0value\0 */
812 	return (cookiep);
813 }
814 
815 static int
816 bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
817     bhnd_nvram_val *value, bhnd_nvram_val **result)
818 {
819 	bhnd_nvram_val	*str;
820 	int		 error;
821 
822 	/* Name (trimmed of any path prefix) must be valid */
823 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
824 		return (EINVAL);
825 
826 	/* Value must be bcm-formatted string */
827 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
828 	    value, BHND_NVRAM_VAL_DYNAMIC);
829 	if (error)
830 		return (error);
831 
832 	/* Success. Transfer result ownership to the caller. */
833 	*result = str;
834 	return (0);
835 }
836 
837 static int
838 bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
839 {
840 	/* We permit deletion of any variable */
841 	return (0);
842 }
843 
844 /**
845  * Return the internal BCM data reference for a header-defined variable
846  * with @p name, or NULL if none exists.
847  */
848 static struct bhnd_nvram_bcm_hvar *
849 bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name)
850 {
851 	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
852 		if (strcmp(bcm->hvars[i].name, name) == 0)
853 			return (&bcm->hvars[i]);
854 	}
855 
856 	/* Not found */
857 	return (NULL);
858 }
859 
860 /**
861  * If @p cookiep references a header-defined variable, return the
862  * internal BCM data reference. Otherwise, returns NULL.
863  */
864 static struct bhnd_nvram_bcm_hvar *
865 bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep)
866 {
867 #ifdef BHND_NVRAM_INVARIANTS
868 	uintptr_t base, ptr;
869 #endif
870 
871 	/* If the cookie falls within the hvar array, it's a
872 	 * header variable cookie */
873 	if (nitems(bcm->hvars) == 0)
874 		return (NULL);
875 
876 	if (cookiep < (void *)&bcm->hvars[0])
877 		return (NULL);
878 
879 	if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1])
880 		return (NULL);
881 
882 #ifdef BHND_NVRAM_INVARIANTS
883 	base = (uintptr_t)bcm->hvars;
884 	ptr = (uintptr_t)cookiep;
885 
886 	BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0,
887 	    ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars));
888 #endif /* INVARIANTS */
889 
890 	return ((struct bhnd_nvram_bcm_hvar *)cookiep);
891 }
892 
893 /**
894  * Return the index of @p hdrvar within @p bcm's backing hvars array.
895  */
896 static size_t
897 bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm,
898     struct bhnd_nvram_bcm_hvar *hdrvar)
899 {
900 	BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL,
901 	    ("%p is not a valid hdrvar reference", hdrvar));
902 
903 	return (hdrvar - &bcm->hvars[0]);
904 }
905