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