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