xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision fe6060f10f634930ff71b7c50291ddc610da2475)
1 /*-
2  * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer,
10  *    without modification.
11  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13  *    redistribution must be conditioned upon including a substantially
14  *    similar Disclaimer requirement for further binary redistribution.
15  *
16  * NO WARRANTY
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27  * THE POSSIBILITY OF SUCH DAMAGES.
28  */
29 
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32 
33 #ifdef _KERNEL
34 #include <sys/param.h>
35 #include <sys/ctype.h>
36 #include <sys/limits.h>
37 #include <sys/malloc.h>
38 #include <sys/systm.h>
39 #else /* !_KERNEL */
40 #include <ctype.h>
41 #include <errno.h>
42 #include <stdint.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #endif /* _KERNEL */
47 
48 #include "bhnd_nvram_private.h"
49 
50 #include "bhnd_nvram_datavar.h"
51 
52 #include "bhnd_nvram_data_tlvreg.h"
53 
54 /*
55  * CFE TLV NVRAM data class.
56  *
57  * The CFE-defined TLV NVRAM format is used on the WGT634U.
58  */
59 
60 struct bhnd_nvram_tlv {
61 	struct bhnd_nvram_data	 nv;	/**< common instance state */
62 	struct bhnd_nvram_io	*data;	/**< backing buffer */
63 	size_t			 count;	/**< variable count */
64 };
65 
66 BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
67     sizeof(struct bhnd_nvram_tlv))
68 
69 /** Minimal TLV_ENV record header */
70 struct bhnd_nvram_tlv_env_hdr {
71 	uint8_t		tag;
72 	uint8_t		size;
73 } __packed;
74 
75 /** Minimal TLV_ENV record */
76 struct bhnd_nvram_tlv_env {
77 	struct bhnd_nvram_tlv_env_hdr	hdr;
78 	uint8_t				flags;
79 	char				envp[];
80 } __packed;
81 
82 /* Return the length in bytes of an TLV_ENV's envp data */
83 #define	NVRAM_TLV_ENVP_DATA_LEN(_env)	\
84 	(((_env)->hdr.size < sizeof((_env)->flags)) ? 0 :	\
85 	    ((_env)->hdr.size - sizeof((_env)->flags)))
86 
87 /* Maximum supported length of the envp data field, in bytes */
88 #define	NVRAM_TLV_ENVP_DATA_MAX_LEN	\
89 	(UINT8_MAX - sizeof(uint8_t) /* flags */)
90 
91 static int				 bhnd_nvram_tlv_parse_size(
92 					     struct bhnd_nvram_io *io,
93 					     size_t *size);
94 
95 static int				 bhnd_nvram_tlv_next_record(
96 					     struct bhnd_nvram_io *io,
97 					     size_t *next, size_t *offset,
98 					     uint8_t *tag);
99 
100 static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_next_env(
101 					     struct bhnd_nvram_tlv *tlv,
102 					     size_t *next, void **cookiep);
103 
104 static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_get_env(
105 					     struct bhnd_nvram_tlv *tlv,
106 					     void *cookiep);
107 
108 static void				*bhnd_nvram_tlv_to_cookie(
109 					     struct bhnd_nvram_tlv *tlv,
110 					     size_t io_offset);
111 static size_t				 bhnd_nvram_tlv_to_offset(
112 					     struct bhnd_nvram_tlv *tlv,
113 					     void *cookiep);
114 
115 static int
116 bhnd_nvram_tlv_probe(struct bhnd_nvram_io *io)
117 {
118 	struct bhnd_nvram_tlv_env	ident;
119 	size_t				nbytes;
120 	int				error;
121 
122 	nbytes = bhnd_nvram_io_getsize(io);
123 
124 	/* Handle what might be an empty TLV image */
125 	if (nbytes < sizeof(ident)) {
126 		uint8_t tag;
127 
128 		/* Fetch just the first tag */
129 		error = bhnd_nvram_io_read(io, 0x0, &tag, sizeof(tag));
130 		if (error)
131 			return (error);
132 
133 		/* This *could* be an empty TLV image, but all we're
134 		 * testing for here is a single 0x0 byte followed by EOF */
135 		if (tag == NVRAM_TLV_TYPE_END)
136 			return (BHND_NVRAM_DATA_PROBE_MAYBE);
137 
138 		return (ENXIO);
139 	}
140 
141 	/* Otherwise, look at the initial header for a valid TLV ENV tag,
142 	 * plus one byte of the entry data */
143 	error = bhnd_nvram_io_read(io, 0x0, &ident,
144 	    sizeof(ident) + sizeof(ident.envp[0]));
145 	if (error)
146 		return (error);
147 
148 	/* First entry should be a variable record (which we statically
149 	 * assert as being defined to use a single byte size field) */
150 	if (ident.hdr.tag != NVRAM_TLV_TYPE_ENV)
151 		return (ENXIO);
152 
153 	_Static_assert(NVRAM_TLV_TYPE_ENV & NVRAM_TLV_TF_U8_LEN,
154 	    "TYPE_ENV is not a U8-sized field");
155 
156 	/* The entry must be at least 3 characters ('x=\0') in length */
157 	if (ident.hdr.size < 3)
158 		return (ENXIO);
159 
160 	/* The first character should be a valid key char (alpha) */
161 	if (!bhnd_nv_isalpha(ident.envp[0]))
162 		return (ENXIO);
163 
164 	return (BHND_NVRAM_DATA_PROBE_DEFAULT);
165 }
166 
167 static int
168 bhnd_nvram_tlv_getvar_direct(struct bhnd_nvram_io *io, const char *name,
169     void *buf, size_t *len, bhnd_nvram_type type)
170 {
171 	struct bhnd_nvram_tlv_env	 env;
172 	char				 data[NVRAM_TLV_ENVP_DATA_MAX_LEN];
173 	size_t				 data_len;
174 	const char			*key, *value;
175 	size_t				 keylen, vlen;
176 	size_t				 namelen;
177 	size_t				 next, off;
178 	uint8_t				 tag;
179 	int				 error;
180 
181 	namelen = strlen(name);
182 
183 	/* Iterate over the input looking for the requested variable */
184 	next = 0;
185 	while (!(error = bhnd_nvram_tlv_next_record(io, &next, &off, &tag))) {
186 		switch (tag) {
187 		case NVRAM_TLV_TYPE_END:
188 			/* Not found */
189 			return (ENOENT);
190 
191 		case NVRAM_TLV_TYPE_ENV:
192 			/* Read the record header */
193 			error = bhnd_nvram_io_read(io, off, &env, sizeof(env));
194 			if (error) {
195 				BHND_NV_LOG("error reading TLV_ENV record "
196 				    "header: %d\n", error);
197 				return (error);
198 			}
199 
200 			/* Read the record data */
201 			data_len = NVRAM_TLV_ENVP_DATA_LEN(&env);
202 			error = bhnd_nvram_io_read(io, off + sizeof(env), data,
203 			    data_len);
204 			if (error) {
205 				BHND_NV_LOG("error reading TLV_ENV record "
206 				    "data: %d\n", error);
207 				return (error);
208 			}
209 
210 			/* Parse the key=value string */
211 			error = bhnd_nvram_parse_env(data, data_len, '=', &key,
212 			    &keylen, &value, &vlen);
213 			if (error) {
214 				BHND_NV_LOG("error parsing TLV_ENV data: %d\n",
215 				    error);
216 				return (error);
217 			}
218 
219 			/* Match against requested variable name */
220 			if (keylen == namelen &&
221 			    strncmp(key, name, namelen) == 0)
222 			{
223 				return (bhnd_nvram_value_coerce(value, vlen,
224 				    BHND_NVRAM_TYPE_STRING, buf, len, type));
225 			}
226 
227 			break;
228 
229 		default:
230 			/* Skip unknown tags */
231 			break;
232 		}
233 	}
234 
235 	/* Hit I/O error */
236 	return (error);
237 }
238 
239 static int
240 bhnd_nvram_tlv_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
241     bhnd_nvram_plist *options, void *outp, size_t *olen)
242 {
243 	bhnd_nvram_prop	*prop;
244 	size_t		 limit, nbytes;
245 	int		 error;
246 
247 	/* Determine output byte limit */
248 	if (outp != NULL)
249 		limit = *olen;
250 	else
251 		limit = 0;
252 
253 	nbytes = 0;
254 
255 	/* Write all properties */
256 	prop = NULL;
257 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
258 		struct bhnd_nvram_tlv_env	 env;
259 		const char			*name;
260 		uint8_t				*p;
261 		size_t				 name_len, value_len;
262 		size_t				 rec_size;
263 
264 		env.hdr.tag = NVRAM_TLV_TYPE_ENV;
265 		env.hdr.size = sizeof(env.flags);
266 		env.flags = 0x0;
267 
268 		/* Fetch name value and add to record length */
269 		name = bhnd_nvram_prop_name(prop);
270 		name_len = strlen(name) + 1 /* '=' */;
271 
272 		if (UINT8_MAX - env.hdr.size < name_len) {
273 			BHND_NV_LOG("%s name exceeds maximum TLV record "
274 			    "length\n", name);
275 			return (EFTYPE); /* would overflow TLV size */
276 		}
277 
278 		env.hdr.size += name_len;
279 
280 		/* Add string value to record length */
281 		error = bhnd_nvram_prop_encode(prop, NULL, &value_len,
282 		    BHND_NVRAM_TYPE_STRING);
283 		if (error) {
284 			BHND_NV_LOG("error serializing %s to required type "
285 			    "%s: %d\n", name,
286 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
287 			    error);
288 			return (error);
289 		}
290 
291 		if (UINT8_MAX - env.hdr.size < value_len) {
292 			BHND_NV_LOG("%s value exceeds maximum TLV record "
293 			    "length\n", name);
294 			return (EFTYPE); /* would overflow TLV size */
295 		}
296 
297 		env.hdr.size += value_len;
298 
299 		/* Calculate total record size */
300 		rec_size = sizeof(env.hdr) + env.hdr.size;
301 		if (SIZE_MAX - nbytes < rec_size)
302 			return (EFTYPE); /* would overflow size_t */
303 
304 		/* Calculate our output pointer */
305 		if (nbytes > limit || limit - nbytes < rec_size) {
306 			/* buffer is full; cannot write */
307 			p = NULL;
308 		} else {
309 			p = (uint8_t *)outp + nbytes;
310 		}
311 
312 		/* Write to output */
313 		if (p != NULL) {
314 			memcpy(p, &env, sizeof(env));
315 			p += sizeof(env);
316 
317 			memcpy(p, name, name_len - 1);
318 			p[name_len - 1] = '=';
319 			p += name_len;
320 
321 			error = bhnd_nvram_prop_encode(prop, p, &value_len,
322 			    BHND_NVRAM_TYPE_STRING);
323 			if (error) {
324 				BHND_NV_LOG("error serializing %s to required "
325 				    "type %s: %d\n", name,
326 				    bhnd_nvram_type_name(
327 					BHND_NVRAM_TYPE_STRING),
328 				    error);
329 				return (error);
330 			}
331 		}
332 
333 		nbytes += rec_size;
334 	}
335 
336 	/* Write terminating END record */
337 	if (limit > nbytes)
338 		*((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END;
339 
340 	if (nbytes == SIZE_MAX)
341 		return (EFTYPE); /* would overflow size_t */
342 	nbytes++;
343 
344 	/* Provide required length */
345 	*olen = nbytes;
346 	if (limit < *olen) {
347 		if (outp == NULL)
348 			return (0);
349 
350 		return (ENOMEM);
351 	}
352 
353 	return (0);
354 }
355 
356 /**
357  * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src.
358  *
359  * @param tlv A newly allocated data instance.
360  */
361 static int
362 bhnd_nvram_tlv_init(struct bhnd_nvram_tlv *tlv, struct bhnd_nvram_io *src)
363 {
364 	struct bhnd_nvram_tlv_env	*env;
365 	size_t				 size;
366 	size_t				 next;
367 	int				 error;
368 
369 	BHND_NV_ASSERT(tlv->data == NULL, ("tlv data already initialized"));
370 
371 	/* Determine the actual size of the TLV source data */
372 	if ((error = bhnd_nvram_tlv_parse_size(src, &size)))
373 		return (error);
374 
375 	/* Copy to our own internal buffer */
376 	if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL)
377 		return (ENOMEM);
378 
379 	/* Initialize our backing buffer */
380 	tlv->count = 0;
381 	next = 0;
382 	while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) {
383 		size_t env_len;
384 		size_t name_len;
385 
386 		/* TLV_ENV data must not be empty */
387 		env_len = NVRAM_TLV_ENVP_DATA_LEN(env);
388 		if (env_len == 0) {
389 			BHND_NV_LOG("cannot parse zero-length TLV_ENV record "
390 			    "data\n");
391 			return (EINVAL);
392 		}
393 
394 		/* Parse the key=value string, and then replace the '='
395 		 * delimiter with '\0' to allow us to provide direct
396 		 * name pointers from our backing buffer */
397 		error = bhnd_nvram_parse_env(env->envp, env_len, '=', NULL,
398 		    &name_len, NULL, NULL);
399 		if (error) {
400 			BHND_NV_LOG("error parsing TLV_ENV data: %d\n", error);
401 			return (error);
402 		}
403 
404 		/* Replace '=' with '\0' */
405 		*(env->envp + name_len) = '\0';
406 
407 		/* Add to variable count */
408 		tlv->count++;
409 	};
410 
411 	return (0);
412 }
413 
414 static int
415 bhnd_nvram_tlv_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
416 {
417 
418 	struct bhnd_nvram_tlv	*tlv;
419 	int			 error;
420 
421 	/* Allocate and initialize the TLV data instance */
422 	tlv = (struct bhnd_nvram_tlv *)nv;
423 
424 	/* Parse the TLV input data and initialize our backing
425 	 * data representation */
426 	if ((error = bhnd_nvram_tlv_init(tlv, io))) {
427 		bhnd_nvram_tlv_free(nv);
428 		return (error);
429 	}
430 
431 	return (0);
432 }
433 
434 static void
435 bhnd_nvram_tlv_free(struct bhnd_nvram_data *nv)
436 {
437 	struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
438 	if (tlv->data != NULL)
439 		bhnd_nvram_io_free(tlv->data);
440 }
441 
442 size_t
443 bhnd_nvram_tlv_count(struct bhnd_nvram_data *nv)
444 {
445 	struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
446 	return (tlv->count);
447 }
448 
449 static bhnd_nvram_plist *
450 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
451 {
452 	return (NULL);
453 }
454 
455 static uint32_t
456 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
457 {
458 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
459 }
460 
461 static const char *
462 bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep)
463 {
464 	struct bhnd_nvram_tlv		*tlv;
465 	struct bhnd_nvram_tlv_env	*env;
466 	size_t				 io_offset;
467 
468 	tlv = (struct bhnd_nvram_tlv *)nv;
469 
470 	/* Find next readable TLV record */
471 	if (*cookiep == NULL) {
472 		/* Start search at offset 0x0 */
473 		io_offset = 0x0;
474 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
475 	} else {
476 		/* Seek past the previous env record */
477 		io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep);
478 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL);
479 		if (env == NULL)
480 			BHND_NV_PANIC("invalid cookiep; record missing");
481 
482 		/* Advance to next env record, update the caller's cookiep */
483 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
484 	}
485 
486 	/* Check for EOF */
487 	if (env == NULL)
488 		return (NULL);
489 
490 	/* Return the NUL terminated name */
491 	return (env->envp);
492 }
493 
494 static void *
495 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
496 {
497 	return (bhnd_nvram_data_generic_find(nv, name));
498 }
499 
500 static int
501 bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
502     void *cookiep2)
503 {
504 	if (cookiep1 < cookiep2)
505 		return (-1);
506 
507 	if (cookiep1 > cookiep2)
508 		return (1);
509 
510 	return (0);
511 }
512 
513 static int
514 bhnd_nvram_tlv_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
515     size_t *len, bhnd_nvram_type type)
516 {
517 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
518 }
519 
520 static int
521 bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
522     bhnd_nvram_val **value)
523 {
524 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
525 }
526 
527 static const void *
528 bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
529     size_t *len, bhnd_nvram_type *type)
530 {
531 	struct bhnd_nvram_tlv		*tlv;
532 	struct bhnd_nvram_tlv_env	*env;
533 	const char			*val;
534 	int				 error;
535 
536 	tlv = (struct bhnd_nvram_tlv *)nv;
537 
538 	/* Fetch pointer to the TLV_ENV record */
539 	if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
540 		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
541 
542 	/* Parse value pointer and length from key\0value data */
543 	error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env),
544 	    '\0', NULL, NULL, &val, len);
545 	if (error)
546 		BHND_NV_PANIC("unexpected error parsing '%s'", env->envp);
547 
548 	/* Type is always CSTR */
549 	*type = BHND_NVRAM_TYPE_STRING;
550 
551 	return (val);
552 }
553 
554 static const char *
555 bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
556 {
557 	struct bhnd_nvram_tlv		*tlv;
558 	const struct bhnd_nvram_tlv_env	*env;
559 
560 	tlv = (struct bhnd_nvram_tlv *)nv;
561 
562 	/* Fetch pointer to the TLV_ENV record */
563 	if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
564 		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
565 
566 	/* Return name pointer */
567 	return (&env->envp[0]);
568 }
569 
570 static int
571 bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
572     bhnd_nvram_val *value, bhnd_nvram_val **result)
573 {
574 	bhnd_nvram_val	*str;
575 	const char	*inp;
576 	bhnd_nvram_type	 itype;
577 	size_t		 ilen;
578 	size_t		 name_len, tlv_nremain;
579 	int		 error;
580 
581 	tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN;
582 
583 	/* Name (trimmed of any path prefix) must be valid */
584 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
585 		return (EINVAL);
586 
587 	/* 'name=' must fit within the maximum TLV_ENV record length */
588 	name_len = strlen(name) + 1; /* '=' */
589 	if (tlv_nremain < name_len) {
590 		BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n",
591 		    name);
592 		return (EINVAL);
593 	}
594 	tlv_nremain -= name_len;
595 
596 	/* Convert value to a (bcm-formatted) string */
597 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
598 	    value, BHND_NVRAM_VAL_DYNAMIC);
599 	if (error)
600 		return (error);
601 
602 	/* The string value must fit within remaining TLV_ENV record length */
603 	inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
604 	if (tlv_nremain < ilen) {
605 		BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n",
606 		    BHND_NV_PRINT_WIDTH(ilen), inp);
607 
608 		bhnd_nvram_val_release(str);
609 		return (EINVAL);
610 	}
611 	tlv_nremain -= name_len;
612 
613 	/* Success. Transfer result ownership to the caller. */
614 	*result = str;
615 	return (0);
616 }
617 
618 static int
619 bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
620 {
621 	/* We permit deletion of any variable */
622 	return (0);
623 }
624 
625 /**
626  * Iterate over the records starting at @p next, returning the parsed
627  * record's @p tag, @p size, and @p offset.
628  *
629  * @param		io		The I/O context to parse.
630  * @param[in,out]	next		The next offset to be parsed, or 0x0
631  *					to begin parsing. Upon successful
632  *					return, will be set to the offset of the
633  *					next record (or EOF, if
634  *					NVRAM_TLV_TYPE_END was parsed).
635  * @param[out]		offset		The record's value offset.
636  * @param[out]		tag		The record's tag.
637  *
638  * @retval 0		success
639  * @retval EINVAL	if parsing @p io as TLV fails.
640  * @retval non-zero	if reading @p io otherwise fails, a regular unix error
641  *			code will be returned.
642  */
643 static int
644 bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t
645     *offset, uint8_t *tag)
646 {
647 	size_t		io_offset, io_size;
648 	uint16_t	parsed_len;
649 	uint8_t		len_hdr[2];
650 	int		error;
651 
652 	io_offset = *next;
653 	io_size = bhnd_nvram_io_getsize(io);
654 
655 	/* Save the record offset */
656 	if (offset != NULL)
657 		*offset = io_offset;
658 
659 	/* Fetch initial tag */
660 	error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag));
661 	if (error)
662 		return (error);
663 	io_offset++;
664 
665 	/* EOF */
666 	if (*tag == NVRAM_TLV_TYPE_END) {
667 		*next = io_offset;
668 		return (0);
669 	}
670 
671 	/* Read length field */
672 	if (*tag & NVRAM_TLV_TF_U8_LEN) {
673 		error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
674 		    sizeof(len_hdr[0]));
675 		if (error) {
676 			BHND_NV_LOG("error reading TLV record size: %d\n",
677 			    error);
678 			return (error);
679 		}
680 
681 		parsed_len = len_hdr[0];
682 		io_offset++;
683 	} else {
684 		error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
685 		    sizeof(len_hdr));
686 		if (error) {
687 			BHND_NV_LOG("error reading 16-bit TLV record "
688 			    "size: %d\n", error);
689 			return (error);
690 		}
691 
692 		parsed_len = (len_hdr[0] << 8) | len_hdr[1];
693 		io_offset += 2;
694 	}
695 
696 	/* Advance to next record */
697 	if (parsed_len > io_size || io_size - parsed_len < io_offset) {
698 		/* Hit early EOF */
699 		BHND_NV_LOG("TLV record length %hu truncated by input "
700 		    "size of %zu\n", parsed_len, io_size);
701 		return (EINVAL);
702 	}
703 
704 	*next = io_offset + parsed_len;
705 
706 	/* Valid record found */
707 	return (0);
708 }
709 
710 /**
711  * Parse the TLV data in @p io to determine the total size of the TLV
712  * data mapped by @p io (which may be less than the size of @p io).
713  */
714 static int
715 bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size)
716 {
717 	size_t		next;
718 	uint8_t		tag;
719 	int		error;
720 
721 	/* We have to perform a minimal parse to determine the actual length */
722 	next = 0x0;
723 	*size = 0x0;
724 
725 	/* Iterate over the input until we hit END tag or the read fails */
726 	do {
727 		error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag);
728 		if (error)
729 			return (error);
730 	} while (tag != NVRAM_TLV_TYPE_END);
731 
732 	/* Offset should now point to EOF */
733 	BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io),
734 	    ("parse returned invalid EOF offset"));
735 
736 	*size = next;
737 	return (0);
738 }
739 
740 /**
741  * Iterate over the records in @p tlv, returning a pointer to the next
742  * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached.
743  *
744  * @param		tlv		The TLV instance.
745  * @param[in,out]	next		The next offset to be parsed, or 0x0
746  *					to begin parsing. Upon successful
747  *					return, will be set to the offset of the
748  *					next record.
749  */
750 static struct bhnd_nvram_tlv_env *
751 bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next,
752     void **cookiep)
753 {
754 	uint8_t	tag;
755 	int	error;
756 
757 	/* Find the next TLV_ENV record, starting at @p next */
758 	do {
759 		void	*c;
760 		size_t	 offset;
761 
762 		/* Fetch the next TLV record */
763 		error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset,
764 		    &tag);
765 		if (error) {
766 			BHND_NV_LOG("unexpected error in next_record(): %d\n",
767 			    error);
768 			return (NULL);
769 		}
770 
771 		/* Only interested in ENV records */
772 		if (tag != NVRAM_TLV_TYPE_ENV)
773 			continue;
774 
775 		/* Map and return TLV_ENV record pointer */
776 		c = bhnd_nvram_tlv_to_cookie(tlv, offset);
777 
778 		/* Provide the cookiep value for the returned record */
779 		if (cookiep != NULL)
780 			*cookiep = c;
781 
782 		return (bhnd_nvram_tlv_get_env(tlv, c));
783 	} while (tag != NVRAM_TLV_TYPE_END);
784 
785 	/* No remaining ENV records */
786 	return (NULL);
787 }
788 
789 /**
790  * Return a pointer to the TLV_ENV record for @p cookiep, or NULL
791  * if none vailable.
792  */
793 static struct bhnd_nvram_tlv_env *
794 bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep)
795 {
796 	struct bhnd_nvram_tlv_env	*env;
797 	void				*ptr;
798 	size_t				 navail;
799 	size_t				 io_offset, io_size;
800 	int				 error;
801 
802 	io_size = bhnd_nvram_io_getsize(tlv->data);
803 	io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep);
804 
805 	/* At EOF? */
806 	if (io_offset == io_size)
807 		return (NULL);
808 
809 	/* Fetch non-const pointer to the record entry */
810 	error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr,
811 	    sizeof(env->hdr), &navail);
812 	if (error) {
813 		/* Should never occur with a valid cookiep */
814 		BHND_NV_LOG("error mapping record for cookiep: %d\n", error);
815 		return (NULL);
816 	}
817 
818 	/* Validate the record pointer */
819 	env = ptr;
820 	if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) {
821 		/* Should never occur with a valid cookiep */
822 		BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep);
823 		return (NULL);
824 	}
825 
826 	/* Is the required variable name data is mapped? */
827 	if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size ||
828 	    env->hdr.size == sizeof(env->flags))
829 	{
830 		/* Should never occur with a valid cookiep */
831 		BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n",
832 		    cookiep);
833 		return (NULL);
834 	}
835 
836 	return (env);
837 }
838 
839 /**
840  * Return a cookiep for the given I/O offset.
841  */
842 static void *
843 bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset)
844 {
845 	const void	*ptr;
846 	int		 error;
847 
848 	BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data),
849 	    ("io_offset %zu out-of-range", io_offset));
850 	BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
851 	    ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
852 
853 	error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL);
854 	if (error)
855 		BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
856 
857 	ptr = (const uint8_t *)ptr + io_offset;
858 	return (__DECONST(void *, ptr));
859 }
860 
861 /* Convert a cookiep back to an I/O offset */
862 static size_t
863 bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep)
864 {
865 	const void	*ptr;
866 	intptr_t	 offset;
867 	size_t		 io_size;
868 	int		 error;
869 
870 	BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
871 
872 	io_size = bhnd_nvram_io_getsize(tlv->data);
873 
874 	error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL);
875 	if (error)
876 		BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
877 
878 	offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
879 	BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
880 	BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
881 	BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
882 
883 	return ((size_t)offset);
884 }
885