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