xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_sprom.c (revision 5944f899a2519c6321bac3c17cc076418643a088)
1 /*-
2  * Copyright (c) 2015-2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #include <sys/endian.h>
34 
35 #ifdef _KERNEL
36 #include <sys/param.h>
37 #include <sys/ctype.h>
38 #include <sys/malloc.h>
39 #include <sys/systm.h>
40 
41 #include <machine/_inttypes.h>
42 #else /* !_KERNEL */
43 #include <ctype.h>
44 #include <errno.h>
45 #include <inttypes.h>
46 #include <stdint.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #endif /* _KERNEL */
51 
52 #include "bhnd_nvram_map.h"
53 
54 #include "bhnd_nvram_private.h"
55 #include "bhnd_nvram_datavar.h"
56 
57 #include "bhnd_nvram_data_spromvar.h"
58 
59 /*
60  * BHND SPROM NVRAM data class
61  *
62  * The SPROM data format is a fixed-layout, non-self-descriptive binary format,
63  * used on Broadcom wireless and wired adapters, that provides a subset of the
64  * variables defined by Broadcom SoC NVRAM formats.
65  */
66 
67 static const bhnd_sprom_layout  *bhnd_nvram_sprom_get_layout(uint8_t sromrev);
68 
69 static int			 bhnd_nvram_sprom_ident(
70 				     struct bhnd_nvram_io *io,
71 				     const bhnd_sprom_layout **ident);
72 
73 static int			 bhnd_nvram_sprom_write_var(
74 				     bhnd_sprom_opcode_state *state,
75 				     bhnd_sprom_opcode_idx_entry *entry,
76 				     bhnd_nvram_val *value,
77 				     struct bhnd_nvram_io *io);
78 
79 static int			 bhnd_nvram_sprom_read_var(
80 				     struct bhnd_sprom_opcode_state *state,
81 				     struct bhnd_sprom_opcode_idx_entry *entry,
82 				     struct bhnd_nvram_io *io,
83 				     union bhnd_nvram_sprom_storage *storage,
84 				     bhnd_nvram_val *val);
85 
86 static int			 bhnd_nvram_sprom_write_offset(
87 				     const struct bhnd_nvram_vardefn *var,
88 				     struct bhnd_nvram_io *data,
89 				     bhnd_nvram_type type, size_t offset,
90 				     uint32_t mask, int8_t shift,
91 				     uint32_t value);
92 
93 static int			 bhnd_nvram_sprom_read_offset(
94 				     const struct bhnd_nvram_vardefn *var,
95 				     struct bhnd_nvram_io *data,
96 				     bhnd_nvram_type type, size_t offset,
97 				     uint32_t mask, int8_t shift,
98 				     uint32_t *value);
99 
100 static bool			 bhnd_sprom_is_external_immutable(
101 				     const char *name);
102 
103 BHND_NVRAM_DATA_CLASS_DEFN(sprom, "Broadcom SPROM",
104     BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_sprom))
105 
106 #define	SPROM_COOKIE_TO_VID(_cookie)	\
107 	(((struct bhnd_sprom_opcode_idx_entry *)(_cookie))->vid)
108 
109 #define	SPROM_COOKIE_TO_NVRAM_VAR(_cookie)	\
110 	bhnd_nvram_get_vardefn(SPROM_COOKIE_TO_VID(_cookie))
111 
112 /**
113  * Read the magic value from @p io, and verify that it matches
114  * the @p layout's expected magic value.
115  *
116  * If @p layout does not defined a magic value, @p magic is set to 0x0
117  * and success is returned.
118  *
119  * @param	io	An I/O context mapping the SPROM data to be identified.
120  * @param	layout	The SPROM layout against which @p io should be verified.
121  * @param[out]	magic	On success, the SPROM magic value.
122  *
123  * @retval 0		success
124  * @retval non-zero	If checking @p io otherwise fails, a regular unix
125  *			error code will be returned.
126  */
127 static int
128 bhnd_nvram_sprom_check_magic(struct bhnd_nvram_io *io,
129     const bhnd_sprom_layout *layout, uint16_t *magic)
130 {
131 	int error;
132 
133 	/* Skip if layout does not define a magic value */
134 	if (layout->flags & SPROM_LAYOUT_MAGIC_NONE)
135 		return (0);
136 
137 	/* Read the magic value */
138 	error = bhnd_nvram_io_read(io, layout->magic_offset, magic,
139 	    sizeof(*magic));
140 	if (error)
141 		return (error);
142 
143 	*magic = le16toh(*magic);
144 
145 	/* If the signature does not match, skip to next layout */
146 	if (*magic != layout->magic_value)
147 		return (ENXIO);
148 
149 	return (0);
150 }
151 
152 /**
153  * Attempt to identify the format of the SPROM data mapped by @p io.
154  *
155  * The SPROM data format does not provide any identifying information at a
156  * known offset, instead requiring that we iterate over the known SPROM image
157  * sizes until we are able to compute a valid checksum (and, for later
158  * revisions, validate a signature at a revision-specific offset).
159  *
160  * @param	io	An I/O context mapping the SPROM data to be identified.
161  * @param[out]	ident	On success, the identified SPROM layout.
162  *
163  * @retval 0		success
164  * @retval non-zero	If identifying @p io otherwise fails, a regular unix
165  *			error code will be returned.
166  */
167 static int
168 bhnd_nvram_sprom_ident(struct bhnd_nvram_io *io,
169     const bhnd_sprom_layout **ident)
170 {
171 	uint8_t	crc;
172 	size_t	crc_errors;
173 	size_t	nbytes;
174 	int	error;
175 
176 	crc = BHND_NVRAM_CRC8_INITIAL;
177 	crc_errors = 0;
178 	nbytes = 0;
179 
180 	/* We iterate the SPROM layouts smallest to largest, allowing us to
181 	 * perform incremental checksum calculation */
182 	for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {
183 		const bhnd_sprom_layout	*layout;
184 		u_char			 buf[512];
185 		size_t			 nread;
186 		uint16_t		 magic;
187 		uint8_t			 srev;
188 		bool			 crc_valid;
189 		bool			 have_magic;
190 
191 		layout = &bhnd_sprom_layouts[i];
192 
193 		have_magic = true;
194 		if ((layout->flags & SPROM_LAYOUT_MAGIC_NONE))
195 			have_magic = false;
196 
197 		/*
198 		 * Read image data and update CRC (errors are reported
199 		 * after the signature check)
200 		 *
201 		 * Layout instances must be ordered from smallest to largest by
202 		 * the nvram_map compiler, allowing us to incrementally update
203 		 * our CRC.
204 		 */
205 		if (nbytes > layout->size)
206 			BHND_NV_PANIC("SPROM layout defined out-of-order");
207 
208 		nread = layout->size - nbytes;
209 
210 		while (nread > 0) {
211 			size_t nr;
212 
213 			nr = bhnd_nv_ummin(nread, sizeof(buf));
214 
215 			if ((error = bhnd_nvram_io_read(io, nbytes, buf, nr)))
216 				return (error);
217 
218 			crc = bhnd_nvram_crc8(buf, nr, crc);
219 			crc_valid = (crc == BHND_NVRAM_CRC8_VALID);
220 			if (!crc_valid)
221 				crc_errors++;
222 
223 			nread -= nr;
224 			nbytes += nr;
225 		}
226 
227 		/* Read SPROM revision */
228 		error = bhnd_nvram_io_read(io, layout->srev_offset, &srev,
229 		    sizeof(srev));
230 		if (error)
231 			return (error);
232 
233 		/* Early sromrev 1 devices (specifically some BCM440x enet
234 		 * cards) are reported to have been incorrectly programmed
235 		 * with a revision of 0x10. */
236 		if (layout->rev == 1 && srev == 0x10)
237 			srev = 0x1;
238 
239 		/* Check revision against the layout definition */
240 		if (srev != layout->rev)
241 			continue;
242 
243 		/* Check the magic value, skipping to the next layout on
244 		 * failure. */
245 		error = bhnd_nvram_sprom_check_magic(io, layout, &magic);
246 		if (error) {
247 			/* If the CRC is was valid, log the mismatch */
248 			if (crc_valid || BHND_NV_VERBOSE) {
249 				BHND_NV_LOG("invalid sprom %hhu signature: "
250 					    "0x%hx (expected 0x%hx)\n", srev,
251 					    magic, layout->magic_value);
252 
253 					return (ENXIO);
254 			}
255 
256 			continue;
257 		}
258 
259 		/* Check for an earlier CRC error */
260 		if (!crc_valid) {
261 			/* If the magic check succeeded, then we may just have
262 			 * data corruption -- log the CRC error */
263 			if (have_magic || BHND_NV_VERBOSE) {
264 				BHND_NV_LOG("sprom %hhu CRC error (crc=%#hhx, "
265 					    "expected=%#x)\n", srev, crc,
266 					    BHND_NVRAM_CRC8_VALID);
267 			}
268 
269 			continue;
270 		}
271 
272 		/* Identified */
273 		*ident = layout;
274 		return (0);
275 	}
276 
277 	/* No match */
278 	if (crc_errors > 0 && BHND_NV_VERBOSE) {
279 		BHND_NV_LOG("sprom parsing failed with %zu CRC errors\n",
280 		    crc_errors);
281 	}
282 
283 	return (ENXIO);
284 }
285 
286 static int
287 bhnd_nvram_sprom_probe(struct bhnd_nvram_io *io)
288 {
289 	const bhnd_sprom_layout	*layout;
290 	int			 error;
291 
292 	/* Try to parse the input */
293 	if ((error = bhnd_nvram_sprom_ident(io, &layout)))
294 		return (error);
295 
296 	return (BHND_NVRAM_DATA_PROBE_DEFAULT);
297 }
298 
299 static int
300 bhnd_nvram_sprom_getvar_direct(struct bhnd_nvram_io *io, const char *name,
301     void *buf, size_t *len, bhnd_nvram_type type)
302 {
303 	const bhnd_sprom_layout		*layout;
304 	bhnd_sprom_opcode_state		 state;
305 	const struct bhnd_nvram_vardefn	*var;
306 	size_t				 vid;
307 	int				 error;
308 
309 	/* Look up the variable definition and ID */
310 	if ((var = bhnd_nvram_find_vardefn(name)) == NULL)
311 		return (ENOENT);
312 
313 	vid = bhnd_nvram_get_vardefn_id(var);
314 
315 	/* Identify the SPROM image layout */
316 	if ((error = bhnd_nvram_sprom_ident(io, &layout)))
317 		return (error);
318 
319 	/* Initialize SPROM layout interpreter */
320 	if ((error = bhnd_sprom_opcode_init(&state, layout))) {
321 		BHND_NV_LOG("error initializing opcode state: %d\n", error);
322 		return (ENXIO);
323 	}
324 
325 	/* Find SPROM layout entry for the requested variable */
326 	while ((error = bhnd_sprom_opcode_next_var(&state)) == 0) {
327 		bhnd_sprom_opcode_idx_entry	entry;
328 		union bhnd_nvram_sprom_storage	storage;
329 		bhnd_nvram_val			val;
330 
331 		/* Fetch the variable's entry state */
332 		if ((error = bhnd_sprom_opcode_init_entry(&state, &entry)))
333 			return (error);
334 
335 		/* Match against expected VID */
336 		if (entry.vid != vid)
337 			continue;
338 
339 		/* Decode variable to a new value instance */
340 		error = bhnd_nvram_sprom_read_var(&state, &entry, io, &storage,
341 		    &val);
342 		if (error)
343 			return (error);
344 
345 		/* Perform value coercion */
346 		error = bhnd_nvram_val_encode(&val, buf, len, type);
347 
348 		/* Clean up */
349 		bhnd_nvram_val_release(&val);
350 		return (error);
351 	}
352 
353 	/* Hit EOF without matching the requested variable? */
354 	if (error == ENOENT)
355 		return (ENOENT);
356 
357 	/* Some other parse error occured */
358 	return (error);
359 }
360 
361 /**
362  * Return the SPROM layout definition for the given @p sromrev, or NULL if
363  * not found.
364  */
365 static const bhnd_sprom_layout *
366 bhnd_nvram_sprom_get_layout(uint8_t sromrev)
367 {
368 	/* Find matching SPROM layout definition */
369 	for (size_t i = 0; i < bhnd_sprom_num_layouts; i++) {
370 		if (bhnd_sprom_layouts[i].rev == sromrev)
371 			return (&bhnd_sprom_layouts[i]);
372 	}
373 
374 	/* Not found */
375 	return (NULL);
376 }
377 
378 /**
379  * Serialize a SPROM variable.
380  *
381  * @param state	The SPROM opcode state describing the layout of @p io.
382  * @param entry	The variable's SPROM opcode index entry.
383  * @param value	The value to encode to @p io as per @p entry.
384  * @param io	I/O context to which @p value should be written, or NULL
385  *		if no output should be produced. This may be used to validate
386  *		values prior to write.
387  *
388  * @retval 0		success
389  * @retval EFTYPE	If value coercion from @p value to the type required by
390  *			@p entry is unsupported.
391  * @retval ERANGE	If value coercion from @p value would overflow
392  *			(or underflow) the type required by @p entry.
393  * @retval non-zero	If serialization otherwise fails, a regular unix error
394  *			code will be returned.
395  */
396 static int
397 bhnd_nvram_sprom_write_var(bhnd_sprom_opcode_state *state,
398     bhnd_sprom_opcode_idx_entry *entry, bhnd_nvram_val *value,
399     struct bhnd_nvram_io *io)
400 {
401 	const struct bhnd_nvram_vardefn	*var;
402 	uint32_t			 u32[BHND_SPROM_ARRAY_MAXLEN];
403 	bhnd_nvram_type			 itype, var_base_type;
404 	size_t				 ipos, ilen, nelem;
405 	int				 error;
406 
407 	/* Fetch variable definition and the native element type */
408 	var = bhnd_nvram_get_vardefn(entry->vid);
409 	BHND_NV_ASSERT(var != NULL, ("missing variable definition"));
410 
411 	var_base_type = bhnd_nvram_base_type(var->type);
412 
413 	/* Fetch the element count from the SPROM variable layout definition */
414 	if ((error = bhnd_sprom_opcode_eval_var(state, entry)))
415 		return (error);
416 
417 	nelem = state->var.nelem;
418 	BHND_NV_ASSERT(nelem <= var->nelem, ("SPROM nelem=%zu exceeds maximum "
419 	     "NVRAM nelem=%hhu", nelem, var->nelem));
420 
421 	/* Promote the data to a common 32-bit representation */
422 	if (bhnd_nvram_is_signed_type(var_base_type))
423 		itype = BHND_NVRAM_TYPE_INT32_ARRAY;
424 	else
425 		itype = BHND_NVRAM_TYPE_UINT32_ARRAY;
426 
427 	/* Calculate total size of the 32-bit promoted representation */
428 	if ((ilen = bhnd_nvram_value_size(NULL, 0, itype, nelem)) == 0) {
429 		/* Variable-width types are unsupported */
430 		BHND_NV_LOG("invalid %s SPROM variable type %d\n",
431 			    var->name, var->type);
432 		return (EFTYPE);
433 	}
434 
435 	/* The native representation must fit within our scratch array */
436 	if (ilen > sizeof(u32)) {
437 		BHND_NV_LOG("error encoding '%s', SPROM_ARRAY_MAXLEN "
438 			    "incorrect\n", var->name);
439 		return (EFTYPE);
440 	}
441 
442 	/* Initialize our common 32-bit value representation */
443 	if (bhnd_nvram_val_type(value) == BHND_NVRAM_TYPE_NULL) {
444 		/* No value provided; can this variable be encoded as missing
445 		 * by setting all bits to one? */
446 		if (!(var->flags & BHND_NVRAM_VF_IGNALL1)) {
447 			BHND_NV_LOG("missing required property: %s\n",
448 			    var->name);
449 			return (EINVAL);
450 		}
451 
452 		/* Set all bits */
453 		memset(u32, 0xFF, ilen);
454 	} else {
455 		bhnd_nvram_val	 bcm_val;
456 		const void	*var_ptr;
457 		bhnd_nvram_type	 var_type, raw_type;
458 		size_t		 var_len, enc_nelem;
459 
460 		/* Try to coerce the value to the native variable format. */
461 		error = bhnd_nvram_val_convert_init(&bcm_val, var->fmt, value,
462 		    BHND_NVRAM_VAL_DYNAMIC|BHND_NVRAM_VAL_BORROW_DATA);
463 		if (error) {
464 			BHND_NV_LOG("error converting input type %s to %s "
465 			    "format\n",
466 			    bhnd_nvram_type_name(bhnd_nvram_val_type(value)),
467 			    bhnd_nvram_val_fmt_name(var->fmt));
468 			return (error);
469 		}
470 
471 		var_ptr = bhnd_nvram_val_bytes(&bcm_val, &var_len, &var_type);
472 
473 		/*
474 		 * Promote to a common 32-bit representation.
475 		 *
476 		 * We must use the raw type to interpret the input data as its
477 		 * underlying integer representation -- otherwise, coercion
478 		 * would attempt to parse the input as its complex
479 		 * representation.
480 		 *
481 		 * For example, direct CHAR -> UINT32 coercion would attempt to
482 		 * parse the character as a decimal integer, rather than
483 		 * promoting the raw UTF8 byte value to a 32-bit value.
484 		 */
485 		raw_type = bhnd_nvram_raw_type(var_type);
486 		error = bhnd_nvram_value_coerce(var_ptr, var_len, raw_type,
487 		     u32, &ilen, itype);
488 
489 		/* Clean up temporary value representation */
490 		bhnd_nvram_val_release(&bcm_val);
491 
492 		/* Report coercion failure */
493 		if (error) {
494 			BHND_NV_LOG("error promoting %s to %s: %d\n",
495 			    bhnd_nvram_type_name(var_type),
496 			    bhnd_nvram_type_name(itype), error);
497 			return (error);
498 		}
499 
500 		/* Encoded element count must match SPROM's definition */
501 		error = bhnd_nvram_value_nelem(u32, ilen, itype, &enc_nelem);
502 		if (error)
503 			return (error);
504 
505 		if (enc_nelem != nelem) {
506 			const char *type_name;
507 
508 			type_name = bhnd_nvram_type_name(var_base_type);
509 			BHND_NV_LOG("invalid %s property value '%s[%zu]': "
510 			    "required %s[%zu]", var->name, type_name,
511 			    enc_nelem, type_name, nelem);
512 			return (EFTYPE);
513 		}
514 	}
515 
516 	/*
517 	 * Seek to the start of the variable's SPROM layout definition and
518 	 * iterate over all bindings.
519 	 */
520 	if ((error = bhnd_sprom_opcode_seek(state, entry))) {
521 		BHND_NV_LOG("variable seek failed: %d\n", error);
522 		return (error);
523 	}
524 
525 	ipos = 0;
526 	while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) {
527 		bhnd_sprom_opcode_bind	*binding;
528 		bhnd_sprom_opcode_var	*binding_var;
529 		size_t			 offset;
530 		uint32_t		 skip_out_bytes;
531 
532 		BHND_NV_ASSERT(
533 		    state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN,
534 		    ("invalid var state"));
535 		BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state"));
536 
537 		binding_var = &state->var;
538 		binding = &state->var.bind;
539 
540 		/* Calculate output skip bytes for this binding.
541 		 *
542 		 * Skip directions are defined in terms of decoding, and
543 		 * reversed when encoding. */
544 		skip_out_bytes = binding->skip_in;
545 		error = bhnd_sprom_opcode_apply_scale(state, &skip_out_bytes);
546 		if (error)
547 			return (error);
548 
549 		/* Bind */
550 		offset = state->offset;
551 		for (size_t i = 0; i < binding->count; i++) {
552 			if (ipos >= nelem) {
553 				BHND_NV_LOG("input skip %u positioned %zu "
554 				    "beyond nelem %zu\n", binding->skip_out,
555 				    ipos, nelem);
556 				return (EINVAL);
557 			}
558 
559 			/* Write next offset */
560 			if (io != NULL) {
561 				error = bhnd_nvram_sprom_write_offset(var, io,
562 				    binding_var->base_type,
563 				    offset,
564 				    binding_var->mask,
565 				    binding_var->shift,
566 				    u32[ipos]);
567 				if (error)
568 					return (error);
569 			}
570 
571 			/* Adjust output position; this was already verified to
572 			 * not overflow/underflow during SPROM opcode
573 			 * evaluation */
574 			if (binding->skip_in_negative) {
575 				offset -= skip_out_bytes;
576 			} else {
577 				offset += skip_out_bytes;
578 			}
579 
580 			/* Skip advancing input if additional bindings are
581 			 * required to fully encode intv */
582 			if (binding->skip_out == 0)
583 				continue;
584 
585 			/* Advance input position */
586 			if (SIZE_MAX - binding->skip_out < ipos) {
587 				BHND_NV_LOG("output skip %u would overflow "
588 				    "%zu\n", binding->skip_out, ipos);
589 				return (EINVAL);
590 			}
591 
592 			ipos += binding->skip_out;
593 		}
594 	}
595 
596 	/* Did we iterate all bindings until hitting end of the variable
597 	 * definition? */
598 	BHND_NV_ASSERT(error != 0, ("loop terminated early"));
599 	if (error != ENOENT)
600 		return (error);
601 
602 	return (0);
603 }
604 
605 static int
606 bhnd_nvram_sprom_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
607     bhnd_nvram_plist *options, void *outp, size_t *olen)
608 {
609 	bhnd_sprom_opcode_state		 state;
610 	struct bhnd_nvram_io		*io;
611 	bhnd_nvram_prop			*prop;
612 	bhnd_sprom_opcode_idx_entry	*entry;
613 	const bhnd_sprom_layout		*layout;
614 	size_t				 limit;
615 	uint8_t				 crc;
616 	uint8_t				 sromrev;
617 	int				 error;
618 
619 	limit = *olen;
620 	layout = NULL;
621 	io = NULL;
622 
623 	/* Fetch sromrev property */
624 	if (!bhnd_nvram_plist_contains(props, BHND_NVAR_SROMREV)) {
625 		BHND_NV_LOG("missing required property: %s\n",
626 		    BHND_NVAR_SROMREV);
627 		return (EINVAL);
628 	}
629 
630 	error = bhnd_nvram_plist_get_uint8(props, BHND_NVAR_SROMREV, &sromrev);
631 	if (error) {
632 		BHND_NV_LOG("error reading sromrev property: %d\n", error);
633 		return (EFTYPE);
634 	}
635 
636 	/* Find SPROM layout definition */
637 	if ((layout = bhnd_nvram_sprom_get_layout(sromrev)) == NULL) {
638 		BHND_NV_LOG("unsupported sromrev: %hhu\n", sromrev);
639 		return (EFTYPE);
640 	}
641 
642 	/* Provide required size to caller */
643 	*olen = layout->size;
644 	if (outp == NULL)
645 		return (0);
646 	else if (limit < *olen)
647 		return (ENOMEM);
648 
649 	/* Initialize SPROM layout interpreter */
650 	if ((error = bhnd_sprom_opcode_init(&state, layout))) {
651 		BHND_NV_LOG("error initializing opcode state: %d\n", error);
652 		return (ENXIO);
653 	}
654 
655 	/* Check for unsupported properties */
656 	prop = NULL;
657 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
658 		const char *name;
659 
660 		/* Fetch the corresponding SPROM layout index entry */
661 		name = bhnd_nvram_prop_name(prop);
662 		entry = bhnd_sprom_opcode_index_find(&state, name);
663 		if (entry == NULL) {
664 			BHND_NV_LOG("property '%s' unsupported by sromrev "
665 			    "%hhu\n", name, layout->rev);
666 			error = EINVAL;
667 			goto finished;
668 		}
669 	}
670 
671 	/* Zero-initialize output */
672 	memset(outp, 0, *olen);
673 
674 	/* Allocate wrapping I/O context for output buffer */
675 	io = bhnd_nvram_ioptr_new(outp, *olen, *olen, BHND_NVRAM_IOPTR_RDWR);
676 	if (io == NULL) {
677 		error = ENOMEM;
678 		goto finished;
679 	}
680 
681 	/*
682 	 * Serialize all SPROM variable data.
683 	 */
684 	entry = NULL;
685 	while ((entry = bhnd_sprom_opcode_index_next(&state, entry)) != NULL) {
686 		const struct bhnd_nvram_vardefn	*var;
687 		bhnd_nvram_val			*val;
688 
689 		var = bhnd_nvram_get_vardefn(entry->vid);
690 		BHND_NV_ASSERT(var != NULL, ("missing variable definition"));
691 
692 		/* Fetch prop; will be NULL if unavailable */
693 		prop = bhnd_nvram_plist_get_prop(props, var->name);
694 		if (prop != NULL) {
695 			val = bhnd_nvram_prop_val(prop);
696 		} else {
697 			val = BHND_NVRAM_VAL_NULL;
698 		}
699 
700 		/* Attempt to serialize the property value to the appropriate
701 		 * offset within the output buffer */
702 		error = bhnd_nvram_sprom_write_var(&state, entry, val, io);
703 		if (error) {
704 			BHND_NV_LOG("error serializing %s to required type "
705 			    "%s: %d\n", var->name,
706 			    bhnd_nvram_type_name(var->type), error);
707 
708 			/* ENOMEM is reserved for signaling that the output
709 			 * buffer capacity is insufficient */
710 			if (error == ENOMEM)
711 				error = EINVAL;
712 
713 			goto finished;
714 		}
715 	}
716 
717 	/*
718 	 * Write magic value, if any.
719 	 */
720 	if (!(layout->flags & SPROM_LAYOUT_MAGIC_NONE)) {
721 		uint16_t magic;
722 
723 		magic = htole16(layout->magic_value);
724 		error = bhnd_nvram_io_write(io, layout->magic_offset, &magic,
725 		    sizeof(magic));
726 		if (error) {
727 			BHND_NV_LOG("error writing magic value: %d\n", error);
728 			goto finished;
729 		}
730 	}
731 
732 	/* Calculate the CRC over all SPROM data, not including the CRC byte. */
733 	crc = ~bhnd_nvram_crc8(outp, layout->crc_offset,
734 	    BHND_NVRAM_CRC8_INITIAL);
735 
736 	/* Write the checksum. */
737 	error = bhnd_nvram_io_write(io, layout->crc_offset, &crc, sizeof(crc));
738 	if (error) {
739 		BHND_NV_LOG("error writing CRC value: %d\n", error);
740 		goto finished;
741 	}
742 
743 	/*
744 	 * Success!
745 	 */
746 	error = 0;
747 
748 finished:
749 	bhnd_sprom_opcode_fini(&state);
750 
751 	if (io != NULL)
752 		bhnd_nvram_io_free(io);
753 
754 	return (error);
755 }
756 
757 static int
758 bhnd_nvram_sprom_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
759 {
760 	struct bhnd_nvram_sprom	*sp;
761 	int			 error;
762 
763 	sp = (struct bhnd_nvram_sprom *)nv;
764 
765 	/* Identify the SPROM input data */
766 	if ((error = bhnd_nvram_sprom_ident(io, &sp->layout)))
767 		return (error);
768 
769 	/* Copy SPROM image to our shadow buffer */
770 	sp->data = bhnd_nvram_iobuf_copy_range(io, 0, sp->layout->size);
771 	if (sp->data == NULL)
772 		goto failed;
773 
774 	/* Initialize SPROM binding eval state */
775 	if ((error = bhnd_sprom_opcode_init(&sp->state, sp->layout)))
776 		goto failed;
777 
778 	return (0);
779 
780 failed:
781 	if (sp->data != NULL)
782 		bhnd_nvram_io_free(sp->data);
783 
784 	return (error);
785 }
786 
787 static void
788 bhnd_nvram_sprom_free(struct bhnd_nvram_data *nv)
789 {
790 	struct bhnd_nvram_sprom *sp = (struct bhnd_nvram_sprom *)nv;
791 
792 	bhnd_sprom_opcode_fini(&sp->state);
793 	bhnd_nvram_io_free(sp->data);
794 }
795 
796 size_t
797 bhnd_nvram_sprom_count(struct bhnd_nvram_data *nv)
798 {
799 	struct bhnd_nvram_sprom *sprom = (struct bhnd_nvram_sprom *)nv;
800 	return (sprom->layout->num_vars);
801 }
802 
803 static bhnd_nvram_plist *
804 bhnd_nvram_sprom_options(struct bhnd_nvram_data *nv)
805 {
806 	return (NULL);
807 }
808 
809 static uint32_t
810 bhnd_nvram_sprom_caps(struct bhnd_nvram_data *nv)
811 {
812 	return (BHND_NVRAM_DATA_CAP_INDEXED);
813 }
814 
815 static const char *
816 bhnd_nvram_sprom_next(struct bhnd_nvram_data *nv, void **cookiep)
817 {
818 	struct bhnd_nvram_sprom		*sp;
819 	bhnd_sprom_opcode_idx_entry	*entry;
820 	const struct bhnd_nvram_vardefn	*var;
821 
822 	sp = (struct bhnd_nvram_sprom *)nv;
823 
824 	/* Find next index entry that is not disabled by virtue of IGNALL1 */
825 	entry = *cookiep;
826 	while ((entry = bhnd_sprom_opcode_index_next(&sp->state, entry))) {
827 		/* Update cookiep and fetch variable definition */
828 		*cookiep = entry;
829 		var = SPROM_COOKIE_TO_NVRAM_VAR(*cookiep);
830 
831 		/* We might need to parse the variable's value to determine
832 		 * whether it should be treated as unset */
833 		if (var->flags & BHND_NVRAM_VF_IGNALL1) {
834 			int     error;
835 			size_t  len;
836 
837 			error = bhnd_nvram_sprom_getvar(nv, *cookiep, NULL,
838 			    &len, var->type);
839 			if (error) {
840 				BHND_NV_ASSERT(error == ENOENT, ("unexpected "
841 				    "error parsing variable: %d", error));
842 				continue;
843 			}
844 		}
845 
846 		/* Found! */
847 		return (var->name);
848 	}
849 
850 	/* Reached end of index entries */
851 	return (NULL);
852 }
853 
854 static void *
855 bhnd_nvram_sprom_find(struct bhnd_nvram_data *nv, const char *name)
856 {
857 	struct bhnd_nvram_sprom		*sp;
858 	bhnd_sprom_opcode_idx_entry	*entry;
859 
860 	sp = (struct bhnd_nvram_sprom *)nv;
861 
862 	entry = bhnd_sprom_opcode_index_find(&sp->state, name);
863 	return (entry);
864 }
865 
866 /**
867  * Write @p value of @p type to the SPROM @p data at @p offset, applying
868  * @p mask and @p shift, and OR with the existing data.
869  *
870  * @param var The NVRAM variable definition.
871  * @param data The SPROM data to be modified.
872  * @param type The type to write at @p offset.
873  * @param offset The data offset to be written.
874  * @param mask The mask to be applied to @p value after shifting.
875  * @param shift The shift to be applied to @p value; if positive, a left
876  * shift will be applied, if negative, a right shift (this is the reverse of the
877  * decoding behavior)
878  * @param value The value to be written. The parsed value will be OR'd with the
879  * current contents of @p data at @p offset.
880  */
881 static int
882 bhnd_nvram_sprom_write_offset(const struct bhnd_nvram_vardefn *var,
883     struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset,
884     uint32_t mask, int8_t shift, uint32_t value)
885 {
886 	union bhnd_nvram_sprom_storage	scratch;
887 	int				error;
888 
889 #define	NV_WRITE_INT(_widen, _repr, _swap)	do {		\
890 	/* Narrow the 32-bit representation */			\
891 	scratch._repr[1] = (_widen)value;			\
892 								\
893 	/* Shift and mask the new value */			\
894 	if (shift > 0)						\
895 		scratch._repr[1] <<= shift;			\
896 	else if (shift < 0)					\
897 		scratch._repr[1] >>= -shift;			\
898 	scratch._repr[1] &= mask;				\
899 								\
900 	/* Swap to output byte order */				\
901 	scratch._repr[1] = _swap(scratch._repr[1]);		\
902 								\
903 	/* Fetch the current value */				\
904 	error = bhnd_nvram_io_read(data, offset,		\
905 	    &scratch._repr[0], sizeof(scratch._repr[0]));	\
906 	if (error) {						\
907 		BHND_NV_LOG("error reading %s SPROM offset "	\
908 		    "%#zx: %d\n", var->name, offset, error);	\
909 		return (EFTYPE);				\
910 	}							\
911 								\
912 	/* Mask and set our new value's bits in the current	\
913 	 * value */						\
914 	if (shift >= 0)						\
915 		scratch._repr[0] &= ~_swap(mask << shift);	\
916 	else if (shift < 0)					\
917 		scratch._repr[0] &= ~_swap(mask >> (-shift));	\
918 	scratch._repr[0] |= scratch._repr[1];			\
919 								\
920 	/* Perform write */					\
921 	error = bhnd_nvram_io_write(data, offset,		\
922 	    &scratch._repr[0], sizeof(scratch._repr[0]));	\
923 	if (error) {						\
924 		BHND_NV_LOG("error writing %s SPROM offset "	\
925 		    "%#zx: %d\n", var->name, offset, error);	\
926 		return (EFTYPE);				\
927 	}							\
928 } while(0)
929 
930 	/* Apply mask/shift and widen to a common 32bit representation */
931 	switch (type) {
932 	case BHND_NVRAM_TYPE_UINT8:
933 		NV_WRITE_INT(uint32_t,	u8,	);
934 		break;
935 	case BHND_NVRAM_TYPE_UINT16:
936 		NV_WRITE_INT(uint32_t,	u16,	htole16);
937 		break;
938 	case BHND_NVRAM_TYPE_UINT32:
939 		NV_WRITE_INT(uint32_t,	u32,	htole32);
940 		break;
941 	case BHND_NVRAM_TYPE_INT8:
942 		NV_WRITE_INT(int32_t,	i8,	);
943 		break;
944 	case BHND_NVRAM_TYPE_INT16:
945 		NV_WRITE_INT(int32_t,	i16,	htole16);
946 		break;
947 	case BHND_NVRAM_TYPE_INT32:
948 		NV_WRITE_INT(int32_t,	i32,	htole32);
949 		break;
950 	case BHND_NVRAM_TYPE_CHAR:
951 		NV_WRITE_INT(uint32_t,	u8,	);
952 		break;
953 	default:
954 		BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type);
955 		return (EFTYPE);
956 	}
957 #undef	NV_WRITE_INT
958 
959 	return (0);
960 }
961 
962 /**
963  * Read the value of @p type from the SPROM @p data at @p offset, apply @p mask
964  * and @p shift, and OR with the existing @p value.
965  *
966  * @param var The NVRAM variable definition.
967  * @param data The SPROM data to be decoded.
968  * @param type The type to read at @p offset
969  * @param offset The data offset to be read.
970  * @param mask The mask to be applied to the value read at @p offset.
971  * @param shift The shift to be applied after masking; if positive, a right
972  * shift will be applied, if negative, a left shift.
973  * @param value The read destination; the parsed value will be OR'd with the
974  * current contents of @p value.
975  */
976 static int
977 bhnd_nvram_sprom_read_offset(const struct bhnd_nvram_vardefn *var,
978     struct bhnd_nvram_io *data, bhnd_nvram_type type, size_t offset,
979     uint32_t mask, int8_t shift, uint32_t *value)
980 {
981 	union bhnd_nvram_sprom_storage	scratch;
982 	int				error;
983 
984 #define	NV_PARSE_INT(_widen, _repr, _swap)		do {	\
985 	/* Perform read */					\
986 	error = bhnd_nvram_io_read(data, offset,		\
987 	    &scratch._repr[0], sizeof(scratch._repr[0]));	\
988 	if (error) {						\
989 		BHND_NV_LOG("error reading %s SPROM offset "	\
990 		    "%#zx: %d\n", var->name, offset, error);	\
991 		return (EFTYPE);				\
992 	}							\
993 								\
994 	/* Swap to host byte order */				\
995 	scratch._repr[0] = _swap(scratch._repr[0]);		\
996 								\
997 	/* Mask and shift the value */				\
998 	scratch._repr[0] &= mask;				\
999 	if (shift > 0) {					\
1000 		scratch. _repr[0] >>= shift;			\
1001 	} else if (shift < 0) {					\
1002 		scratch. _repr[0] <<= -shift;			\
1003 	}							\
1004 								\
1005 	/* Widen to 32-bit representation and OR with current	\
1006 	 * value */						\
1007 	(*value) |= (_widen)scratch._repr[0];			\
1008 } while(0)
1009 
1010 	/* Apply mask/shift and widen to a common 32bit representation */
1011 	switch (type) {
1012 	case BHND_NVRAM_TYPE_UINT8:
1013 		NV_PARSE_INT(uint32_t,	u8,	);
1014 		break;
1015 	case BHND_NVRAM_TYPE_UINT16:
1016 		NV_PARSE_INT(uint32_t,	u16,	le16toh);
1017 		break;
1018 	case BHND_NVRAM_TYPE_UINT32:
1019 		NV_PARSE_INT(uint32_t,	u32,	le32toh);
1020 		break;
1021 	case BHND_NVRAM_TYPE_INT8:
1022 		NV_PARSE_INT(int32_t,	i8,	);
1023 		break;
1024 	case BHND_NVRAM_TYPE_INT16:
1025 		NV_PARSE_INT(int32_t,	i16,	le16toh);
1026 		break;
1027 	case BHND_NVRAM_TYPE_INT32:
1028 		NV_PARSE_INT(int32_t,	i32,	le32toh);
1029 		break;
1030 	case BHND_NVRAM_TYPE_CHAR:
1031 		NV_PARSE_INT(uint32_t,	u8,	);
1032 		break;
1033 	default:
1034 		BHND_NV_LOG("unhandled %s offset type: %d\n", var->name, type);
1035 		return (EFTYPE);
1036 	}
1037 #undef	NV_PARSE_INT
1038 
1039 	return (0);
1040 }
1041 
1042 /**
1043  * Read a SPROM variable value from @p io.
1044  *
1045  * @param	state		The SPROM opcode state describing the layout of @p io.
1046  * @param	entry		The variable's SPROM opcode index entry.
1047  * @param	io		The input I/O context.
1048  * @param	storage		Storage to be used with @p val.
1049  * @param[out]	val		Value instance to be initialized with the
1050  *				parsed variable data.
1051  *
1052  * The returned @p val instance will hold a borrowed reference to @p storage,
1053  * and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond
1054  * the lifetime of @p storage.
1055  *
1056  * The caller is responsible for releasing any allocated value state
1057  * via bhnd_nvram_val_release().
1058  */
1059 static int
1060 bhnd_nvram_sprom_read_var(struct bhnd_sprom_opcode_state *state,
1061     struct bhnd_sprom_opcode_idx_entry *entry, struct bhnd_nvram_io *io,
1062     union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val)
1063 {
1064 	union bhnd_nvram_sprom_storage	*inp;
1065 	const struct bhnd_nvram_vardefn	*var;
1066 	bhnd_nvram_type			 var_btype;
1067 	uint32_t			 intv;
1068 	size_t				 ilen, ipos, iwidth;
1069 	size_t				 nelem;
1070 	bool				 all_bits_set;
1071 	int				 error;
1072 
1073 	/* Fetch canonical variable definition */
1074 	var = bhnd_nvram_get_vardefn(entry->vid);
1075 	BHND_NV_ASSERT(var != NULL, ("invalid entry"));
1076 
1077 	/*
1078 	 * Fetch the array length from the SPROM variable definition.
1079 	 *
1080 	 * This generally be identical to the array length provided by the
1081 	 * canonical NVRAM variable definition, but some SPROM layouts may
1082 	 * define a smaller element count.
1083 	 */
1084 	if ((error = bhnd_sprom_opcode_eval_var(state, entry))) {
1085 		BHND_NV_LOG("variable evaluation failed: %d\n", error);
1086 		return (error);
1087 	}
1088 
1089 	nelem = state->var.nelem;
1090 	if (nelem > var->nelem) {
1091 		BHND_NV_LOG("SPROM array element count %zu cannot be "
1092 		    "represented by '%s' element count of %hhu\n", nelem,
1093 		    var->name, var->nelem);
1094 		return (EFTYPE);
1095 	}
1096 
1097 	/* Fetch the var's base element type */
1098 	var_btype = bhnd_nvram_base_type(var->type);
1099 
1100 	/* Calculate total byte length of the native encoding */
1101 	if ((iwidth = bhnd_nvram_value_size(NULL, 0, var_btype, 1)) == 0) {
1102 		/* SPROM does not use (and we do not support) decoding of
1103 		 * variable-width data types */
1104 		BHND_NV_LOG("invalid SPROM data type: %d", var->type);
1105 		return (EFTYPE);
1106 	}
1107 	ilen = nelem * iwidth;
1108 
1109 	/* Decode into our caller's local storage */
1110 	inp = storage;
1111 	if (ilen > sizeof(*storage)) {
1112 		BHND_NV_LOG("error decoding '%s', SPROM_ARRAY_MAXLEN "
1113 		    "incorrect\n", var->name);
1114 		return (EFTYPE);
1115 	}
1116 
1117 	/* Zero-initialize our decode buffer; any output elements skipped
1118 	 * during decode should default to zero. */
1119 	memset(inp, 0, ilen);
1120 
1121 	/*
1122 	 * Decode the SPROM data, iteratively decoding up to nelem values.
1123 	 */
1124 	if ((error = bhnd_sprom_opcode_seek(state, entry))) {
1125 		BHND_NV_LOG("variable seek failed: %d\n", error);
1126 		return (error);
1127 	}
1128 
1129 	ipos = 0;
1130 	intv = 0x0;
1131 	if (var->flags & BHND_NVRAM_VF_IGNALL1)
1132 		all_bits_set = true;
1133 	else
1134 		all_bits_set = false;
1135 	while ((error = bhnd_sprom_opcode_next_binding(state)) == 0) {
1136 		bhnd_sprom_opcode_bind	*binding;
1137 		bhnd_sprom_opcode_var	*binding_var;
1138 		bhnd_nvram_type		 intv_type;
1139 		size_t			 offset;
1140 		size_t			 nbyte;
1141 		uint32_t		 skip_in_bytes;
1142 		void			*ptr;
1143 
1144 		BHND_NV_ASSERT(
1145 		    state->var_state >= SPROM_OPCODE_VAR_STATE_OPEN,
1146 		    ("invalid var state"));
1147 		BHND_NV_ASSERT(state->var.have_bind, ("invalid bind state"));
1148 
1149 		binding_var = &state->var;
1150 		binding = &state->var.bind;
1151 
1152 		if (ipos >= nelem) {
1153 			BHND_NV_LOG("output skip %u positioned "
1154 			    "%zu beyond nelem %zu\n",
1155 			    binding->skip_out, ipos, nelem);
1156 			return (EINVAL);
1157 		}
1158 
1159 		/* Calculate input skip bytes for this binding */
1160 		skip_in_bytes = binding->skip_in;
1161 		error = bhnd_sprom_opcode_apply_scale(state, &skip_in_bytes);
1162 		if (error)
1163 			return (error);
1164 
1165 		/* Bind */
1166 		offset = state->offset;
1167 		for (size_t i = 0; i < binding->count; i++) {
1168 			/* Read the offset value, OR'ing with the current
1169 			 * value of intv */
1170 			error = bhnd_nvram_sprom_read_offset(var, io,
1171 			    binding_var->base_type,
1172 			    offset,
1173 			    binding_var->mask,
1174 			    binding_var->shift,
1175 			    &intv);
1176 			if (error)
1177 				return (error);
1178 
1179 			/* If IGNALL1, record whether value does not have
1180 			 * all bits set. */
1181 			if (var->flags & BHND_NVRAM_VF_IGNALL1 &&
1182 			    all_bits_set)
1183 			{
1184 				uint32_t all1;
1185 
1186 				all1 = binding_var->mask;
1187 				if (binding_var->shift > 0)
1188 					all1 >>= binding_var->shift;
1189 				else if (binding_var->shift < 0)
1190 					all1 <<= -binding_var->shift;
1191 
1192 				if ((intv & all1) != all1)
1193 					all_bits_set = false;
1194 			}
1195 
1196 			/* Adjust input position; this was already verified to
1197 			 * not overflow/underflow during SPROM opcode
1198 			 * evaluation */
1199 			if (binding->skip_in_negative) {
1200 				offset -= skip_in_bytes;
1201 			} else {
1202 				offset += skip_in_bytes;
1203 			}
1204 
1205 			/* Skip writing to inp if additional bindings are
1206 			 * required to fully populate intv */
1207 			if (binding->skip_out == 0)
1208 				continue;
1209 
1210 			/* We use bhnd_nvram_value_coerce() to perform
1211 			 * overflow-checked coercion from the widened
1212 			 * uint32/int32 intv value to the requested output
1213 			 * type */
1214 			if (bhnd_nvram_is_signed_type(var_btype))
1215 				intv_type = BHND_NVRAM_TYPE_INT32;
1216 			else
1217 				intv_type = BHND_NVRAM_TYPE_UINT32;
1218 
1219 			/* Calculate address of the current element output
1220 			 * position */
1221 			ptr = (uint8_t *)inp + (iwidth * ipos);
1222 
1223 			/* Perform coercion of the array element */
1224 			nbyte = iwidth;
1225 			error = bhnd_nvram_value_coerce(&intv, sizeof(intv),
1226 			    intv_type, ptr, &nbyte, var_btype);
1227 			if (error)
1228 				return (error);
1229 
1230 			/* Clear temporary state */
1231 			intv = 0x0;
1232 
1233 			/* Advance output position */
1234 			if (SIZE_MAX - binding->skip_out < ipos) {
1235 				BHND_NV_LOG("output skip %u would overflow "
1236 				    "%zu\n", binding->skip_out, ipos);
1237 				return (EINVAL);
1238 			}
1239 
1240 			ipos += binding->skip_out;
1241 		}
1242 	}
1243 
1244 	/* Did we iterate all bindings until hitting end of the variable
1245 	 * definition? */
1246 	BHND_NV_ASSERT(error != 0, ("loop terminated early"));
1247 	if (error != ENOENT) {
1248 		return (error);
1249 	}
1250 
1251 	/* If marked IGNALL1 and all bits are set, treat variable as
1252 	 * unavailable */
1253 	if ((var->flags & BHND_NVRAM_VF_IGNALL1) && all_bits_set)
1254 		return (ENOENT);
1255 
1256 	/* Provide value wrapper */
1257 	return (bhnd_nvram_val_init(val, var->fmt, inp, ilen, var->type,
1258 	    BHND_NVRAM_VAL_BORROW_DATA));
1259 		return (error);
1260 }
1261 
1262 
1263 /**
1264  * Common variable decoding; fetches and decodes variable to @p val,
1265  * using @p storage for actual data storage.
1266  *
1267  * The returned @p val instance will hold a borrowed reference to @p storage,
1268  * and must be copied via bhnd_nvram_val_copy() if it will be referenced beyond
1269  * the lifetime of @p storage.
1270  *
1271  * The caller is responsible for releasing any allocated value state
1272  * via bhnd_nvram_val_release().
1273  */
1274 static int
1275 bhnd_nvram_sprom_getvar_common(struct bhnd_nvram_data *nv, void *cookiep,
1276     union bhnd_nvram_sprom_storage *storage, bhnd_nvram_val *val)
1277 {
1278 	struct bhnd_nvram_sprom		*sp;
1279 	bhnd_sprom_opcode_idx_entry	*entry;
1280 	const struct bhnd_nvram_vardefn	*var;
1281 
1282 	BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));
1283 
1284 	sp = (struct bhnd_nvram_sprom *)nv;
1285 	entry = cookiep;
1286 
1287 	/* Fetch canonical variable definition */
1288 	var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep);
1289 	BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));
1290 
1291 	return (bhnd_nvram_sprom_read_var(&sp->state, entry, sp->data, storage,
1292 	    val));
1293 }
1294 
1295 static int
1296 bhnd_nvram_sprom_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
1297     void *cookiep2)
1298 {
1299 	struct bhnd_sprom_opcode_idx_entry *e1, *e2;
1300 
1301 	e1 = cookiep1;
1302 	e2 = cookiep2;
1303 
1304 	/* Use the index entry order; this matches the order of variables
1305 	 * returned via bhnd_nvram_sprom_next() */
1306 	if (e1 < e2)
1307 		return (-1);
1308 	else if (e1 > e2)
1309 		return (1);
1310 
1311 	return (0);
1312 }
1313 
1314 static int
1315 bhnd_nvram_sprom_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
1316     size_t *len, bhnd_nvram_type otype)
1317 {
1318 	bhnd_nvram_val			val;
1319 	union bhnd_nvram_sprom_storage	storage;
1320 	int				error;
1321 
1322 	/* Decode variable to a new value instance */
1323 	error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val);
1324 	if (error)
1325 		return (error);
1326 
1327 	/* Perform value coercion */
1328 	error = bhnd_nvram_val_encode(&val, buf, len, otype);
1329 
1330 	/* Clean up */
1331 	bhnd_nvram_val_release(&val);
1332 	return (error);
1333 }
1334 
1335 static int
1336 bhnd_nvram_sprom_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
1337     bhnd_nvram_val **value)
1338 {
1339 	bhnd_nvram_val			val;
1340 	union bhnd_nvram_sprom_storage	storage;
1341 	int				error;
1342 
1343 	/* Decode variable to a new value instance */
1344 	error = bhnd_nvram_sprom_getvar_common(nv, cookiep, &storage, &val);
1345 	if (error)
1346 		return (error);
1347 
1348 	/* Attempt to copy to heap */
1349 	*value = bhnd_nvram_val_copy(&val);
1350 	bhnd_nvram_val_release(&val);
1351 
1352 	if (*value == NULL)
1353 		return (ENOMEM);
1354 
1355 	return (0);
1356 }
1357 
1358 static const void *
1359 bhnd_nvram_sprom_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
1360     size_t *len, bhnd_nvram_type *type)
1361 {
1362 	/* Unsupported */
1363 	return (NULL);
1364 }
1365 
1366 static const char *
1367 bhnd_nvram_sprom_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
1368 {
1369 	const struct bhnd_nvram_vardefn	*var;
1370 
1371 	BHND_NV_ASSERT(cookiep != NULL, ("NULL variable cookiep"));
1372 
1373 	var = SPROM_COOKIE_TO_NVRAM_VAR(cookiep);
1374 	BHND_NV_ASSERT(var != NULL, ("invalid cookiep %p", cookiep));
1375 
1376 	return (var->name);
1377 }
1378 
1379 static int
1380 bhnd_nvram_sprom_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
1381     bhnd_nvram_val *value, bhnd_nvram_val **result)
1382 {
1383 	struct bhnd_nvram_sprom		*sp;
1384 	const struct bhnd_nvram_vardefn	*var;
1385 	bhnd_sprom_opcode_idx_entry	*entry;
1386 	bhnd_nvram_val			*spval;
1387 	int				 error;
1388 
1389 	sp = (struct bhnd_nvram_sprom *)nv;
1390 
1391 	/* Is this an externally immutable variable name? */
1392 	if (bhnd_sprom_is_external_immutable(name))
1393 		return (EINVAL);
1394 
1395 	/* Variable must be defined in our SPROM layout */
1396 	if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL)
1397 		return (ENOENT);
1398 
1399 	var = bhnd_nvram_get_vardefn(entry->vid);
1400 	BHND_NV_ASSERT(var != NULL, ("missing variable definition"));
1401 
1402 	/* Value must be convertible to the native variable type */
1403 	error = bhnd_nvram_val_convert_new(&spval, var->fmt, value,
1404 	    BHND_NVRAM_VAL_DYNAMIC);
1405 	if (error)
1406 		return (error);
1407 
1408 	/* Value must be encodeable by our SPROM layout */
1409 	error = bhnd_nvram_sprom_write_var(&sp->state, entry, spval, NULL);
1410 	if (error) {
1411 		bhnd_nvram_val_release(spval);
1412 		return (error);
1413 	}
1414 
1415 	/* Success. Transfer our ownership of the converted value to the
1416 	 * caller */
1417 	*result = spval;
1418 	return (0);
1419 }
1420 
1421 static int
1422 bhnd_nvram_sprom_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
1423 {
1424 	struct bhnd_nvram_sprom		*sp;
1425 	const struct bhnd_nvram_vardefn	*var;
1426 	bhnd_sprom_opcode_idx_entry	*entry;
1427 
1428 	sp = (struct bhnd_nvram_sprom *)nv;
1429 
1430 	/* Is this an externally immutable variable name? */
1431 	if (bhnd_sprom_is_external_immutable(name))
1432 		return (EINVAL);
1433 
1434 	/* Variable must be defined in our SPROM layout */
1435 	if ((entry = bhnd_sprom_opcode_index_find(&sp->state, name)) == NULL)
1436 		return (ENOENT);
1437 
1438 	var = bhnd_nvram_get_vardefn(entry->vid);
1439 
1440 	/* Variable must be capable of representing a NULL/deleted value.
1441 	 *
1442 	 * Since SPROM's layout is fixed, this requires IGNALL -- if
1443 	 * all bits are set, an IGNALL variable is treated as unset. */
1444 	if (!(var->flags & BHND_NVRAM_VF_IGNALL1))
1445 		return (EINVAL);
1446 
1447 	return (0);
1448 }
1449 
1450 /**
1451  * Return true if @p name represents a special immutable variable name
1452  * (e.g. sromrev) that cannot be updated in an SPROM existing image.
1453  *
1454  * @param name The name to check.
1455  */
1456 static bool
1457 bhnd_sprom_is_external_immutable(const char *name)
1458 {
1459 	/* The layout revision is immutable and cannot be changed */
1460 	if (strcmp(name, BHND_NVAR_SROMREV) == 0)
1461 		return (true);
1462 
1463 	return (false);
1464 }
1465