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