xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c (revision d3d381b2b194b4d24853e92eecef55f262688d1a)
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 }
966