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