xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c (revision 74d9553e43cfafc29448d0bb836916aa21dea0de)
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/malloc.h>
37 #include <sys/systm.h>
38 #else /* !_KERNEL */
39 #include <ctype.h>
40 #include <errno.h>
41 #include <stdint.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #endif /* _KERNEL */
46 
47 #include "bhnd_nvram_private.h"
48 
49 #include "bhnd_nvram_datavar.h"
50 
51 #include "bhnd_nvram_data_tlvreg.h"
52 
53 /*
54  * CFE TLV NVRAM data class.
55  *
56  * The CFE-defined TLV NVRAM format is used on the WGT634U.
57  */
58 
59 struct bhnd_nvram_tlv {
60 	struct bhnd_nvram_data	 nv;	/**< common instance state */
61 	struct bhnd_nvram_io	*data;	/**< backing buffer */
62 	size_t			 count;	/**< variable count */
63 };
64 
65 BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
66     sizeof(struct bhnd_nvram_tlv))
67 
68 /** Minimal TLV_ENV record header */
69 struct bhnd_nvram_tlv_env_hdr {
70 	uint8_t		tag;
71 	uint8_t		size;
72 } __packed;
73 
74 /** Minimal TLV_ENV record */
75 struct bhnd_nvram_tlv_env {
76 	struct bhnd_nvram_tlv_env_hdr	hdr;
77 	uint8_t				flags;
78 	char				envp[];
79 } __packed;
80 
81 /* Return the length in bytes of an TLV_ENV's envp data */
82 #define	NVRAM_TLV_ENVP_DATA_LEN(_env)	\
83 	(((_env)->hdr.size < sizeof((_env)->flags)) ? 0 :	\
84 	    ((_env)->hdr.size - sizeof((_env)->flags)))
85 
86 /* Maximum supported length of the envp data field, in bytes */
87 #define	NVRAM_TLV_ENVP_DATA_MAX_LEN	\
88 	(UINT8_MAX - sizeof(uint8_t) /* flags */)
89 
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_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
169     bhnd_nvram_plist *options, void *outp, size_t *olen)
170 {
171 	bhnd_nvram_prop	*prop;
172 	size_t		 limit, nbytes;
173 	int		 error;
174 
175 	/* Determine output byte limit */
176 	if (outp != NULL)
177 		limit = *olen;
178 	else
179 		limit = 0;
180 
181 	nbytes = 0;
182 
183 	/* Write all properties */
184 	prop = NULL;
185 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
186 		struct bhnd_nvram_tlv_env	 env;
187 		const char			*name;
188 		uint8_t				*p;
189 		size_t				 name_len, value_len;
190 		size_t				 rec_size;
191 
192 		env.hdr.tag = NVRAM_TLV_TYPE_ENV;
193 		env.hdr.size = sizeof(env.flags);
194 		env.flags = 0x0;
195 
196 		/* Fetch name value and add to record length */
197 		name = bhnd_nvram_prop_name(prop);
198 		name_len = strlen(name) + 1 /* '=' */;
199 
200 		if (UINT8_MAX - env.hdr.size < name_len) {
201 			BHND_NV_LOG("%s name exceeds maximum TLV record "
202 			    "length\n", name);
203 			return (EFTYPE); /* would overflow TLV size */
204 		}
205 
206 		env.hdr.size += name_len;
207 
208 		/* Add string value to record length */
209 		error = bhnd_nvram_prop_encode(prop, NULL, &value_len,
210 		    BHND_NVRAM_TYPE_STRING);
211 		if (error) {
212 			BHND_NV_LOG("error serializing %s to required type "
213 			    "%s: %d\n", name,
214 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
215 			    error);
216 			return (error);
217 		}
218 
219 		if (UINT8_MAX - env.hdr.size < value_len) {
220 			BHND_NV_LOG("%s value exceeds maximum TLV record "
221 			    "length\n", name);
222 			return (EFTYPE); /* would overflow TLV size */
223 		}
224 
225 		env.hdr.size += value_len;
226 
227 		/* Calculate total record size */
228 		rec_size = sizeof(env.hdr) + env.hdr.size;
229 		if (SIZE_MAX - nbytes < rec_size)
230 			return (EFTYPE); /* would overflow size_t */
231 
232 		/* Calculate our output pointer */
233 		if (nbytes > limit || limit - nbytes < rec_size) {
234 			/* buffer is full; cannot write */
235 			p = NULL;
236 		} else {
237 			p = (uint8_t *)outp + nbytes;
238 		}
239 
240 		/* Write to output */
241 		if (p != NULL) {
242 			memcpy(p, &env, sizeof(env));
243 			p += sizeof(env);
244 
245 			memcpy(p, name, name_len - 1);
246 			p[name_len - 1] = '=';
247 			p += name_len;
248 
249 			error = bhnd_nvram_prop_encode(prop, p, &value_len,
250 			    BHND_NVRAM_TYPE_STRING);
251 			if (error) {
252 				BHND_NV_LOG("error serializing %s to required "
253 				    "type %s: %d\n", name,
254 				    bhnd_nvram_type_name(
255 					BHND_NVRAM_TYPE_STRING),
256 				    error);
257 				return (error);
258 			}
259 		}
260 
261 		nbytes += rec_size;
262 	}
263 
264 	/* Write terminating END record */
265 	if (limit > nbytes)
266 		*((uint8_t *)outp + nbytes) = NVRAM_TLV_TYPE_END;
267 
268 	if (nbytes == SIZE_MAX)
269 		return (EFTYPE); /* would overflow size_t */
270 	nbytes++;
271 
272 	/* Provide required length */
273 	*olen = nbytes;
274 	if (limit < *olen) {
275 		if (outp == NULL)
276 			return (0);
277 
278 		return (ENOMEM);
279 	}
280 
281 	return (0);
282 }
283 
284 /**
285  * Initialize @p tlv with the provided NVRAM TLV data mapped by @p src.
286  *
287  * @param tlv A newly allocated data instance.
288  */
289 static int
290 bhnd_nvram_tlv_init(struct bhnd_nvram_tlv *tlv, struct bhnd_nvram_io *src)
291 {
292 	struct bhnd_nvram_tlv_env	*env;
293 	size_t				 size;
294 	size_t				 next;
295 	int				 error;
296 
297 	BHND_NV_ASSERT(tlv->data == NULL, ("tlv data already initialized"));
298 
299 	/* Determine the actual size of the TLV source data */
300 	if ((error = bhnd_nvram_tlv_parse_size(src, &size)))
301 		return (error);
302 
303 	/* Copy to our own internal buffer */
304 	if ((tlv->data = bhnd_nvram_iobuf_copy_range(src, 0x0, size)) == NULL)
305 		return (ENOMEM);
306 
307 	/* Initialize our backing buffer */
308 	tlv->count = 0;
309 	next = 0;
310 	while ((env = bhnd_nvram_tlv_next_env(tlv, &next, NULL)) != NULL) {
311 		size_t env_len;
312 		size_t name_len;
313 
314 		/* TLV_ENV data must not be empty */
315 		env_len = NVRAM_TLV_ENVP_DATA_LEN(env);
316 		if (env_len == 0) {
317 			BHND_NV_LOG("cannot parse zero-length TLV_ENV record "
318 			    "data\n");
319 			return (EINVAL);
320 		}
321 
322 		/* Parse the key=value string, and then replace the '='
323 		 * delimiter with '\0' to allow us to provide direct
324 		 * name pointers from our backing buffer */
325 		error = bhnd_nvram_parse_env(env->envp, env_len, '=', NULL,
326 		    &name_len, NULL, NULL);
327 		if (error) {
328 			BHND_NV_LOG("error parsing TLV_ENV data: %d\n", error);
329 			return (error);
330 		}
331 
332 		/* Replace '=' with '\0' */
333 		*(env->envp + name_len) = '\0';
334 
335 		/* Add to variable count */
336 		tlv->count++;
337 	};
338 
339 	return (0);
340 }
341 
342 static int
343 bhnd_nvram_tlv_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
344 {
345 
346 	struct bhnd_nvram_tlv	*tlv;
347 	int			 error;
348 
349 	/* Allocate and initialize the TLV data instance */
350 	tlv = (struct bhnd_nvram_tlv *)nv;
351 
352 	/* Parse the TLV input data and initialize our backing
353 	 * data representation */
354 	if ((error = bhnd_nvram_tlv_init(tlv, io))) {
355 		bhnd_nvram_tlv_free(nv);
356 		return (error);
357 	}
358 
359 	return (0);
360 }
361 
362 static void
363 bhnd_nvram_tlv_free(struct bhnd_nvram_data *nv)
364 {
365 	struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
366 	if (tlv->data != NULL)
367 		bhnd_nvram_io_free(tlv->data);
368 }
369 
370 size_t
371 bhnd_nvram_tlv_count(struct bhnd_nvram_data *nv)
372 {
373 	struct bhnd_nvram_tlv *tlv = (struct bhnd_nvram_tlv *)nv;
374 	return (tlv->count);
375 }
376 
377 
378 static bhnd_nvram_plist *
379 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
380 {
381 	return (NULL);
382 }
383 
384 static uint32_t
385 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
386 {
387 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
388 }
389 
390 static const char *
391 bhnd_nvram_tlv_next(struct bhnd_nvram_data *nv, void **cookiep)
392 {
393 	struct bhnd_nvram_tlv		*tlv;
394 	struct bhnd_nvram_tlv_env	*env;
395 	size_t				 io_offset;
396 
397 	tlv = (struct bhnd_nvram_tlv *)nv;
398 
399 	/* Find next readable TLV record */
400 	if (*cookiep == NULL) {
401 		/* Start search at offset 0x0 */
402 		io_offset = 0x0;
403 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
404 	} else {
405 		/* Seek past the previous env record */
406 		io_offset = bhnd_nvram_tlv_to_offset(tlv, *cookiep);
407 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, NULL);
408 		if (env == NULL)
409 			BHND_NV_PANIC("invalid cookiep; record missing");
410 
411 		/* Advance to next env record, update the caller's cookiep */
412 		env = bhnd_nvram_tlv_next_env(tlv, &io_offset, cookiep);
413 	}
414 
415 	/* Check for EOF */
416 	if (env == NULL)
417 		return (NULL);
418 
419 	/* Return the NUL terminated name */
420 	return (env->envp);
421 }
422 
423 static void *
424 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
425 {
426 	return (bhnd_nvram_data_generic_find(nv, name));
427 }
428 
429 static int
430 bhnd_nvram_tlv_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
431     void *cookiep2)
432 {
433 	if (cookiep1 < cookiep2)
434 		return (-1);
435 
436 	if (cookiep1 > cookiep2)
437 		return (1);
438 
439 	return (0);
440 }
441 
442 static int
443 bhnd_nvram_tlv_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
444     size_t *len, bhnd_nvram_type type)
445 {
446 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
447 }
448 
449 static int
450 bhnd_nvram_tlv_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
451     bhnd_nvram_val **value)
452 {
453 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
454 }
455 
456 static const void *
457 bhnd_nvram_tlv_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
458     size_t *len, bhnd_nvram_type *type)
459 {
460 	struct bhnd_nvram_tlv		*tlv;
461 	struct bhnd_nvram_tlv_env	*env;
462 	const char			*val;
463 	int				 error;
464 
465 	tlv = (struct bhnd_nvram_tlv *)nv;
466 
467 	/* Fetch pointer to the TLV_ENV record */
468 	if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
469 		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
470 
471 	/* Parse value pointer and length from key\0value data */
472 	error = bhnd_nvram_parse_env(env->envp, NVRAM_TLV_ENVP_DATA_LEN(env),
473 	    '\0', NULL, NULL, &val, len);
474 	if (error)
475 		BHND_NV_PANIC("unexpected error parsing '%s'", env->envp);
476 
477 	/* Type is always CSTR */
478 	*type = BHND_NVRAM_TYPE_STRING;
479 
480 	return (val);
481 }
482 
483 static const char *
484 bhnd_nvram_tlv_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
485 {
486 	struct bhnd_nvram_tlv		*tlv;
487 	const struct bhnd_nvram_tlv_env	*env;
488 
489 	tlv = (struct bhnd_nvram_tlv *)nv;
490 
491 	/* Fetch pointer to the TLV_ENV record */
492 	if ((env = bhnd_nvram_tlv_get_env(tlv, cookiep)) == NULL)
493 		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
494 
495 	/* Return name pointer */
496 	return (&env->envp[0]);
497 }
498 
499 static int
500 bhnd_nvram_tlv_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
501     bhnd_nvram_val *value, bhnd_nvram_val **result)
502 {
503 	bhnd_nvram_val	*str;
504 	const char	*inp;
505 	bhnd_nvram_type	 itype;
506 	size_t		 ilen;
507 	size_t		 name_len, tlv_nremain;
508 	int		 error;
509 
510 	tlv_nremain = NVRAM_TLV_ENVP_DATA_MAX_LEN;
511 
512 	/* Name (trimmed of any path prefix) must be valid */
513 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
514 		return (EINVAL);
515 
516 	/* 'name=' must fit within the maximum TLV_ENV record length */
517 	name_len = strlen(name) + 1; /* '=' */
518 	if (tlv_nremain < name_len) {
519 		BHND_NV_LOG("'%s=' exceeds maximum TLV_ENV record length\n",
520 		    name);
521 		return (EINVAL);
522 	}
523 	tlv_nremain -= name_len;
524 
525 	/* Convert value to a (bcm-formatted) string */
526 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
527 	    value, BHND_NVRAM_VAL_DYNAMIC);
528 	if (error)
529 		return (error);
530 
531 	/* The string value must fit within remaining TLV_ENV record length */
532 	inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
533 	if (tlv_nremain < ilen) {
534 		BHND_NV_LOG("'%.*s\\0' exceeds maximum TLV_ENV record length\n",
535 		    BHND_NV_PRINT_WIDTH(ilen), inp);
536 
537 		bhnd_nvram_val_release(str);
538 		return (EINVAL);
539 	}
540 	tlv_nremain -= name_len;
541 
542 	/* Success. Transfer result ownership to the caller. */
543 	*result = str;
544 	return (0);
545 }
546 
547 static int
548 bhnd_nvram_tlv_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
549 {
550 	/* We permit deletion of any variable */
551 	return (0);
552 }
553 
554 /**
555  * Iterate over the records starting at @p next, returning the parsed
556  * record's @p tag, @p size, and @p offset.
557  *
558  * @param		io		The I/O context to parse.
559  * @param[in,out]	next		The next offset to be parsed, or 0x0
560  *					to begin parsing. Upon successful
561  *					return, will be set to the offset of the
562  *					next record (or EOF, if
563  *					NVRAM_TLV_TYPE_END was parsed).
564  * @param[out]		offset		The record's value offset.
565  * @param[out]		tag		The record's tag.
566  *
567  * @retval 0		success
568  * @retval EINVAL	if parsing @p io as TLV fails.
569  * @retval non-zero	if reading @p io otherwise fails, a regular unix error
570  *			code will be returned.
571  */
572 static int
573 bhnd_nvram_tlv_next_record(struct bhnd_nvram_io *io, size_t *next, size_t
574     *offset, uint8_t *tag)
575 {
576 	size_t		io_offset, io_size;
577 	uint16_t	parsed_len;
578 	uint8_t		len_hdr[2];
579 	int		error;
580 
581 	io_offset = *next;
582 	io_size = bhnd_nvram_io_getsize(io);
583 
584 	/* Save the record offset */
585 	if (offset != NULL)
586 		*offset = io_offset;
587 
588 	/* Fetch initial tag */
589 	error = bhnd_nvram_io_read(io, io_offset, tag, sizeof(*tag));
590 	if (error)
591 		return (error);
592 	io_offset++;
593 
594 	/* EOF */
595 	if (*tag == NVRAM_TLV_TYPE_END) {
596 		*next = io_offset;
597 		return (0);
598 	}
599 
600 	/* Read length field */
601 	if (*tag & NVRAM_TLV_TF_U8_LEN) {
602 		error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
603 		    sizeof(len_hdr[0]));
604 		if (error) {
605 			BHND_NV_LOG("error reading TLV record size: %d\n",
606 			    error);
607 			return (error);
608 		}
609 
610 		parsed_len = len_hdr[0];
611 		io_offset++;
612 	} else {
613 		error = bhnd_nvram_io_read(io, io_offset, &len_hdr,
614 		    sizeof(len_hdr));
615 		if (error) {
616 			BHND_NV_LOG("error reading 16-bit TLV record "
617 			    "size: %d\n", error);
618 			return (error);
619 		}
620 
621 		parsed_len = (len_hdr[0] << 8) | len_hdr[1];
622 		io_offset += 2;
623 	}
624 
625 	/* Advance to next record */
626 	if (parsed_len > io_size || io_size - parsed_len < io_offset) {
627 		/* Hit early EOF */
628 		BHND_NV_LOG("TLV record length %hu truncated by input "
629 		    "size of %zu\n", parsed_len, io_size);
630 		return (EINVAL);
631 	}
632 
633 	*next = io_offset + parsed_len;
634 
635 	/* Valid record found */
636 	return (0);
637 }
638 
639 /**
640  * Parse the TLV data in @p io to determine the total size of the TLV
641  * data mapped by @p io (which may be less than the size of @p io).
642  */
643 static int
644 bhnd_nvram_tlv_parse_size(struct bhnd_nvram_io *io, size_t *size)
645 {
646 	size_t		next;
647 	uint8_t		tag;
648 	int		error;
649 
650 	/* We have to perform a minimal parse to determine the actual length */
651 	next = 0x0;
652 	*size = 0x0;
653 
654 	/* Iterate over the input until we hit END tag or the read fails */
655 	do {
656 		error = bhnd_nvram_tlv_next_record(io, &next, NULL, &tag);
657 		if (error)
658 			return (error);
659 	} while (tag != NVRAM_TLV_TYPE_END);
660 
661 	/* Offset should now point to EOF */
662 	BHND_NV_ASSERT(next <= bhnd_nvram_io_getsize(io),
663 	    ("parse returned invalid EOF offset"));
664 
665 	*size = next;
666 	return (0);
667 }
668 
669 /**
670  * Iterate over the records in @p tlv, returning a pointer to the next
671  * NVRAM_TLV_TYPE_ENV record, or NULL if EOF is reached.
672  *
673  * @param		tlv		The TLV instance.
674  * @param[in,out]	next		The next offset to be parsed, or 0x0
675  *					to begin parsing. Upon successful
676  *					return, will be set to the offset of the
677  *					next record.
678  */
679 static struct bhnd_nvram_tlv_env *
680 bhnd_nvram_tlv_next_env(struct bhnd_nvram_tlv *tlv, size_t *next,
681     void **cookiep)
682 {
683 	uint8_t	tag;
684 	int	error;
685 
686 	/* Find the next TLV_ENV record, starting at @p next */
687 	do {
688 		void	*c;
689 		size_t	 offset;
690 
691 		/* Fetch the next TLV record */
692 		error = bhnd_nvram_tlv_next_record(tlv->data, next, &offset,
693 		    &tag);
694 		if (error) {
695 			BHND_NV_LOG("unexpected error in next_record(): %d\n",
696 			    error);
697 			return (NULL);
698 		}
699 
700 		/* Only interested in ENV records */
701 		if (tag != NVRAM_TLV_TYPE_ENV)
702 			continue;
703 
704 		/* Map and return TLV_ENV record pointer */
705 		c = bhnd_nvram_tlv_to_cookie(tlv, offset);
706 
707 		/* Provide the cookiep value for the returned record */
708 		if (cookiep != NULL)
709 			*cookiep = c;
710 
711 		return (bhnd_nvram_tlv_get_env(tlv, c));
712 	} while (tag != NVRAM_TLV_TYPE_END);
713 
714 	/* No remaining ENV records */
715 	return (NULL);
716 }
717 
718 /**
719  * Return a pointer to the TLV_ENV record for @p cookiep, or NULL
720  * if none vailable.
721  */
722 static struct bhnd_nvram_tlv_env *
723 bhnd_nvram_tlv_get_env(struct bhnd_nvram_tlv *tlv, void *cookiep)
724 {
725 	struct bhnd_nvram_tlv_env	*env;
726 	void				*ptr;
727 	size_t				 navail;
728 	size_t				 io_offset, io_size;
729 	int				 error;
730 
731 	io_size = bhnd_nvram_io_getsize(tlv->data);
732 	io_offset = bhnd_nvram_tlv_to_offset(tlv, cookiep);
733 
734 	/* At EOF? */
735 	if (io_offset == io_size)
736 		return (NULL);
737 
738 	/* Fetch non-const pointer to the record entry */
739 	error = bhnd_nvram_io_write_ptr(tlv->data, io_offset, &ptr,
740 	    sizeof(env->hdr), &navail);
741 	if (error) {
742 		/* Should never occur with a valid cookiep */
743 		BHND_NV_LOG("error mapping record for cookiep: %d\n", error);
744 		return (NULL);
745 	}
746 
747 	/* Validate the record pointer */
748 	env = ptr;
749 	if (env->hdr.tag != NVRAM_TLV_TYPE_ENV) {
750 		/* Should never occur with a valid cookiep */
751 		BHND_NV_LOG("non-ENV record mapped for %p\n", cookiep);
752 		return (NULL);
753 	}
754 
755 	/* Is the required variable name data is mapped? */
756 	if (navail < sizeof(struct bhnd_nvram_tlv_env_hdr) + env->hdr.size ||
757 	    env->hdr.size == sizeof(env->flags))
758 	{
759 		/* Should never occur with a valid cookiep */
760 		BHND_NV_LOG("TLV_ENV variable data not mapped for %p\n",
761 		    cookiep);
762 		return (NULL);
763 	}
764 
765 	return (env);
766 }
767 
768 /**
769  * Return a cookiep for the given I/O offset.
770  */
771 static void *
772 bhnd_nvram_tlv_to_cookie(struct bhnd_nvram_tlv *tlv, size_t io_offset)
773 {
774 	const void	*ptr;
775 	int		 error;
776 
777 	BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(tlv->data),
778 	    ("io_offset %zu out-of-range", io_offset));
779 	BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
780 	    ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
781 
782 	error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_offset, NULL);
783 	if (error)
784 		BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
785 
786 	ptr = (const uint8_t *)ptr + io_offset;
787 	return (__DECONST(void *, ptr));
788 }
789 
790 /* Convert a cookiep back to an I/O offset */
791 static size_t
792 bhnd_nvram_tlv_to_offset(struct bhnd_nvram_tlv *tlv, void *cookiep)
793 {
794 	const void	*ptr;
795 	intptr_t	 offset;
796 	size_t		 io_size;
797 	int		 error;
798 
799 	BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
800 
801 	io_size = bhnd_nvram_io_getsize(tlv->data);
802 
803 	error = bhnd_nvram_io_read_ptr(tlv->data, 0x0, &ptr, io_size, NULL);
804 	if (error)
805 		BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
806 
807 	offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
808 	BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
809 	BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
810 	BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
811 
812 	return ((size_t)offset);
813 }
814