]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c
MFV r350898: 8423 8199 7432 Implement large_dnode pool feature
[FreeBSD/FreeBSD.git] / sys / dev / bhnd / nvram / bhnd_nvram_data_btxt.c
1 /*-
2  * Copyright (c) 2015-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 #include <sys/endian.h>
34
35 #ifdef _KERNEL
36
37 #include <sys/param.h>
38 #include <sys/ctype.h>
39 #include <sys/malloc.h>
40 #include <sys/systm.h>
41
42 #else /* !_KERNEL */
43
44 #include <ctype.h>
45 #include <stdint.h>
46 #include <stdlib.h>
47 #include <string.h>
48
49 #endif /* _KERNEL */
50
51 #include "bhnd_nvram_private.h"
52
53 #include "bhnd_nvram_datavar.h"
54
55 #include "bhnd_nvram_data_bcmreg.h"     /* for BCM_NVRAM_MAGIC */
56
57 /**
58  * Broadcom "Board Text" data class.
59  *
60  * This format is used to provide external NVRAM data for some
61  * fullmac WiFi devices, and as an input format when programming
62  * NVRAM/SPROM/OTP.
63  */
64
65 struct bhnd_nvram_btxt {
66         struct bhnd_nvram_data   nv;    /**< common instance state */
67         struct bhnd_nvram_io    *data;  /**< memory-backed board text data */
68         size_t                   count; /**< variable count */
69 };
70
71 BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text",
72     BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt))
73
74 /** Minimal identification header */
75 union bhnd_nvram_btxt_ident {
76         uint32_t        bcm_magic;
77         char            btxt[8];
78 };
79
80 static void     *bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
81                  size_t io_offset);
82 static size_t    bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt,
83                      void *cookiep);
84
85 static int      bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io,
86                     size_t offset, size_t *line_len, size_t *env_len);
87 static int      bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io,
88                     size_t *offset);
89 static int      bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io,
90                     size_t *offset);
91
92 static int
93 bhnd_nvram_btxt_probe(struct bhnd_nvram_io *io)
94 {
95         union bhnd_nvram_btxt_ident     ident;
96         char                            c;
97         int                             error;
98
99         /* Look at the initial header for something that looks like 
100          * an ASCII board text file */
101         if ((error = bhnd_nvram_io_read(io, 0x0, &ident, sizeof(ident))))
102                 return (error);
103
104         /* The BCM NVRAM format uses a 'FLSH' little endian magic value, which
105          * shouldn't be interpreted as BTXT */
106         if (le32toh(ident.bcm_magic) == BCM_NVRAM_MAGIC)
107                 return (ENXIO);
108
109         /* Don't match on non-ASCII/non-printable data */
110         for (size_t i = 0; i < nitems(ident.btxt); i++) {
111                 c = ident.btxt[i];
112                 if (!bhnd_nv_isprint(c))
113                         return (ENXIO);
114         }
115
116         /* The first character should either be a valid key char (alpha),
117          * whitespace, or the start of a comment ('#') */
118         c = ident.btxt[0];
119         if (!bhnd_nv_isspace(c) && !bhnd_nv_isalpha(c) && c != '#')
120                 return (ENXIO);
121
122         /* We assert a low priority, given that we've only scanned an
123          * initial few bytes of the file. */
124         return (BHND_NVRAM_DATA_PROBE_MAYBE);
125 }
126
127
128 /**
129  * Parser states for bhnd_nvram_bcm_getvar_direct_common().
130  */
131 typedef enum {
132         BTXT_PARSE_LINE_START,
133         BTXT_PARSE_KEY,
134         BTXT_PARSE_KEY_END,
135         BTXT_PARSE_NEXT_LINE,
136         BTXT_PARSE_VALUE_START,
137         BTXT_PARSE_VALUE
138 } btxt_parse_state;
139
140 static int
141 bhnd_nvram_btxt_getvar_direct(struct bhnd_nvram_io *io, const char *name,
142     void *outp, size_t *olen, bhnd_nvram_type otype)
143 {
144         char                             buf[512];
145         btxt_parse_state                 pstate;
146         size_t                           limit, offset;
147         size_t                           buflen, bufpos;
148         size_t                           namelen, namepos;
149         size_t                           vlen;
150         int                              error;
151
152         limit = bhnd_nvram_io_getsize(io);
153         offset = 0;
154
155         /* Loop our parser until we find the requested variable, or hit EOF */
156         pstate = BTXT_PARSE_LINE_START;
157         buflen = 0;
158         bufpos = 0;
159         namelen = strlen(name);
160         namepos = 0;
161         vlen = 0;
162
163         while ((offset - bufpos) < limit) {
164                 BHND_NV_ASSERT(bufpos <= buflen,
165                     ("buf position invalid (%zu > %zu)", bufpos, buflen));
166                 BHND_NV_ASSERT(buflen <= sizeof(buf),
167                     ("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
168
169                 /* Repopulate our parse buffer? */
170                 if (buflen - bufpos == 0) {
171                         BHND_NV_ASSERT(offset < limit, ("offset overrun"));
172
173                         buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
174                         bufpos = 0;
175
176                         error = bhnd_nvram_io_read(io, offset, buf, buflen);
177                         if (error)
178                                 return (error);
179
180                         offset += buflen;
181                 }
182
183                 switch (pstate) {
184                 case BTXT_PARSE_LINE_START:
185                         BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
186
187                         /* Reset name matching position */
188                         namepos = 0;
189
190                         /* Trim any leading whitespace */
191                         while (bufpos < buflen && bhnd_nv_isspace(buf[bufpos]))
192                         {
193                                 bufpos++;
194                         }
195
196                         if (bufpos == buflen) {
197                                 /* Continue parsing the line */
198                                 pstate = BTXT_PARSE_LINE_START;
199                         } else if (bufpos < buflen && buf[bufpos] == '#') {
200                                 /* Comment; skip to next line */
201                                 pstate = BTXT_PARSE_NEXT_LINE;
202                         } else {
203                                 /* Start name matching */
204                                 pstate = BTXT_PARSE_KEY;
205                         }
206
207
208                         break;
209
210                 case BTXT_PARSE_KEY: {
211                         size_t navail, nleft;
212
213                         nleft = namelen - namepos;
214                         navail = bhnd_nv_ummin(buflen - bufpos, nleft);
215
216                         if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
217                                 /* Matched */
218                                 namepos += navail;
219                                 bufpos += navail;
220
221                                 if (namepos == namelen) {
222                                         /* Matched the full variable; look for
223                                          * its trailing delimiter */
224                                         pstate = BTXT_PARSE_KEY_END;
225                                 } else {
226                                         /* Continue matching the name */
227                                         pstate = BTXT_PARSE_KEY;
228                                 }
229                         } else {
230                                 /* No match; advance to next entry and restart
231                                  * name matching */
232                                 pstate = BTXT_PARSE_NEXT_LINE;
233                         }
234
235                         break;
236                 }
237
238                 case BTXT_PARSE_KEY_END:
239                         BHND_NV_ASSERT(bufpos < buflen, ("empty buffer!"));
240
241                         if (buf[bufpos] == '=') {
242                                 /* Key fully matched; advance past '=' and
243                                  * parse the value */
244                                 bufpos++;
245                                 pstate = BTXT_PARSE_VALUE_START;
246                         } else {
247                                 /* No match; advance to next line and restart
248                                  * name matching */
249                                 pstate = BTXT_PARSE_NEXT_LINE;
250                         }
251
252                         break;
253
254                 case BTXT_PARSE_NEXT_LINE: {
255                         const char *p;
256
257                         /* Scan for a '\r', '\n', or '\r\n' terminator */
258                         p = memchr(buf+bufpos, '\n', buflen - bufpos);
259                         if (p == NULL)
260                                 p = memchr(buf+bufpos, '\r', buflen - bufpos);
261
262                         if (p != NULL) {
263                                 /* Found entry terminator; restart name
264                                  * matching at next line */
265                                 pstate = BTXT_PARSE_LINE_START;
266                                 bufpos = (p - buf);
267                         } else {
268                                 /* Consumed full buffer looking for newline; 
269                                  * force repopulation of the buffer and
270                                  * retry */
271                                 pstate = BTXT_PARSE_NEXT_LINE;
272                                 bufpos = buflen;
273                         }
274
275                         break;
276                 }
277
278                 case BTXT_PARSE_VALUE_START: {
279                         const char *p;
280
281                         /* Scan for a terminating newline */
282                         p = memchr(buf+bufpos, '\n', buflen - bufpos);
283                         if (p == NULL)
284                                 p = memchr(buf+bufpos, '\r', buflen - bufpos);
285
286                         if (p != NULL) {
287                                 /* Found entry terminator; parse the value */
288                                 vlen = p - &buf[bufpos];
289                                 pstate = BTXT_PARSE_VALUE;
290
291                         } else if (p == NULL && offset == limit) {
292                                 /* Hit EOF without a terminating newline;
293                                  * treat the entry as implicitly terminated */
294                                 vlen = buflen - bufpos;
295                                 pstate = BTXT_PARSE_VALUE;
296
297                         } else if (p == NULL && bufpos > 0) {
298                                 size_t  nread;
299
300                                 /* Move existing value data to start of
301                                  * buffer */
302                                 memmove(buf, buf+bufpos, buflen - bufpos);
303                                 buflen = bufpos;
304                                 bufpos = 0;
305
306                                 /* Populate full buffer to allow retry of
307                                  * value parsing */
308                                 nread = bhnd_nv_ummin(sizeof(buf) - buflen,
309                                     limit - offset);
310
311                                 error = bhnd_nvram_io_read(io, offset,
312                                     buf+buflen, nread);
313                                 if (error)
314                                         return (error);
315
316                                 offset += nread;
317                                 buflen += nread;
318                         } else {
319                                 /* Value exceeds our buffer capacity */
320                                 BHND_NV_LOG("cannot parse value for '%s' "
321                                     "(exceeds %zu byte limit)\n", name,
322                                     sizeof(buf));
323
324                                 return (ENXIO);
325                         }
326
327                         break;
328                 }
329
330                 case BTXT_PARSE_VALUE:
331                         BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
332
333                         /* Trim any trailing whitespace */
334                         while (vlen > 0 && bhnd_nv_isspace(buf[bufpos+vlen-1]))
335                                 vlen--;
336
337                         /* Write the value to the caller's buffer */
338                         return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
339                             BHND_NVRAM_TYPE_STRING, outp, olen, otype));
340                 }
341         }
342
343         /* Variable not found */
344         return (ENOENT);
345 }
346
347 static int
348 bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
349     bhnd_nvram_plist *options, void *outp, size_t *olen)
350 {
351         bhnd_nvram_prop *prop;
352         size_t           limit, nbytes;
353         int              error;
354
355         /* Determine output byte limit */
356         if (outp != NULL)
357                 limit = *olen;
358         else
359                 limit = 0;
360
361         nbytes = 0;
362
363         /* Write all properties */
364         prop = NULL;
365         while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
366                 const char      *name;
367                 char            *p;
368                 size_t           prop_limit;
369                 size_t           name_len, value_len;
370
371                 if (outp == NULL || limit < nbytes) {
372                         p = NULL;
373                         prop_limit = 0;
374                 } else {
375                         p = ((char *)outp) + nbytes;
376                         prop_limit = limit - nbytes;
377                 }
378
379                 /* Fetch and write 'name=' to output */
380                 name = bhnd_nvram_prop_name(prop);
381                 name_len = strlen(name) + 1;
382
383                 if (prop_limit > name_len) {
384                         memcpy(p, name, name_len - 1);
385                         p[name_len - 1] = '=';
386
387                         prop_limit -= name_len;
388                         p += name_len;
389                 } else {
390                         prop_limit = 0;
391                         p = NULL;
392                 }
393
394                 /* Advance byte count */
395                 if (SIZE_MAX - nbytes < name_len)
396                         return (EFTYPE); /* would overflow size_t */
397
398                 nbytes += name_len;
399
400                 /* Write NUL-terminated value to output, rewrite NUL as
401                  * '\n' record delimiter */
402                 value_len = prop_limit;
403                 error = bhnd_nvram_prop_encode(prop, p, &value_len,
404                     BHND_NVRAM_TYPE_STRING);
405                 if (p != NULL && error == 0) {
406                         /* Replace trailing '\0' with newline */
407                         BHND_NV_ASSERT(value_len > 0, ("string length missing "
408                             "minimum required trailing NUL"));
409
410                         *(p + (value_len - 1)) = '\n';
411                 } else if (error && error != ENOMEM) {
412                         /* If encoding failed for any reason other than ENOMEM
413                          * (which we'll detect and report after encoding all
414                          * properties), return immediately */
415                         BHND_NV_LOG("error serializing %s to required type "
416                             "%s: %d\n", name,
417                             bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
418                             error);
419                         return (error);
420                 }
421
422                 /* Advance byte count */
423                 if (SIZE_MAX - nbytes < value_len)
424                         return (EFTYPE); /* would overflow size_t */
425
426                 nbytes += value_len;
427         }
428
429         /* Provide required length */
430         *olen = nbytes;
431         if (limit < *olen) {
432                 if (outp == NULL)
433                         return (0);
434
435                 return (ENOMEM);
436         }
437
438         return (0);
439 }
440
441 /**
442  * Initialize @p btxt with the provided board text data mapped by @p src.
443  * 
444  * @param btxt A newly allocated data instance.
445  */
446 static int
447 bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src)
448 {
449         const void              *ptr;
450         const char              *name, *value;
451         size_t                   name_len, value_len;
452         size_t                   line_len, env_len;
453         size_t                   io_offset, io_size, str_size;
454         int                      error;
455
456         BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated"));
457         
458         if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL)
459                 return (ENOMEM);
460
461         io_size = bhnd_nvram_io_getsize(btxt->data);
462         io_offset = 0;
463
464         /* Fetch a pointer mapping the entirity of the board text data */
465         error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
466         if (error)
467                 return (error);
468
469         /* Determine the actual size, minus any terminating NUL. We
470          * parse NUL-terminated C strings, but do not include NUL termination
471          * in our internal or serialized representations */
472         str_size = strnlen(ptr, io_size);
473
474         /* If the terminating NUL is not found at the end of the buffer,
475          * this is BCM-RAW or other NUL-delimited NVRAM format. */
476         if (str_size < io_size && str_size + 1 < io_size)
477                 return (EINVAL);
478
479         /* Adjust buffer size to account for NUL termination (if any) */
480         io_size = str_size;
481         if ((error = bhnd_nvram_io_setsize(btxt->data, io_size)))
482                 return (error);
483
484         /* Process the buffer */
485         btxt->count = 0;
486         while (io_offset < io_size) {
487                 const void      *envp;
488
489                 /* Seek to the next key=value entry */
490                 if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset)))
491                         return (error);
492
493                 /* Determine the entry and line length */
494                 error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset,
495                     &line_len, &env_len);
496                 if (error)
497                         return (error);
498         
499                 /* EOF? */
500                 if (env_len == 0) {
501                         BHND_NV_ASSERT(io_offset == io_size,
502                            ("zero-length record returned from "
503                             "bhnd_nvram_btxt_seek_next()"));
504                         break;
505                 }
506
507                 /* Fetch a pointer to the line start */
508                 error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp,
509                     env_len, NULL);
510                 if (error)
511                         return (error);
512
513                 /* Parse the key=value string */
514                 error = bhnd_nvram_parse_env(envp, env_len, '=', &name,
515                     &name_len, &value, &value_len);
516                 if (error) {
517                         return (error);
518                 }
519
520                 /* Insert a '\0' character, replacing the '=' delimiter and
521                  * allowing us to vend references directly to the variable
522                  * name */
523                 error = bhnd_nvram_io_write(btxt->data, io_offset+name_len,
524                     &(char){'\0'}, 1);
525                 if (error)
526                         return (error);
527
528                 /* Add to variable count */
529                 btxt->count++;
530
531                 /* Advance past EOL */
532                 io_offset += line_len;
533         }
534
535         return (0);
536 }
537
538 static int
539 bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
540 {
541         struct bhnd_nvram_btxt  *btxt;
542         int                      error;
543
544         /* Allocate and initialize the BTXT data instance */
545         btxt = (struct bhnd_nvram_btxt *)nv;
546
547         /* Parse the BTXT input data and initialize our backing
548          * data representation */
549         if ((error = bhnd_nvram_btxt_init(btxt, io))) {
550                 bhnd_nvram_btxt_free(nv);
551                 return (error);
552         }
553
554         return (0);
555 }
556
557 static void
558 bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv)
559 {
560         struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
561         if (btxt->data != NULL)
562                 bhnd_nvram_io_free(btxt->data);
563 }
564
565 size_t
566 bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv)
567 {
568         struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
569         return (btxt->count);
570 }
571
572 static bhnd_nvram_plist *
573 bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)
574 {
575         return (NULL);
576 }
577
578 static uint32_t
579 bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)
580 {
581         return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
582 }
583
584 static void *
585 bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)
586 {
587         return (bhnd_nvram_data_generic_find(nv, name));
588 }
589
590 static const char *
591 bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep)
592 {
593         struct bhnd_nvram_btxt  *btxt;
594         const void              *nptr;
595         size_t                   io_offset, io_size;
596         int                      error;
597
598         btxt = (struct bhnd_nvram_btxt *)nv;
599
600         io_size = bhnd_nvram_io_getsize(btxt->data);
601
602         if (*cookiep == NULL) {
603                 /* Start search at initial file offset */
604                 io_offset = 0x0;
605         } else {
606                 /* Start search after the current entry */
607                 io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep);
608
609                 /* Scan past the current entry by finding the next newline */
610                 error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset);
611                 if (error) {
612                         BHND_NV_LOG("unexpected error in seek_eol(): %d\n",
613                             error);
614                         return (NULL);
615                 }
616         }
617
618         /* Already at EOF? */
619         if (io_offset == io_size)
620                 return (NULL);
621
622         /* Seek to the first valid entry, or EOF */
623         if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) {
624                 BHND_NV_LOG("unexpected error in seek_next(): %d\n", error);
625                 return (NULL);
626         }
627
628         /* Hit EOF? */
629         if (io_offset == io_size)
630                 return (NULL);
631
632         /* Provide the new cookie for this offset */
633         *cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset);
634
635         /* Fetch the name pointer; it must be at least 1 byte long */
636         error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL);
637         if (error) {
638                 BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
639                 return (NULL);
640         }
641
642         /* Return the name pointer */
643         return (nptr);
644 }
645
646 static int
647 bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
648     void *cookiep2)
649 {
650         if (cookiep1 < cookiep2)
651                 return (-1);
652
653         if (cookiep1 > cookiep2)
654                 return (1);
655
656         return (0);
657 }
658
659 static int
660 bhnd_nvram_btxt_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
661     size_t *len, bhnd_nvram_type type)
662 {
663         return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
664 }
665
666 static int
667 bhnd_nvram_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
668     bhnd_nvram_val **value)
669 {
670         return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
671 }
672
673 const void *
674 bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
675     size_t *len, bhnd_nvram_type *type)
676 {
677         struct bhnd_nvram_btxt  *btxt;
678         const void              *eptr;
679         const char              *vptr;
680         size_t                   io_offset, io_size;
681         size_t                   line_len, env_len;
682         int                      error;
683         
684         btxt = (struct bhnd_nvram_btxt *)nv;
685         
686         io_size = bhnd_nvram_io_getsize(btxt->data);
687         io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
688
689         /* At EOF? */
690         if (io_offset == io_size)
691                 return (NULL);
692
693         /* Determine the entry length */
694         error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len,
695             &env_len);
696         if (error) {
697                 BHND_NV_LOG("unexpected error in entry_len(): %d\n", error);
698                 return (NULL);
699         }
700
701         /* Fetch the entry's value pointer and length */
702         error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len,
703             NULL);
704         if (error) {
705                 BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
706                 return (NULL);
707         }
708
709         error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr,
710             len);
711         if (error) {
712                 BHND_NV_LOG("unexpected error in parse_env(): %d\n", error);
713                 return (NULL);
714         }
715
716         /* Type is always CSTR */
717         *type = BHND_NVRAM_TYPE_STRING;
718
719         return (vptr);
720 }
721
722 static const char *
723 bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
724 {
725         struct bhnd_nvram_btxt  *btxt;
726         const void              *ptr;
727         size_t                   io_offset, io_size;
728         int                      error;
729         
730         btxt = (struct bhnd_nvram_btxt *)nv;
731         
732         io_size = bhnd_nvram_io_getsize(btxt->data);
733         io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
734
735         /* At EOF? */
736         if (io_offset == io_size)
737                 BHND_NV_PANIC("invalid cookiep: %p", cookiep);
738
739         /* Variable name is found directly at the given offset; trailing
740          * NUL means we can assume that it's at least 1 byte long */
741         error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL);
742         if (error)
743                 BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error);
744
745         return (ptr);
746 }
747
748 /**
749  * Return a cookiep for the given I/O offset.
750  */
751 static void *
752 bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
753     size_t io_offset)
754 {
755         const void      *ptr;
756         int              error;
757
758         BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data),
759             ("io_offset %zu out-of-range", io_offset));
760         BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
761             ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
762
763         error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL);
764         if (error)
765                 BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
766
767         ptr = (const uint8_t *)ptr + io_offset;
768         return (__DECONST(void *, ptr));
769 }
770
771 /* Convert a cookiep back to an I/O offset */
772 static size_t
773 bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep)
774 {
775         const void      *ptr;
776         intptr_t         offset;
777         size_t           io_size;
778         int              error;
779
780         BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
781
782         io_size = bhnd_nvram_io_getsize(btxt->data);
783         error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
784         if (error)
785                 BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
786
787         offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
788         BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
789         BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
790         BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
791
792         return ((size_t)offset);
793 }
794
795 /* Determine the entry length and env 'key=value' string length of the entry
796  * at @p offset */
797 static int
798 bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset,
799     size_t *line_len, size_t *env_len)
800 {
801         const uint8_t   *baseptr, *p;
802         const void      *rbuf;
803         size_t           nbytes;
804         int              error;
805
806         /* Fetch read buffer */
807         if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes)))
808                 return (error);
809
810         /* Find record termination (EOL, or '#') */
811         p = rbuf;
812         baseptr = rbuf;
813         while ((size_t)(p - baseptr) < nbytes) {
814                 if (*p == '#' || *p == '\n' || *p == '\r')
815                         break;
816
817                 p++;
818         }
819
820         /* Got line length, now trim any trailing whitespace to determine
821          * actual env length */
822         *line_len = p - baseptr;
823         *env_len = *line_len;
824
825         for (size_t i = 0; i < *line_len; i++) {
826                 char c = baseptr[*line_len - i - 1];
827                 if (!bhnd_nv_isspace(c))
828                         break;
829
830                 *env_len -= 1;
831         }
832
833         return (0);
834 }
835
836 /* Seek past the next line ending (\r, \r\n, or \n) */
837 static int
838 bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset)
839 {
840         const uint8_t   *baseptr, *p;
841         const void      *rbuf;
842         size_t           nbytes;
843         int              error;
844
845         /* Fetch read buffer */
846         if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
847                 return (error);
848
849         baseptr = rbuf;
850         p = rbuf;
851         while ((size_t)(p - baseptr) < nbytes) {
852                 char c = *p;
853
854                 /* Advance to next char. The next position may be EOF, in which
855                  * case a read will be invalid */
856                 p++;
857
858                 if (c == '\r') {
859                         /* CR, check for optional LF */
860                         if ((size_t)(p - baseptr) < nbytes) {
861                                 if (*p == '\n')
862                                         p++;
863                         }
864
865                         break;
866                 } else if (c == '\n') {
867                         break;
868                 }
869         }
870
871         /* Hit newline or EOF */
872         *offset += (p - baseptr);
873         return (0);
874 }
875
876 /* Seek to the next valid non-comment line (or EOF) */
877 static int
878 bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset)
879 {
880         const uint8_t   *baseptr, *p;
881         const void      *rbuf;
882         size_t           nbytes;
883         int              error;
884
885         /* Fetch read buffer */
886         if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
887                 return (error);
888
889         /* Skip leading whitespace and comments */
890         baseptr = rbuf;
891         p = rbuf;
892         while ((size_t)(p - baseptr) < nbytes) {
893                 char c = *p;
894
895                 /* Skip whitespace */
896                 if (bhnd_nv_isspace(c)) {
897                         p++;
898                         continue;
899                 }
900
901                 /* Skip entire comment line */
902                 if (c == '#') {
903                         size_t line_off = *offset + (p - baseptr);
904         
905                         if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off)))
906                                 return (error);
907
908                         p = baseptr + (line_off - *offset);
909                         continue;
910                 }
911
912                 /* Non-whitespace, non-comment */
913                 break;
914         }
915
916         *offset += (p - baseptr);
917         return (0);
918 }
919
920 static int
921 bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
922     bhnd_nvram_val *value, bhnd_nvram_val **result)
923 {
924         bhnd_nvram_val  *str;
925         const char      *inp;
926         bhnd_nvram_type  itype;
927         size_t           ilen;
928         int              error;
929
930         /* Name (trimmed of any path prefix) must be valid */
931         if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
932                 return (EINVAL);
933
934         /* Value must be bcm-formatted string */
935         error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
936             value, BHND_NVRAM_VAL_DYNAMIC);
937         if (error)
938                 return (error);
939
940         /* Value string must not contain our record delimiter character ('\n'),
941          * or our comment character ('#') */
942         inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
943         BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value"));
944         for (size_t i = 0; i < ilen; i++) {
945                 switch (inp[i]) {
946                 case '\n':
947                 case '#':
948                         BHND_NV_LOG("invalid character (%#hhx) in value\n",
949                             inp[i]);
950                         bhnd_nvram_val_release(str);
951                         return (EINVAL);
952                 }
953         }
954
955         /* Success. Transfer result ownership to the caller. */
956         *result = str;
957         return (0);
958 }
959
960 static int
961 bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
962 {
963         /* We permit deletion of any variable */
964         return (0);
965 }