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
60struct 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
66BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
67    sizeof(struct bhnd_nvram_tlv))
68
69/** Minimal TLV_ENV record header */
70struct bhnd_nvram_tlv_env_hdr {
71	uint8_t		tag;
72	uint8_t		size;
73} __packed;
74
75/** Minimal TLV_ENV record */
76struct 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
92static int				 bhnd_nvram_tlv_parse_size(
93					     struct bhnd_nvram_io *io,
94					     size_t *size);
95
96static int				 bhnd_nvram_tlv_next_record(
97					     struct bhnd_nvram_io *io,
98					     size_t *next, size_t *offset,
99					     uint8_t *tag);
100
101static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_next_env(
102					     struct bhnd_nvram_tlv *tlv,
103					     size_t *next, void **cookiep);
104
105static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_get_env(
106					     struct bhnd_nvram_tlv *tlv,
107					     void *cookiep);
108
109static void				*bhnd_nvram_tlv_to_cookie(
110					     struct bhnd_nvram_tlv *tlv,
111					     size_t io_offset);
112static size_t				 bhnd_nvram_tlv_to_offset(
113					     struct bhnd_nvram_tlv *tlv,
114					     void *cookiep);
115
116static int
117bhnd_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
168static int
169bhnd_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
240static int
241bhnd_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 */
362static int
363bhnd_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
415static int
416bhnd_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
435static void
436bhnd_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
443size_t
444bhnd_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
451static bhnd_nvram_plist *
452bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
453{
454	return (NULL);
455}
456
457static uint32_t
458bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
459{
460	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
461}
462
463static const char *
464bhnd_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
496static void *
497bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
498{
499	return (bhnd_nvram_data_generic_find(nv, name));
500}
501
502static int
503bhnd_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
515static int
516bhnd_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
522static int
523bhnd_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
529static const void *
530bhnd_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
556static const char *
557bhnd_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
572static int
573bhnd_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
620static int
621bhnd_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 */
645static int
646bhnd_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 */
716static int
717bhnd_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 */
752static struct bhnd_nvram_tlv_env *
753bhnd_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 */
795static struct bhnd_nvram_tlv_env *
796bhnd_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 */
844static void *
845bhnd_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 */
864static size_t
865bhnd_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