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