]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/bhnd/nvram/bhnd_nvram_data_tlv.c
zfs: merge openzfs/zfs@ec64fdb93 (master) into main
[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 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_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
239 static int
240 bhnd_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  */
361 static int
362 bhnd_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
414 static int
415 bhnd_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
434 static void
435 bhnd_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
442 size_t
443 bhnd_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
449 static bhnd_nvram_plist *
450 bhnd_nvram_tlv_options(struct bhnd_nvram_data *nv)
451 {
452         return (NULL);
453 }
454
455 static uint32_t
456 bhnd_nvram_tlv_caps(struct bhnd_nvram_data *nv)
457 {
458         return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
459 }
460
461 static const char *
462 bhnd_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
494 static void *
495 bhnd_nvram_tlv_find(struct bhnd_nvram_data *nv, const char *name)
496 {
497         return (bhnd_nvram_data_generic_find(nv, name));
498 }
499
500 static int
501 bhnd_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
513 static int
514 bhnd_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
520 static int
521 bhnd_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
527 static const void *
528 bhnd_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
554 static const char *
555 bhnd_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
570 static int
571 bhnd_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
618 static int
619 bhnd_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  */
643 static int
644 bhnd_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  */
714 static int
715 bhnd_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  */
750 static struct bhnd_nvram_tlv_env *
751 bhnd_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  */
793 static struct bhnd_nvram_tlv_env *
794 bhnd_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  */
842 static void *
843 bhnd_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 */
862 static size_t
863 bhnd_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 }