1/*-
2 * Copyright (c) 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/param.h>
34#include <sys/endian.h>
35
36#ifdef _KERNEL
37
38#include <sys/bus.h>
39#include <sys/ctype.h>
40#include <sys/malloc.h>
41#include <sys/systm.h>
42
43#else /* !_KERNEL */
44
45#include <ctype.h>
46#include <stdint.h>
47#include <stdio.h>
48#include <stdlib.h>
49#include <string.h>
50
51#endif /* _KERNEL */
52
53#include "bhnd_nvram_private.h"
54
55#include "bhnd_nvram_datavar.h"
56
57#include "bhnd_nvram_data_bcmreg.h"
58#include "bhnd_nvram_data_bcmvar.h"
59
60/*
61 * Broadcom NVRAM data class.
62 *
63 * The Broadcom NVRAM NUL-delimited ASCII format is used by most
64 * Broadcom SoCs.
65 *
66 * The NVRAM data is encoded as a standard header, followed by series of
67 * NUL-terminated 'key=value' strings; the end of the stream is denoted
68 * by a single extra NUL character.
69 */
70
71struct bhnd_nvram_bcm;
72
73static struct bhnd_nvram_bcm_hvar	*bhnd_nvram_bcm_gethdrvar(
74					     struct bhnd_nvram_bcm *bcm,
75					     const char *name);
76static struct bhnd_nvram_bcm_hvar	*bhnd_nvram_bcm_to_hdrvar(
77					     struct bhnd_nvram_bcm *bcm,
78					     void *cookiep);
79static size_t				 bhnd_nvram_bcm_hdrvar_index(
80					     struct bhnd_nvram_bcm *bcm,
81					     struct bhnd_nvram_bcm_hvar *hvar);
82/*
83 * Set of BCM NVRAM header values that are required to be mirrored in the
84 * NVRAM data itself.
85 *
86 * If they're not included in the parsed NVRAM data, we need to vend the
87 * header-parsed values with their appropriate keys, and add them in any
88 * updates to the NVRAM data.
89 *
90 * If they're modified in NVRAM, we need to sync the changes with the
91 * the NVRAM header values.
92 */
93static const struct bhnd_nvram_bcm_hvar bhnd_nvram_bcm_hvars[] = {
94	{
95		.name	= BCM_NVRAM_CFG0_SDRAM_INIT_VAR,
96		.type	= BHND_NVRAM_TYPE_UINT16,
97		.len	= sizeof(uint16_t),
98		.nelem	= 1,
99	},
100	{
101		.name	= BCM_NVRAM_CFG1_SDRAM_CFG_VAR,
102		.type	= BHND_NVRAM_TYPE_UINT16,
103		.len	= sizeof(uint16_t),
104		.nelem	= 1,
105	},
106	{
107		.name	= BCM_NVRAM_CFG1_SDRAM_REFRESH_VAR,
108		.type	= BHND_NVRAM_TYPE_UINT16,
109		.len	= sizeof(uint16_t),
110		.nelem	= 1,
111	},
112	{
113		.name	= BCM_NVRAM_SDRAM_NCDL_VAR,
114		.type	= BHND_NVRAM_TYPE_UINT32,
115		.len	= sizeof(uint32_t),
116		.nelem	= 1,
117	},
118};
119
120/** BCM NVRAM data class instance */
121struct bhnd_nvram_bcm {
122	struct bhnd_nvram_data		 nv;	/**< common instance state */
123	struct bhnd_nvram_io		*data;	/**< backing buffer */
124	bhnd_nvram_plist		*opts;	/**< serialization options */
125
126	/** BCM header values */
127	struct bhnd_nvram_bcm_hvar	 hvars[nitems(bhnd_nvram_bcm_hvars)];
128
129	size_t				 count;	/**< total variable count */
130};
131
132BHND_NVRAM_DATA_CLASS_DEFN(bcm, "Broadcom", BHND_NVRAM_DATA_CAP_DEVPATHS,
133    sizeof(struct bhnd_nvram_bcm))
134
135static int
136bhnd_nvram_bcm_probe(struct bhnd_nvram_io *io)
137{
138	struct bhnd_nvram_bcmhdr	hdr;
139	int				error;
140
141	if ((error = bhnd_nvram_io_read(io, 0x0, &hdr, sizeof(hdr))))
142		return (error);
143
144	if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
145		return (ENXIO);
146
147	if (le32toh(hdr.size) > bhnd_nvram_io_getsize(io))
148		return (ENXIO);
149
150	return (BHND_NVRAM_DATA_PROBE_DEFAULT);
151}
152
153/**
154 * Parser states for bhnd_nvram_bcm_getvar_direct_common().
155 */
156typedef enum {
157	BCM_PARSE_KEY_START,
158	BCM_PARSE_KEY_CONT,
159	BCM_PARSE_KEY,
160	BCM_PARSE_NEXT_KEY,
161	BCM_PARSE_VALUE_START,
162	BCM_PARSE_VALUE
163} bcm_parse_state;
164
165static int
166bhnd_nvram_bcm_getvar_direct(struct bhnd_nvram_io *io, const char *name,
167    void *outp, size_t *olen, bhnd_nvram_type otype)
168{
169	return (bhnd_nvram_bcm_getvar_direct_common(io, name, outp, olen, otype,
170	    true));
171}
172
173/**
174 * Common BCM/BCMRAW implementation of bhnd_nvram_getvar_direct().
175 */
176int
177bhnd_nvram_bcm_getvar_direct_common(struct bhnd_nvram_io *io, const char *name,
178    void *outp, size_t *olen, bhnd_nvram_type otype, bool have_header)
179{
180	struct bhnd_nvram_bcmhdr	 hdr;
181	char				 buf[512];
182	bcm_parse_state			 pstate;
183	size_t				 limit, offset;
184	size_t				 buflen, bufpos;
185	size_t				 namelen, namepos;
186	size_t				 vlen;
187	int				 error;
188
189	limit = bhnd_nvram_io_getsize(io);
190	offset = 0;
191
192	/* Fetch and validate the header */
193	if (have_header) {
194		if ((error = bhnd_nvram_io_read(io, offset, &hdr, sizeof(hdr))))
195			return (error);
196
197		if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
198			return (ENXIO);
199
200		offset += sizeof(hdr);
201		limit = bhnd_nv_ummin(le32toh(hdr.size), limit);
202	}
203
204	/* Loop our parser until we find the requested variable, or hit EOF */
205	pstate = BCM_PARSE_KEY_START;
206	buflen = 0;
207	bufpos = 0;
208	namelen = strlen(name);
209	namepos = 0;
210	vlen = 0;
211
212	while ((offset - bufpos) < limit) {
213		BHND_NV_ASSERT(bufpos <= buflen,
214		    ("buf position invalid (%zu > %zu)", bufpos, buflen));
215		BHND_NV_ASSERT(buflen <= sizeof(buf),
216		    ("buf length invalid (%zu > %zu", buflen, sizeof(buf)));
217
218		/* Repopulate our parse buffer? */
219		if (buflen - bufpos == 0) {
220			BHND_NV_ASSERT(offset < limit, ("offset overrun"));
221
222			buflen = bhnd_nv_ummin(sizeof(buf), limit - offset);
223			bufpos = 0;
224
225			error = bhnd_nvram_io_read(io, offset, buf, buflen);
226			if (error)
227				return (error);
228
229			offset += buflen;
230		}
231
232		switch (pstate) {
233		case BCM_PARSE_KEY_START:
234			BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
235
236			/* An extra '\0' denotes NVRAM EOF */
237			if (buf[bufpos] == '\0')
238				return (ENOENT);
239
240			/* Reset name matching position */
241			namepos = 0;
242
243			/* Start name matching */
244			pstate = BCM_PARSE_KEY_CONT;
245			break;
246
247		case BCM_PARSE_KEY_CONT: {
248			size_t navail, nleft;
249
250			nleft = namelen - namepos;
251			navail = bhnd_nv_ummin(buflen - bufpos, nleft);
252
253			if (strncmp(name+namepos, buf+bufpos, navail) == 0) {
254				/* Matched */
255				namepos += navail;
256				bufpos += navail;
257
258				/* If we've matched the full variable name,
259				 * look for its trailing delimiter */
260				if (namepos == namelen)
261					pstate = BCM_PARSE_KEY;
262			} else {
263				/* No match; advance to next entry and restart
264				 * name matching */
265				pstate = BCM_PARSE_NEXT_KEY;
266			}
267
268			break;
269		}
270
271		case BCM_PARSE_KEY:
272			BHND_NV_ASSERT(buflen - bufpos > 0, ("empty buffer!"));
273
274			if (buf[bufpos] == '=') {
275				/* Key fully matched; advance past '=' and
276				 * parse the value */
277				bufpos++;
278				pstate = BCM_PARSE_VALUE_START;
279			} else {
280				/* No match; advance to next entry and restart
281				 * name matching */
282				pstate = BCM_PARSE_NEXT_KEY;
283			}
284
285			break;
286
287		case BCM_PARSE_NEXT_KEY: {
288			const char *p;
289
290			/* Scan for a '\0' terminator */
291			p = memchr(buf+bufpos, '\0', buflen - bufpos);
292
293			if (p != NULL) {
294				/* Found entry terminator; restart name
295				 * matching at next entry */
296				pstate = BCM_PARSE_KEY_START;
297				bufpos = (p - buf) + 1 /* skip '\0' */;
298			} else {
299				/* Consumed full buffer looking for '\0';
300				 * force repopulation of the buffer and
301				 * retry */
302				bufpos = buflen;
303			}
304
305			break;
306		}
307
308		case BCM_PARSE_VALUE_START: {
309			const char *p;
310
311			/* Scan for a '\0' terminator */
312			p = memchr(buf+bufpos, '\0', buflen - bufpos);
313
314			if (p != NULL) {
315				/* Found entry terminator; parse the value */
316				vlen = p - &buf[bufpos];
317				pstate = BCM_PARSE_VALUE;
318
319			} else if (p == NULL && offset == limit) {
320				/* Hit EOF without a terminating '\0';
321				 * treat the entry as implicitly terminated */
322				vlen = buflen - bufpos;
323				pstate = BCM_PARSE_VALUE;
324
325			} else if (p == NULL && bufpos > 0) {
326				size_t	nread;
327
328				/* Move existing value data to start of
329				 * buffer */
330				memmove(buf, buf+bufpos, buflen - bufpos);
331				buflen = bufpos;
332				bufpos = 0;
333
334				/* Populate full buffer to allow retry of
335				 * value parsing */
336				nread = bhnd_nv_ummin(sizeof(buf) - buflen,
337				    limit - offset);
338
339				error = bhnd_nvram_io_read(io, offset,
340				    buf+buflen, nread);
341				if (error)
342					return (error);
343
344				offset += nread;
345				buflen += nread;
346			} else {
347				/* Value exceeds our buffer capacity */
348				BHND_NV_LOG("cannot parse value for '%s' "
349				    "(exceeds %zu byte limit)\n", name,
350				    sizeof(buf));
351
352				return (ENXIO);
353			}
354
355			break;
356		}
357
358		case BCM_PARSE_VALUE:
359			BHND_NV_ASSERT(vlen <= buflen, ("value buf overrun"));
360
361			return (bhnd_nvram_value_coerce(buf+bufpos, vlen,
362			    BHND_NVRAM_TYPE_STRING, outp, olen, otype));
363		}
364	}
365
366	/* Variable not found */
367	return (ENOENT);
368}
369
370static int
371bhnd_nvram_bcm_serialize(bhnd_nvram_data_class *cls, bhnd_nvram_plist *props,
372    bhnd_nvram_plist *options, void *outp, size_t *olen)
373{
374	struct bhnd_nvram_bcmhdr	 hdr;
375	bhnd_nvram_prop			*prop;
376	size_t				 limit, nbytes;
377	uint32_t			 sdram_ncdl;
378	uint16_t			 sdram_init, sdram_cfg, sdram_refresh;
379	uint8_t				 bcm_ver, crc8;
380	int				 error;
381
382	/* Determine output byte limit */
383	if (outp != NULL)
384		limit = *olen;
385	else
386		limit = 0;
387
388	/* Fetch required header variables */
389#define	PROPS_GET_HDRVAR(_name, _dest, _type)	do {			\
390		const char *name = BCM_NVRAM_ ## _name ## _VAR;	\
391		if (!bhnd_nvram_plist_contains(props, name)) {		\
392			BHND_NV_LOG("missing required property: %s\n",	\
393			    name);					\
394			return (EFTYPE);				\
395		}							\
396									\
397		error = bhnd_nvram_plist_get_encoded(props, name,	\
398		    (_dest), sizeof(*(_dest)),				\
399		    BHND_NVRAM_TYPE_ ##_type);				\
400		if (error) {						\
401			BHND_NV_LOG("error reading required header "	\
402			    "%s property: %d\n", name, error);		\
403			return (EFTYPE);				\
404		}							\
405} while (0)
406
407	PROPS_GET_HDRVAR(SDRAM_NCDL,		&sdram_ncdl,	UINT32);
408	PROPS_GET_HDRVAR(CFG0_SDRAM_INIT,	&sdram_init,	UINT16);
409	PROPS_GET_HDRVAR(CFG1_SDRAM_CFG,	&sdram_cfg,	UINT16);
410	PROPS_GET_HDRVAR(CFG1_SDRAM_REFRESH,	&sdram_refresh,	UINT16);
411
412#undef	PROPS_GET_HDRVAR
413
414	/* Fetch BCM nvram version from options */
415	if (options != NULL &&
416	    bhnd_nvram_plist_contains(options, BCM_NVRAM_ENCODE_OPT_VERSION))
417	{
418		error = bhnd_nvram_plist_get_uint8(options,
419		    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver);
420		if (error) {
421			BHND_NV_LOG("error reading %s uint8 option value: %d\n",
422			    BCM_NVRAM_ENCODE_OPT_VERSION, error);
423			return (EINVAL);
424		}
425	} else {
426		bcm_ver = BCM_NVRAM_CFG0_VER_DEFAULT;
427	}
428
429	/* Construct our header */
430	hdr = (struct bhnd_nvram_bcmhdr) {
431		.magic = htole32(BCM_NVRAM_MAGIC),
432		.size = 0,
433		.cfg0 = 0,
434		.cfg1 = 0,
435		.sdram_ncdl = htole32(sdram_ncdl)
436	};
437
438	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, 0x0);
439	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER, bcm_ver);
440	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_SDRAM_INIT,
441	    htole16(sdram_init));
442
443	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_CFG,
444	    htole16(sdram_cfg));
445	hdr.cfg1 = BCM_NVRAM_SET_BITS(hdr.cfg1, BCM_NVRAM_CFG1_SDRAM_REFRESH,
446	    htole16(sdram_refresh));
447
448	/* Write the header */
449	nbytes = sizeof(hdr);
450	if (limit >= nbytes)
451		memcpy(outp, &hdr, sizeof(hdr));
452
453	/* Write all properties */
454	prop = NULL;
455	while ((prop = bhnd_nvram_plist_next(props, prop)) != NULL) {
456		const char	*name;
457		char		*p;
458		size_t		 prop_limit;
459		size_t		 name_len, value_len;
460
461		if (outp == NULL || limit < nbytes) {
462			p = NULL;
463			prop_limit = 0;
464		} else {
465			p = ((char *)outp) + nbytes;
466			prop_limit = limit - nbytes;
467		}
468
469		/* Fetch and write name + '=' to output */
470		name = bhnd_nvram_prop_name(prop);
471		name_len = strlen(name) + 1;
472
473		if (prop_limit > name_len) {
474			memcpy(p, name, name_len - 1);
475			p[name_len - 1] = '=';
476
477			prop_limit -= name_len;
478			p += name_len;
479		} else {
480			prop_limit = 0;
481			p = NULL;
482		}
483
484		/* Advance byte count */
485		if (SIZE_MAX - nbytes < name_len)
486			return (EFTYPE); /* would overflow size_t */
487
488		nbytes += name_len;
489
490		/* Attempt to write NUL-terminated value to output */
491		value_len = prop_limit;
492		error = bhnd_nvram_prop_encode(prop, p, &value_len,
493		    BHND_NVRAM_TYPE_STRING);
494
495		/* If encoding failed for any reason other than ENOMEM (which
496		 * we'll detect and report after encoding all properties),
497		 * return immediately */
498		if (error && error != ENOMEM) {
499			BHND_NV_LOG("error serializing %s to required type "
500			    "%s: %d\n", name,
501			    bhnd_nvram_type_name(BHND_NVRAM_TYPE_STRING),
502			    error);
503			return (error);
504		}
505
506		/* Advance byte count */
507		if (SIZE_MAX - nbytes < value_len)
508			return (EFTYPE); /* would overflow size_t */
509
510		nbytes += value_len;
511	}
512
513	/* Write terminating '\0' */
514	if (limit > nbytes)
515		*((char *)outp + nbytes) = '\0';
516
517	if (nbytes == SIZE_MAX)
518		return (EFTYPE); /* would overflow size_t */
519	else
520		nbytes++;
521
522	/* Update header length; this must fit within the header's 32-bit size
523	 * field */
524	if (nbytes <= UINT32_MAX) {
525		hdr.size = (uint32_t)nbytes;
526	} else {
527		BHND_NV_LOG("size %zu exceeds maximum supported size of %u "
528		    "bytes\n", nbytes, UINT32_MAX);
529		return (EFTYPE);
530	}
531
532	/* Provide required length */
533	*olen = nbytes;
534	if (limit < *olen) {
535		if (outp == NULL)
536			return (0);
537
538		return (ENOMEM);
539	}
540
541	/* Calculate the CRC value */
542	BHND_NV_ASSERT(nbytes >= BCM_NVRAM_CRC_SKIP, ("invalid output size"));
543	crc8 = bhnd_nvram_crc8((uint8_t *)outp + BCM_NVRAM_CRC_SKIP,
544	    nbytes - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
545
546	/* Update CRC and write the finalized header */
547	BHND_NV_ASSERT(nbytes >= sizeof(hdr), ("invalid output size"));
548	hdr.cfg0 = BCM_NVRAM_SET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC, crc8);
549	memcpy(outp, &hdr, sizeof(hdr));
550
551	return (0);
552}
553
554/**
555 * Initialize @p bcm with the provided NVRAM data mapped by @p src.
556 *
557 * @param bcm A newly allocated data instance.
558 */
559static int
560bhnd_nvram_bcm_init(struct bhnd_nvram_bcm *bcm, struct bhnd_nvram_io *src)
561{
562	struct bhnd_nvram_bcmhdr	 hdr;
563	uint8_t				*p;
564	void				*ptr;
565	size_t				 io_offset, io_size;
566	uint8_t				 crc, valid, bcm_ver;
567	int				 error;
568
569	if ((error = bhnd_nvram_io_read(src, 0x0, &hdr, sizeof(hdr))))
570		return (error);
571
572	if (le32toh(hdr.magic) != BCM_NVRAM_MAGIC)
573		return (ENXIO);
574
575	/* Fetch the actual NVRAM image size */
576	io_size = le32toh(hdr.size);
577	if (io_size < sizeof(hdr)) {
578		/* The header size must include the header itself */
579		BHND_NV_LOG("corrupt header size: %zu\n", io_size);
580		return (EINVAL);
581	}
582
583	if (io_size > bhnd_nvram_io_getsize(src)) {
584		BHND_NV_LOG("header size %zu exceeds input size %zu\n",
585		    io_size, bhnd_nvram_io_getsize(src));
586		return (EINVAL);
587	}
588
589	/* Allocate a buffer large enough to hold the NVRAM image, and
590	 * an extra EOF-signaling NUL (on the chance it's missing from the
591	 * source data) */
592	if (io_size == SIZE_MAX)
593		return (ENOMEM);
594
595	bcm->data = bhnd_nvram_iobuf_empty(io_size, io_size + 1);
596	if (bcm->data == NULL)
597		return (ENOMEM);
598
599	/* Fetch a pointer into our backing buffer and copy in the
600	 * NVRAM image. */
601	error = bhnd_nvram_io_write_ptr(bcm->data, 0x0, &ptr, io_size, NULL);
602	if (error)
603		return (error);
604
605	p = ptr;
606	if ((error = bhnd_nvram_io_read(src, 0x0, p, io_size)))
607		return (error);
608
609	/* Verify the CRC */
610	valid = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_CRC);
611	crc = bhnd_nvram_crc8(p + BCM_NVRAM_CRC_SKIP,
612	    io_size - BCM_NVRAM_CRC_SKIP, BHND_NVRAM_CRC8_INITIAL);
613
614	if (crc != valid) {
615		BHND_NV_LOG("warning: NVRAM CRC error (crc=%#hhx, "
616		    "expected=%hhx)\n", crc, valid);
617	}
618
619	/* Populate header variable definitions */
620#define	BCM_READ_HDR_VAR(_name, _dest, _swap) do {		\
621	struct bhnd_nvram_bcm_hvar *data;				\
622	data = bhnd_nvram_bcm_gethdrvar(bcm, _name ##_VAR);		\
623	BHND_NV_ASSERT(data != NULL,						\
624	    ("no such header variable: " __STRING(_name)));		\
625									\
626									\
627	data->value. _dest = _swap(BCM_NVRAM_GET_BITS(			\
628	    hdr. _name ## _FIELD, _name));				\
629} while(0)
630
631	BCM_READ_HDR_VAR(BCM_NVRAM_CFG0_SDRAM_INIT,	u16, le16toh);
632	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_CFG,	u16, le16toh);
633	BCM_READ_HDR_VAR(BCM_NVRAM_CFG1_SDRAM_REFRESH,	u16, le16toh);
634	BCM_READ_HDR_VAR(BCM_NVRAM_SDRAM_NCDL,		u32, le32toh);
635
636	_Static_assert(nitems(bcm->hvars) == 4, "missing initialization for"
637	    "NVRAM header variable(s)");
638
639#undef BCM_READ_HDR_VAR
640
641	/* Process the buffer */
642	bcm->count = 0;
643	io_offset = sizeof(hdr);
644	while (io_offset < io_size) {
645		char		*envp;
646		const char	*name, *value;
647		size_t		 envp_len;
648		size_t		 name_len, value_len;
649
650		/* Parse the key=value string */
651		envp = (char *) (p + io_offset);
652		envp_len = strnlen(envp, io_size - io_offset);
653		error = bhnd_nvram_parse_env(envp, envp_len, '=', &name,
654					     &name_len, &value, &value_len);
655		if (error) {
656			BHND_NV_LOG("error parsing envp at offset %#zx: %d\n",
657			    io_offset, error);
658			return (error);
659		}
660
661		/* Insert a '\0' character, replacing the '=' delimiter and
662		 * allowing us to vend references directly to the variable
663		 * name */
664		*(envp + name_len) = '\0';
665
666		/* Record any NVRAM variables that mirror our header variables.
667		 * This is a brute-force search -- for the amount of data we're
668		 * operating on, it shouldn't be an issue. */
669		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
670			struct bhnd_nvram_bcm_hvar	*hvar;
671			union bhnd_nvram_bcm_hvar_value	 hval;
672			size_t				 hval_len;
673
674			hvar = &bcm->hvars[i];
675
676			/* Already matched? */
677			if (hvar->envp != NULL)
678				continue;
679
680			/* Name matches? */
681			if ((strcmp(name, hvar->name)) != 0)
682				continue;
683
684			/* Save pointer to mirrored envp */
685			hvar->envp = envp;
686
687			/* Check for stale value */
688			hval_len = sizeof(hval);
689			error = bhnd_nvram_value_coerce(value, value_len,
690			    BHND_NVRAM_TYPE_STRING, &hval, &hval_len,
691			    hvar->type);
692			if (error) {
693				/* If parsing fails, we can likely only make
694				 * things worse by trying to synchronize the
695				 * variables */
696				BHND_NV_LOG("error parsing header variable "
697				    "'%s=%s': %d\n", name, value, error);
698			} else if (hval_len != hvar->len) {
699				hvar->stale = true;
700			} else if (memcmp(&hval, &hvar->value, hval_len) != 0) {
701				hvar->stale = true;
702			}
703		}
704
705		/* Seek past the value's terminating '\0' */
706		io_offset += envp_len;
707		if (io_offset == io_size) {
708			BHND_NV_LOG("missing terminating NUL at offset %#zx\n",
709			    io_offset);
710			return (EINVAL);
711		}
712
713		if (*(p + io_offset) != '\0') {
714			BHND_NV_LOG("invalid terminator '%#hhx' at offset "
715			    "%#zx\n", *(p + io_offset), io_offset);
716			return (EINVAL);
717		}
718
719		/* Update variable count */
720		bcm->count++;
721
722		/* Seek to the next record */
723		if (++io_offset == io_size) {
724			char ch;
725
726			/* Hit EOF without finding a terminating NUL
727			 * byte; we need to grow our buffer and append
728			 * it */
729			io_size++;
730			if ((error = bhnd_nvram_io_setsize(bcm->data, io_size)))
731				return (error);
732
733			/* Write NUL byte */
734			ch = '\0';
735			error = bhnd_nvram_io_write(bcm->data, io_size-1, &ch,
736			    sizeof(ch));
737			if (error)
738				return (error);
739		}
740
741		/* Check for explicit EOF (encoded as a single empty NUL
742		 * terminated string) */
743		if (*(p + io_offset) == '\0')
744			break;
745	}
746
747	/* Add non-mirrored header variables to total count variable */
748	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
749		if (bcm->hvars[i].envp == NULL)
750			bcm->count++;
751	}
752
753	/* Populate serialization options from our header */
754	bcm_ver = BCM_NVRAM_GET_BITS(hdr.cfg0, BCM_NVRAM_CFG0_VER);
755	error = bhnd_nvram_plist_append_bytes(bcm->opts,
756	    BCM_NVRAM_ENCODE_OPT_VERSION, &bcm_ver, sizeof(bcm_ver),
757	    BHND_NVRAM_TYPE_UINT8);
758	if (error)
759		return (error);
760
761	return (0);
762}
763
764static int
765bhnd_nvram_bcm_new(struct bhnd_nvram_data *nv, struct bhnd_nvram_io *io)
766{
767	struct bhnd_nvram_bcm	*bcm;
768	int			 error;
769
770	bcm = (struct bhnd_nvram_bcm *)nv;
771
772	/* Populate default BCM mirrored header variable set */
773	_Static_assert(sizeof(bcm->hvars) == sizeof(bhnd_nvram_bcm_hvars),
774	    "hvar declarations must match bhnd_nvram_bcm_hvars template");
775	memcpy(bcm->hvars, bhnd_nvram_bcm_hvars, sizeof(bcm->hvars));
776
777	/* Allocate (empty) option list, to be populated by
778	 * bhnd_nvram_bcm_init() */
779	bcm->opts = bhnd_nvram_plist_new();
780	if (bcm->opts == NULL)
781		return (ENOMEM);
782
783	/* Parse the BCM input data and initialize our backing
784	 * data representation */
785	if ((error = bhnd_nvram_bcm_init(bcm, io))) {
786		bhnd_nvram_bcm_free(nv);
787		return (error);
788	}
789
790	return (0);
791}
792
793static void
794bhnd_nvram_bcm_free(struct bhnd_nvram_data *nv)
795{
796	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
797
798	if (bcm->data != NULL)
799		bhnd_nvram_io_free(bcm->data);
800
801	if (bcm->opts != NULL)
802		bhnd_nvram_plist_release(bcm->opts);
803}
804
805size_t
806bhnd_nvram_bcm_count(struct bhnd_nvram_data *nv)
807{
808	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
809	return (bcm->count);
810}
811
812static bhnd_nvram_plist *
813bhnd_nvram_bcm_options(struct bhnd_nvram_data *nv)
814{
815	struct bhnd_nvram_bcm *bcm = (struct bhnd_nvram_bcm *)nv;
816	return (bcm->opts);
817}
818
819static uint32_t
820bhnd_nvram_bcm_caps(struct bhnd_nvram_data *nv)
821{
822	return (BHND_NVRAM_DATA_CAP_READ_PTR|BHND_NVRAM_DATA_CAP_DEVPATHS);
823}
824
825static const char *
826bhnd_nvram_bcm_next(struct bhnd_nvram_data *nv, void **cookiep)
827{
828	struct bhnd_nvram_bcm		*bcm;
829	struct bhnd_nvram_bcm_hvar	*hvar, *hvar_next;
830	const void			*ptr;
831	const char			*envp, *basep;
832	size_t				 io_size, io_offset;
833	int				 error;
834
835	bcm = (struct bhnd_nvram_bcm *)nv;
836
837	io_offset = sizeof(struct bhnd_nvram_bcmhdr);
838	io_size = bhnd_nvram_io_getsize(bcm->data) - io_offset;
839
840	/* Map backing buffer */
841	error = bhnd_nvram_io_read_ptr(bcm->data, io_offset, &ptr, io_size,
842	    NULL);
843	if (error) {
844		BHND_NV_LOG("error mapping backing buffer: %d\n", error);
845		return (NULL);
846	}
847
848	basep = ptr;
849
850	/* If cookiep pointers into our header variable array, handle as header
851	 * variable iteration. */
852	hvar = bhnd_nvram_bcm_to_hdrvar(bcm, *cookiep);
853	if (hvar != NULL) {
854		size_t idx;
855
856		/* Advance to next entry, if any */
857		idx = bhnd_nvram_bcm_hdrvar_index(bcm, hvar) + 1;
858
859		/* Find the next header-defined variable that isn't defined in
860		 * the NVRAM data, start iteration there */
861		for (size_t i = idx; i < nitems(bcm->hvars); i++) {
862			hvar_next = &bcm->hvars[i];
863			if (hvar_next->envp != NULL && !hvar_next->stale)
864				continue;
865
866			*cookiep = hvar_next;
867			return (hvar_next->name);
868		}
869
870		/* No further header-defined variables; iteration
871		 * complete */
872		return (NULL);
873	}
874
875	/* Handle standard NVRAM data iteration */
876	if (*cookiep == NULL) {
877		/* Start at the first NVRAM data record */
878		envp = basep;
879	} else {
880		/* Seek to next record */
881		envp = *cookiep;
882		envp += strlen(envp) + 1;	/* key + '\0' */
883		envp += strlen(envp) + 1;	/* value + '\0' */
884	}
885
886	/*
887	 * Skip entries that have an existing header variable entry that takes
888	 * precedence over the NVRAM data value.
889	 *
890	 * The header's value will be provided when performing header variable
891	 * iteration
892	 */
893	 while ((size_t)(envp - basep) < io_size && *envp != '\0') {
894		/* Locate corresponding header variable */
895		hvar = NULL;
896		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
897			if (bcm->hvars[i].envp != envp)
898				continue;
899
900			hvar = &bcm->hvars[i];
901			break;
902		}
903
904		/* If no corresponding hvar entry, or the entry does not take
905		 * precedence over this NVRAM value, we can safely return this
906		 * value as-is. */
907		if (hvar == NULL || !hvar->stale)
908			break;
909
910		/* Seek to next record */
911		envp += strlen(envp) + 1;	/* key + '\0' */
912		envp += strlen(envp) + 1;	/* value + '\0' */
913	 }
914
915	/* On NVRAM data EOF, try switching to header variables */
916	if ((size_t)(envp - basep) == io_size || *envp == '\0') {
917		/* Find first valid header variable */
918		for (size_t i = 0; i < nitems(bcm->hvars); i++) {
919			if (bcm->hvars[i].envp != NULL)
920				continue;
921
922			*cookiep = &bcm->hvars[i];
923			return (bcm->hvars[i].name);
924		}
925
926		/* No header variables */
927		return (NULL);
928	}
929
930	*cookiep = __DECONST(void *, envp);
931	return (envp);
932}
933
934static void *
935bhnd_nvram_bcm_find(struct bhnd_nvram_data *nv, const char *name)
936{
937	return (bhnd_nvram_data_generic_find(nv, name));
938}
939
940static int
941bhnd_nvram_bcm_getvar_order(struct bhnd_nvram_data *nv, void *cookiep1,
942    void *cookiep2)
943{
944	struct bhnd_nvram_bcm		*bcm;
945	struct bhnd_nvram_bcm_hvar	*hvar1, *hvar2;
946
947	bcm = (struct bhnd_nvram_bcm *)nv;
948
949	hvar1 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep1);
950	hvar2 = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep2);
951
952	/* Header variables are always ordered below any variables defined
953	 * in the BCM data */
954	if (hvar1 != NULL && hvar2 == NULL) {
955		return (1);	/* hvar follows non-hvar */
956	} else if (hvar1 == NULL && hvar2 != NULL) {
957		return (-1);	/* non-hvar precedes hvar */
958	}
959
960	/* Otherwise, both cookies are either hvars or non-hvars. We can
961	 * safely fall back on pointer order, which will provide a correct
962	 * ordering matching the behavior of bhnd_nvram_data_next() for
963	 * both cases */
964	if (cookiep1 < cookiep2)
965		return (-1);
966
967	if (cookiep1 > cookiep2)
968		return (1);
969
970	return (0);
971}
972
973static int
974bhnd_nvram_bcm_getvar(struct bhnd_nvram_data *nv, void *cookiep, void *buf,
975    size_t *len, bhnd_nvram_type type)
976{
977	return (bhnd_nvram_data_generic_rp_getvar(nv, cookiep, buf, len, type));
978}
979
980static int
981bhnd_nvram_bcm_copy_val(struct bhnd_nvram_data *nv, void *cookiep,
982    bhnd_nvram_val **value)
983{
984	return (bhnd_nvram_data_generic_rp_copy_val(nv, cookiep, value));
985}
986
987static const void *
988bhnd_nvram_bcm_getvar_ptr(struct bhnd_nvram_data *nv, void *cookiep,
989    size_t *len, bhnd_nvram_type *type)
990{
991	struct bhnd_nvram_bcm		*bcm;
992	struct bhnd_nvram_bcm_hvar	*hvar;
993	const char			*envp;
994
995	bcm = (struct bhnd_nvram_bcm *)nv;
996
997	/* Handle header variables */
998	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
999		BHND_NV_ASSERT(bhnd_nvram_value_check_aligned(&hvar->value,
1000		    hvar->len, hvar->type) == 0, ("value misaligned"));
1001
1002		*type = hvar->type;
1003		*len = hvar->len;
1004		return (&hvar->value);
1005	}
1006
1007	/* Cookie points to key\0value\0 -- get the value address */
1008	BHND_NV_ASSERT(cookiep != NULL, ("NULL cookiep"));
1009
1010	envp = cookiep;
1011	envp += strlen(envp) + 1;	/* key + '\0' */
1012	*len = strlen(envp) + 1;	/* value + '\0' */
1013	*type = BHND_NVRAM_TYPE_STRING;
1014
1015	return (envp);
1016}
1017
1018static const char *
1019bhnd_nvram_bcm_getvar_name(struct bhnd_nvram_data *nv, void *cookiep)
1020{
1021	struct bhnd_nvram_bcm		*bcm;
1022	struct bhnd_nvram_bcm_hvar	*hvar;
1023
1024	bcm = (struct bhnd_nvram_bcm *)nv;
1025
1026	/* Handle header variables */
1027	if ((hvar = bhnd_nvram_bcm_to_hdrvar(bcm, cookiep)) != NULL) {
1028		return (hvar->name);
1029	}
1030
1031	/* Cookie points to key\0value\0 */
1032	return (cookiep);
1033}
1034
1035static int
1036bhnd_nvram_bcm_filter_setvar(struct bhnd_nvram_data *nv, const char *name,
1037    bhnd_nvram_val *value, bhnd_nvram_val **result)
1038{
1039	bhnd_nvram_val	*str;
1040	int		 error;
1041
1042	/* Name (trimmed of any path prefix) must be valid */
1043	if (!bhnd_nvram_validate_name(bhnd_nvram_trim_path_name(name)))
1044		return (EINVAL);
1045
1046	/* Value must be bcm-formatted string */
1047	error = bhnd_nvram_val_convert_new(&str, &bhnd_nvram_val_bcm_string_fmt,
1048	    value, BHND_NVRAM_VAL_DYNAMIC);
1049	if (error)
1050		return (error);
1051
1052	/* Success. Transfer result ownership to the caller. */
1053	*result = str;
1054	return (0);
1055}
1056
1057static int
1058bhnd_nvram_bcm_filter_unsetvar(struct bhnd_nvram_data *nv, const char *name)
1059{
1060	/* We permit deletion of any variable */
1061	return (0);
1062}
1063
1064/**
1065 * Return the internal BCM data reference for a header-defined variable
1066 * with @p name, or NULL if none exists.
1067 */
1068static struct bhnd_nvram_bcm_hvar *
1069bhnd_nvram_bcm_gethdrvar(struct bhnd_nvram_bcm *bcm, const char *name)
1070{
1071	for (size_t i = 0; i < nitems(bcm->hvars); i++) {
1072		if (strcmp(bcm->hvars[i].name, name) == 0)
1073			return (&bcm->hvars[i]);
1074	}
1075
1076	/* Not found */
1077	return (NULL);
1078}
1079
1080/**
1081 * If @p cookiep references a header-defined variable, return the
1082 * internal BCM data reference. Otherwise, returns NULL.
1083 */
1084static struct bhnd_nvram_bcm_hvar *
1085bhnd_nvram_bcm_to_hdrvar(struct bhnd_nvram_bcm *bcm, void *cookiep)
1086{
1087#ifdef BHND_NVRAM_INVARIANTS
1088	uintptr_t base, ptr;
1089#endif
1090
1091	/* If the cookie falls within the hvar array, it's a
1092	 * header variable cookie */
1093	if (nitems(bcm->hvars) == 0)
1094		return (NULL);
1095
1096	if (cookiep < (void *)&bcm->hvars[0])
1097		return (NULL);
1098
1099	if (cookiep > (void *)&bcm->hvars[nitems(bcm->hvars)-1])
1100		return (NULL);
1101
1102#ifdef BHND_NVRAM_INVARIANTS
1103	base = (uintptr_t)bcm->hvars;
1104	ptr = (uintptr_t)cookiep;
1105
1106	BHND_NV_ASSERT((ptr - base) % sizeof(bcm->hvars[0]) == 0,
1107	    ("misaligned hvar pointer %p/%p", cookiep, bcm->hvars));
1108#endif /* INVARIANTS */
1109
1110	return ((struct bhnd_nvram_bcm_hvar *)cookiep);
1111}
1112
1113/**
1114 * Return the index of @p hdrvar within @p bcm's backing hvars array.
1115 */
1116static size_t
1117bhnd_nvram_bcm_hdrvar_index(struct bhnd_nvram_bcm *bcm,
1118    struct bhnd_nvram_bcm_hvar *hdrvar)
1119{
1120	BHND_NV_ASSERT(bhnd_nvram_bcm_to_hdrvar(bcm, (void *)hdrvar) != NULL,
1121	    ("%p is not a valid hdrvar reference", hdrvar));
1122
1123	return (hdrvar - &bcm->hvars[0]);
1124}
1125