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