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