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