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
65struct 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
71BHND_NVRAM_DATA_CLASS_DEFN(btxt, "Broadcom Board Text",
72    BHND_NVRAM_DATA_CAP_DEVPATHS, sizeof(struct bhnd_nvram_btxt))
73
74/** Minimal identification header */
75union bhnd_nvram_btxt_ident {
76	uint32_t	bcm_magic;
77	char		btxt[8];
78};
79
80static void	*bhnd_nvram_btxt_offset_to_cookiep(struct bhnd_nvram_btxt *btxt,
81		 size_t io_offset);
82static size_t	 bhnd_nvram_btxt_cookiep_to_offset(struct bhnd_nvram_btxt *btxt,
83		     void *cookiep);
84
85static int	bhnd_nvram_btxt_entry_len(struct bhnd_nvram_io *io,
86		    size_t offset, size_t *line_len, size_t *env_len);
87static int	bhnd_nvram_btxt_seek_next(struct bhnd_nvram_io *io,
88		    size_t *offset);
89static int	bhnd_nvram_btxt_seek_eol(struct bhnd_nvram_io *io,
90		    size_t *offset);
91
92static int
93bhnd_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 */
130typedef 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
139static int
140bhnd_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
345static int
346bhnd_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 */
444static int
445bhnd_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
536static int
537bhnd_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
555static void
556bhnd_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
563size_t
564bhnd_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
570static bhnd_nvram_plist *
571bhnd_nvram_btxt_options(struct bhnd_nvram_data *nv)
572{
573	return (NULL);
574}
575
576static uint32_t
577bhnd_nvram_btxt_caps(struct bhnd_nvram_data *nv)
578{
579	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
580}
581
582static void *
583bhnd_nvram_btxt_find(struct bhnd_nvram_data *nv, const char *name)
584{
585	return (bhnd_nvram_data_generic_find(nv, name));
586}
587
588static const char *
589bhnd_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
644static int
645bhnd_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
657static int
658bhnd_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
664static int
665bhnd_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
671const void *
672bhnd_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
720static const char *
721bhnd_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 */
749static void *
750bhnd_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 */
770static size_t
771bhnd_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 */
795static int
796bhnd_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) */
835static int
836bhnd_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) */
875static int
876bhnd_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
918static int
919bhnd_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
958static int
959bhnd_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