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
58struct 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
64BHND_NVRAM_DATA_CLASS_DEFN(tlv, "WGT634U", BHND_NVRAM_DATA_CAP_DEVPATHS,
65    sizeof(struct bhnd_nvram_tlv))
66
67/** Minimal TLV_ENV record header */
68struct bhnd_nvram_tlv_env_hdr {
69	uint8_t		tag;
70	uint8_t		size;
71} __packed;
72
73/** Minimal TLV_ENV record */
74struct 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
89static int				 bhnd_nvram_tlv_parse_size(
90					     struct bhnd_nvram_io *io,
91					     size_t *size);
92
93static int				 bhnd_nvram_tlv_next_record(
94					     struct bhnd_nvram_io *io,
95					     size_t *next, size_t *offset,
96					     uint8_t *tag);
97
98static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_next_env(
99					     struct bhnd_nvram_tlv *tlv,
100					     size_t *next, void **cookiep);
101
102static struct bhnd_nvram_tlv_env	*bhnd_nvram_tlv_get_env(
103					     struct bhnd_nvram_tlv *tlv,
104					     void *cookiep);
105
106static void				*bhnd_nvram_tlv_to_cookie(
107					     struct bhnd_nvram_tlv *tlv,
108					     size_t io_offset);
109static size_t				 bhnd_nvram_tlv_to_offset(
110					     struct bhnd_nvram_tlv *tlv,
111					     void *cookiep);
112
113static int
114bhnd_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
165static int
166bhnd_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
237static int
238bhnd_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 */
359static int
360bhnd_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
412static int
413bhnd_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
432static void
433bhnd_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
440size_t
441bhnd_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
447static bhnd_nvram_plist *
448bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
449{
450	return (NULL);
451}
452
453static uint32_t
454bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
455{
456	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
457}
458
459static const char *
460bhnd_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
492static void *
493bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
494{
495	return (bhnd_nvram_data_generic_find(nv, name));
496}
497
498static int
499bhnd_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
511static int
512bhnd_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
518static int
519bhnd_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
525static const void *
526bhnd_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
552static const char *
553bhnd_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
568static int
569bhnd_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
616static int
617bhnd_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 */
641static int
642bhnd_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 */
712static int
713bhnd_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 */
748static struct bhnd_nvram_tlv_env *
749bhnd_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 */
791static struct bhnd_nvram_tlv_env *
792bhnd_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 */
840static void *
841bhnd_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 */
860static size_t
861bhnd_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