xref: /freebsd/sys/dev/bhnd/nvram/bhnd_nvram_data_btxt.c (revision 792bbaba989533a1fc93823df1720c8c4aaf0442)
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 static int
128 bhnd_nvram_btxt_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
129     bhnd_nvram_plist *options, void *outp, size_t *olen)
130 {
131 	bhnd_nvram_prop	*prop;
132 	size_t		 limit, nbytes;
133 	int		 error;
134 
135 	/* Determine output byte limit */
136 	if (outp != NULL)
137 		limit = *olen;
138 	else
139 		limit = 0;
140 
141 	nbytes = 0;
142 
143 	/* Write all properties */
144 	prop = NULL;
145 	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
146 		const char	*name;
147 		char		*p;
148 		size_t		 prop_limit;
149 		size_t		 name_len, value_len;
150 
151 		if (outp == NULL || limit < nbytes) {
152 			p = NULL;
153 			prop_limit = 0;
154 		} else {
155 			p = ((char *)outp) + nbytes;
156 			prop_limit = limit - nbytes;
157 		}
158 
159 		/* Fetch and write 'name=' to output */
160 		name = bhnd_nvram_prop_name(prop);
161 		name_len = strlen(name) + 1;
162 
163 		if (prop_limit > name_len) {
164 			memcpy(p, name, name_len - 1);
165 			p[name_len - 1] = '=';
166 
167 			prop_limit -= name_len;
168 			p += name_len;
169 		} else {
170 			prop_limit = 0;
171 			p = NULL;
172 		}
173 
174 		/* Advance byte count */
175 		if (SIZE_MAX - nbytes < name_len)
176 			return (EFTYPE); /* would overflow size_t */
177 
178 		nbytes += name_len;
179 
180 		/* Write NUL-terminated value to output, rewrite NUL as
181 		 * '\n' record delimiter */
182 		value_len = prop_limit;
183 		error = bhnd_nvram_prop_encode(prop, p, &value_len,
184 		    BHND_NVRAM_TYPE_STRING);
185 		if (p != NULL && error == 0) {
186 			/* Replace trailing '\0' with newline */
187 			BHND_NV_ASSERT(value_len > 0, ("string length missing "
188 			    "minimum required trailing NUL"));
189 
190 			*(p + (value_len - 1)) = '\n';
191 		} else if (error && error != ENOMEM) {
192 			/* If encoding failed for any reason other than ENOMEM
193 			 * (which we'll detect and report after encoding all
194 			 * properties), return immediately */
195 			BHND_NV_LOG("error serializing %s to required type "
196 			    "%s: %d\n", name,
197 			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
198 			    error);
199 			return (error);
200 		}
201 
202 		/* Advance byte count */
203 		if (SIZE_MAX - nbytes < value_len)
204 			return (EFTYPE); /* would overflow size_t */
205 
206 		nbytes += value_len;
207 	}
208 
209 	/* Provide required length */
210 	*olen = nbytes;
211 	if (limit < *olen) {
212 		if (outp == NULL)
213 			return (0);
214 
215 		return (ENOMEM);
216 	}
217 
218 	return (0);
219 }
220 
221 /**
222  * Initialize @p btxt with the provided board text data mapped by @p src.
223  *
224  * @param btxt A newly allocated data instance.
225  */
226 static int
227 bhnd_nvram_btxt_init(struct bhnd_nvram_btxt *btxt, struct bhnd_nvram_io *src)
228 {
229 	const void		*ptr;
230 	const char		*name, *value;
231 	size_t			 name_len, value_len;
232 	size_t			 line_len, env_len;
233 	size_t			 io_offset, io_size, str_size;
234 	int			 error;
235 
236 	BHND_NV_ASSERT(btxt->data == NULL, ("btxt data already allocated"));
237 
238 	if ((btxt->data = bhnd_nvram_iobuf_copy(src)) == NULL)
239 		return (ENOMEM);
240 
241 	io_size = bhnd_nvram_io_getsize(btxt->data);
242 	io_offset = 0;
243 
244 	/* Fetch a pointer mapping the entirity of the board text data */
245 	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
246 	if (error)
247 		return (error);
248 
249 	/* Determine the actual size, minus any terminating NUL. We
250 	 * parse NUL-terminated C strings, but do not include NUL termination
251 	 * in our internal or serialized representations */
252 	str_size = strnlen(ptr, io_size);
253 
254 	/* If the terminating NUL is not found at the end of the buffer,
255 	 * this is BCM-RAW or other NUL-delimited NVRAM format. */
256 	if (str_size < io_size && str_size + 1 < io_size)
257 		return (EINVAL);
258 
259 	/* Adjust buffer size to account for NUL termination (if any) */
260 	io_size = str_size;
261 	if ((error = bhnd_nvram_io_setsize(btxt->data, io_size)))
262 		return (error);
263 
264 	/* Process the buffer */
265 	btxt->count = 0;
266 	while (io_offset < io_size) {
267 		const void	*envp;
268 
269 		/* Seek to the next key=value entry */
270 		if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset)))
271 			return (error);
272 
273 		/* Determine the entry and line length */
274 		error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset,
275 		    &line_len, &env_len);
276 		if (error)
277 			return (error);
278 
279 		/* EOF? */
280 		if (env_len == 0) {
281 			BHND_NV_ASSERT(io_offset == io_size,
282 		           ("zero-length record returned from "
283 			    "bhnd_nvram_btxt_seek_next()"));
284 			break;
285 		}
286 
287 		/* Fetch a pointer to the line start */
288 		error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &envp,
289 		    env_len, NULL);
290 		if (error)
291 			return (error);
292 
293 		/* Parse the key=value string */
294 		error = bhnd_nvram_parse_env(envp, env_len, '=', &name,
295 		    &name_len, &value, &value_len);
296 		if (error) {
297 			return (error);
298 		}
299 
300 		/* Insert a '\0' character, replacing the '=' delimiter and
301 		 * allowing us to vend references directly to the variable
302 		 * name */
303 		error = bhnd_nvram_io_write(btxt->data, io_offset+name_len,
304 		    &(char){'\0'}, 1);
305 		if (error)
306 			return (error);
307 
308 		/* Add to variable count */
309 		btxt->count++;
310 
311 		/* Advance past EOL */
312 		io_offset += line_len;
313 	}
314 
315 	return (0);
316 }
317 
318 static int
319 bhnd_nvram_btxt_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
320 {
321 	struct bhnd_nvram_btxt	*btxt;
322 	int			 error;
323 
324 	/* Allocate and initialize the BTXT data instance */
325 	btxt = (struct bhnd_nvram_btxt *)nv;
326 
327 	/* Parse the BTXT input data and initialize our backing
328 	 * data representation */
329 	if ((error = bhnd_nvram_btxt_init(btxt, io))) {
330 		bhnd_nvram_btxt_free(nv);
331 		return (error);
332 	}
333 
334 	return (0);
335 }
336 
337 static void
338 bhnd_nvram_btxt_free(struct bhnd_nvram_data *nv)
339 {
340 	struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
341 	if (btxt->data != NULL)
342 		bhnd_nvram_io_free(btxt->data);
343 }
344 
345 size_t
346 bhnd_nvram_btxt_count(struct bhnd_nvram_data *nv)
347 {
348 	struct bhnd_nvram_btxt *btxt = (struct bhnd_nvram_btxt *)nv;
349 	return (btxt->count);
350 }
351 
352 static bhnd_nvram_plist *
353 bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)
354 {
355 	return (NULL);
356 }
357 
358 static uint32_t
359 bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)
360 {
361 	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
362 }
363 
364 static void *
365 bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)
366 {
367 	return (bhnd_nvram_data_generic_find(nv, name));
368 }
369 
370 static const char *
371 bhnd_nvram_btxt_next(struct bhnd_nvram_data *nv, void **cookiep)
372 {
373 	struct bhnd_nvram_btxt	*btxt;
374 	const void		*nptr;
375 	size_t			 io_offset, io_size;
376 	int			 error;
377 
378 	btxt = (struct bhnd_nvram_btxt *)nv;
379 
380 	io_size = bhnd_nvram_io_getsize(btxt->data);
381 
382 	if (*cookiep == NULL) {
383 		/* Start search at initial file offset */
384 		io_offset = 0x0;
385 	} else {
386 		/* Start search after the current entry */
387 		io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, *cookiep);
388 
389 		/* Scan past the current entry by finding the next newline */
390 		error = bhnd_nvram_btxt_seek_eol(btxt->data, &io_offset);
391 		if (error) {
392 			BHND_NV_LOG("unexpected error in seek_eol(): %d\n",
393 			    error);
394 			return (NULL);
395 		}
396 	}
397 
398 	/* Already at EOF? */
399 	if (io_offset == io_size)
400 		return (NULL);
401 
402 	/* Seek to the first valid entry, or EOF */
403 	if ((error = bhnd_nvram_btxt_seek_next(btxt->data, &io_offset))) {
404 		BHND_NV_LOG("unexpected error in seek_next(): %d\n", error);
405 		return (NULL);
406 	}
407 
408 	/* Hit EOF? */
409 	if (io_offset == io_size)
410 		return (NULL);
411 
412 	/* Provide the new cookie for this offset */
413 	*cookiep = bhnd_nvram_btxt_offset_to_cookiep(btxt, io_offset);
414 
415 	/* Fetch the name pointer; it must be at least 1 byte long */
416 	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &nptr, 1, NULL);
417 	if (error) {
418 		BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
419 		return (NULL);
420 	}
421 
422 	/* Return the name pointer */
423 	return (nptr);
424 }
425 
426 static int
427 bhnd_nvram_btxt_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
428     void *cookiep2)
429 {
430 	if (cookiep1 < cookiep2)
431 		return (-1);
432 
433 	if (cookiep1 > cookiep2)
434 		return (1);
435 
436 	return (0);
437 }
438 
439 static int
440 bhnd_nvram_btxt_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
441     size_t *len, bhnd_nvram_type type)
442 {
443 	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
444 }
445 
446 static int
447 bhnd_nvram_btxt_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
448     bhnd_nvram_val **value)
449 {
450 	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
451 }
452 
453 const void *
454 bhnd_nvram_btxt_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
455     size_t *len, bhnd_nvram_type *type)
456 {
457 	struct bhnd_nvram_btxt	*btxt;
458 	const void		*eptr;
459 	const char		*vptr;
460 	size_t			 io_offset, io_size;
461 	size_t			 line_len, env_len;
462 	int			 error;
463 
464 	btxt = (struct bhnd_nvram_btxt *)nv;
465 
466 	io_size = bhnd_nvram_io_getsize(btxt->data);
467 	io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
468 
469 	/* At EOF? */
470 	if (io_offset == io_size)
471 		return (NULL);
472 
473 	/* Determine the entry length */
474 	error = bhnd_nvram_btxt_entry_len(btxt->data, io_offset, &line_len,
475 	    &env_len);
476 	if (error) {
477 		BHND_NV_LOG("unexpected error in entry_len(): %d\n", error);
478 		return (NULL);
479 	}
480 
481 	/* Fetch the entry's value pointer and length */
482 	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &eptr, env_len,
483 	    NULL);
484 	if (error) {
485 		BHND_NV_LOG("unexpected error in read_ptr(): %d\n", error);
486 		return (NULL);
487 	}
488 
489 	error = bhnd_nvram_parse_env(eptr, env_len, '\0', NULL, NULL, &vptr,
490 	    len);
491 	if (error) {
492 		BHND_NV_LOG("unexpected error in parse_env(): %d\n", error);
493 		return (NULL);
494 	}
495 
496 	/* Type is always CSTR */
497 	*type = BHND_NVRAM_TYPE_STRING;
498 
499 	return (vptr);
500 }
501 
502 static const char *
503 bhnd_nvram_btxt_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
504 {
505 	struct bhnd_nvram_btxt	*btxt;
506 	const void		*ptr;
507 	size_t			 io_offset, io_size;
508 	int			 error;
509 
510 	btxt = (struct bhnd_nvram_btxt *)nv;
511 
512 	io_size = bhnd_nvram_io_getsize(btxt->data);
513 	io_offset = bhnd_nvram_btxt_cookiep_to_offset(btxt, cookiep);
514 
515 	/* At EOF? */
516 	if (io_offset == io_size)
517 		BHND_NV_PANIC("invalid cookiep: %p", cookiep);
518 
519 	/* Variable name is found directly at the given offset; trailing
520 	 * NUL means we can assume that it's at least 1 byte long */
521 	error = bhnd_nvram_io_read_ptr(btxt->data, io_offset, &ptr, 1, NULL);
522 	if (error)
523 		BHND_NV_PANIC("unexpected error in read_ptr(): %d\n", error);
524 
525 	return (ptr);
526 }
527 
528 /**
529  * Return a cookiep for the given I/O offset.
530  */
531 static void *
532 bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
533     size_t io_offset)
534 {
535 	const void	*ptr;
536 	int		 error;
537 
538 	BHND_NV_ASSERT(io_offset < bhnd_nvram_io_getsize(btxt->data),
539 	    ("io_offset %zu out-of-range", io_offset));
540 	BHND_NV_ASSERT(io_offset < UINTPTR_MAX,
541 	    ("io_offset %#zx exceeds UINTPTR_MAX", io_offset));
542 
543 	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_offset, NULL);
544 	if (error)
545 		BHND_NV_PANIC("error mapping offset %zu: %d", io_offset, error);
546 
547 	ptr = (const uint8_t *)ptr + io_offset;
548 	return (__DECONST(void *, ptr));
549 }
550 
551 /* Convert a cookiep back to an I/O offset */
552 static size_t
553 bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt, void *cookiep)
554 {
555 	const void	*ptr;
556 	intptr_t	 offset;
557 	size_t		 io_size;
558 	int		 error;
559 
560 	BHND_NV_ASSERT(cookiep != NULL, ("null cookiep"));
561 
562 	io_size = bhnd_nvram_io_getsize(btxt->data);
563 	error = bhnd_nvram_io_read_ptr(btxt->data, 0x0, &ptr, io_size, NULL);
564 	if (error)
565 		BHND_NV_PANIC("error mapping offset %zu: %d", io_size, error);
566 
567 	offset = (const uint8_t *)cookiep - (const uint8_t *)ptr;
568 	BHND_NV_ASSERT(offset >= 0, ("invalid cookiep"));
569 	BHND_NV_ASSERT((uintptr_t)offset < SIZE_MAX, ("cookiep > SIZE_MAX)"));
570 	BHND_NV_ASSERT((uintptr_t)offset <= io_size, ("cookiep > io_size)"));
571 
572 	return ((size_t)offset);
573 }
574 
575 /* Determine the entry length and env 'key=value' string length of the entry
576  * at @p offset */
577 static int
578 bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io, size_t offset,
579     size_t *line_len, size_t *env_len)
580 {
581 	const uint8_t	*baseptr, *p;
582 	const void	*rbuf;
583 	size_t		 nbytes;
584 	int		 error;
585 
586 	/* Fetch read buffer */
587 	if ((error = bhnd_nvram_io_read_ptr(io, offset, &rbuf, 0, &nbytes)))
588 		return (error);
589 
590 	/* Find record termination (EOL, or '#') */
591 	p = rbuf;
592 	baseptr = rbuf;
593 	while ((size_t)(p - baseptr) < nbytes) {
594 		if (*p == '#' || *p == '\n' || *p == '\r')
595 			break;
596 
597 		p++;
598 	}
599 
600 	/* Got line length, now trim any trailing whitespace to determine
601 	 * actual env length */
602 	*line_len = p - baseptr;
603 	*env_len = *line_len;
604 
605 	for (size_t i = 0; i < *line_len; i++) {
606 		char c = baseptr[*line_len - i - 1];
607 		if (!bhnd_nv_isspace(c))
608 			break;
609 
610 		*env_len -= 1;
611 	}
612 
613 	return (0);
614 }
615 
616 /* Seek past the next line ending (\r, \r\n, or \n) */
617 static int
618 bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io, size_t *offset)
619 {
620 	const uint8_t	*baseptr, *p;
621 	const void	*rbuf;
622 	size_t		 nbytes;
623 	int		 error;
624 
625 	/* Fetch read buffer */
626 	if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
627 		return (error);
628 
629 	baseptr = rbuf;
630 	p = rbuf;
631 	while ((size_t)(p - baseptr) < nbytes) {
632 		char c = *p;
633 
634 		/* Advance to next char. The next position may be EOF, in which
635 		 * case a read will be invalid */
636 		p++;
637 
638 		if (c == '\r') {
639 			/* CR, check for optional LF */
640 			if ((size_t)(p - baseptr) < nbytes) {
641 				if (*p == '\n')
642 					p++;
643 			}
644 
645 			break;
646 		} else if (c == '\n') {
647 			break;
648 		}
649 	}
650 
651 	/* Hit newline or EOF */
652 	*offset += (p - baseptr);
653 	return (0);
654 }
655 
656 /* Seek to the next valid non-comment line (or EOF) */
657 static int
658 bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io, size_t *offset)
659 {
660 	const uint8_t	*baseptr, *p;
661 	const void	*rbuf;
662 	size_t		 nbytes;
663 	int		 error;
664 
665 	/* Fetch read buffer */
666 	if ((error = bhnd_nvram_io_read_ptr(io, *offset, &rbuf, 0, &nbytes)))
667 		return (error);
668 
669 	/* Skip leading whitespace and comments */
670 	baseptr = rbuf;
671 	p = rbuf;
672 	while ((size_t)(p - baseptr) < nbytes) {
673 		char c = *p;
674 
675 		/* Skip whitespace */
676 		if (bhnd_nv_isspace(c)) {
677 			p++;
678 			continue;
679 		}
680 
681 		/* Skip entire comment line */
682 		if (c == '#') {
683 			size_t line_off = *offset + (p - baseptr);
684 
685 			if ((error = bhnd_nvram_btxt_seek_eol(io, &line_off)))
686 				return (error);
687 
688 			p = baseptr + (line_off - *offset);
689 			continue;
690 		}
691 
692 		/* Non-whitespace, non-comment */
693 		break;
694 	}
695 
696 	*offset += (p - baseptr);
697 	return (0);
698 }
699 
700 static int
701 bhnd_nvram_btxt_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
702     bhnd_nvram_val *value, bhnd_nvram_val **result)
703 {
704 	bhnd_nvram_val	*str;
705 	const char	*inp;
706 	bhnd_nvram_type	 itype;
707 	size_t		 ilen;
708 	int		 error;
709 
710 	/* Name (trimmed of any path prefix) must be valid */
711 	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
712 		return (EINVAL);
713 
714 	/* Value must be bcm-formatted string */
715 	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
716 	    value, BHND_NVRAM_VAL_DYNAMIC);
717 	if (error)
718 		return (error);
719 
720 	/* Value string must not contain our record delimiter character ('\n'),
721 	 * or our comment character ('#') */
722 	inp = bhnd_nvram_val_bytes(str, &ilen, &itype);
723 	BHND_NV_ASSERT(itype == BHND_NVRAM_TYPE_STRING, ("non-string value"));
724 	for (size_t i = 0; i < ilen; i++) {
725 		switch (inp[i]) {
726 		case '\n':
727 		case '#':
728 			BHND_NV_LOG("invalid character (%#hhx) in value\n",
729 			    inp[i]);
730 			bhnd_nvram_val_release(str);
731 			return (EINVAL);
732 		}
733 	}
734 
735 	/* Success. Transfer result ownership to the caller. */
736 	*result = str;
737 	return (0);
738 }
739 
740 static int
741 bhnd_nvram_btxt_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
742 {
743 	/* We permit deletion of any variable */
744 	return (0);
745 }
746