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