xref: /freebsd/sys/dev/bhnd/nvram/bhnd_sprom.c (revision e83ce340355a3a75b0343b46e23fe95ed92157f4)
1*e83ce340SAdrian Chadd /*-
2*e83ce340SAdrian Chadd  * Copyright (c) 2015 Landon Fuller <landon@landonf.org>
3*e83ce340SAdrian Chadd  * All rights reserved.
4*e83ce340SAdrian Chadd  *
5*e83ce340SAdrian Chadd  * Redistribution and use in source and binary forms, with or without
6*e83ce340SAdrian Chadd  * modification, are permitted provided that the following conditions
7*e83ce340SAdrian Chadd  * are met:
8*e83ce340SAdrian Chadd  * 1. Redistributions of source code must retain the above copyright
9*e83ce340SAdrian Chadd  *    notice, this list of conditions and the following disclaimer,
10*e83ce340SAdrian Chadd  *    without modification.
11*e83ce340SAdrian Chadd  * 2. Redistributions in binary form must reproduce at minimum a disclaimer
12*e83ce340SAdrian Chadd  *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
13*e83ce340SAdrian Chadd  *    redistribution must be conditioned upon including a substantially
14*e83ce340SAdrian Chadd  *    similar Disclaimer requirement for further binary redistribution.
15*e83ce340SAdrian Chadd  *
16*e83ce340SAdrian Chadd  * NO WARRANTY
17*e83ce340SAdrian Chadd  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18*e83ce340SAdrian Chadd  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19*e83ce340SAdrian Chadd  * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
20*e83ce340SAdrian Chadd  * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
21*e83ce340SAdrian Chadd  * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
22*e83ce340SAdrian Chadd  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23*e83ce340SAdrian Chadd  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24*e83ce340SAdrian Chadd  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25*e83ce340SAdrian Chadd  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26*e83ce340SAdrian Chadd  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
27*e83ce340SAdrian Chadd  * THE POSSIBILITY OF SUCH DAMAGES.
28*e83ce340SAdrian Chadd  */
29*e83ce340SAdrian Chadd 
30*e83ce340SAdrian Chadd #include <sys/cdefs.h>
31*e83ce340SAdrian Chadd __FBSDID("$FreeBSD$");
32*e83ce340SAdrian Chadd 
33*e83ce340SAdrian Chadd #include <sys/param.h>
34*e83ce340SAdrian Chadd #include <sys/bus.h>
35*e83ce340SAdrian Chadd #include <sys/endian.h>
36*e83ce340SAdrian Chadd #include <sys/rman.h>
37*e83ce340SAdrian Chadd #include <sys/systm.h>
38*e83ce340SAdrian Chadd 
39*e83ce340SAdrian Chadd #include <machine/bus.h>
40*e83ce340SAdrian Chadd #include <machine/resource.h>
41*e83ce340SAdrian Chadd 
42*e83ce340SAdrian Chadd #include <dev/bhnd/bhndvar.h>
43*e83ce340SAdrian Chadd 
44*e83ce340SAdrian Chadd #include "nvramvar.h"
45*e83ce340SAdrian Chadd 
46*e83ce340SAdrian Chadd #include "bhnd_spromreg.h"
47*e83ce340SAdrian Chadd #include "bhnd_spromvar.h"
48*e83ce340SAdrian Chadd 
49*e83ce340SAdrian Chadd /*
50*e83ce340SAdrian Chadd  * BHND SPROM Parsing
51*e83ce340SAdrian Chadd  *
52*e83ce340SAdrian Chadd  * Provides identification and parsing of BHND SPROM data.
53*e83ce340SAdrian Chadd  */
54*e83ce340SAdrian Chadd 
55*e83ce340SAdrian Chadd static int	sprom_direct_read(struct bhnd_sprom *sc, size_t offset,
56*e83ce340SAdrian Chadd 		    void *buf, size_t nbytes, uint8_t *crc);
57*e83ce340SAdrian Chadd static int	sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size,
58*e83ce340SAdrian Chadd 		    uint8_t *crc);
59*e83ce340SAdrian Chadd static int	sprom_populate_shadow(struct bhnd_sprom *sc);
60*e83ce340SAdrian Chadd 
61*e83ce340SAdrian Chadd static int	sprom_var_defn(struct bhnd_sprom *sc, const char *name,
62*e83ce340SAdrian Chadd 		    const struct bhnd_nvram_var **var,
63*e83ce340SAdrian Chadd 		    const struct bhnd_sprom_var **sprom, size_t *size);
64*e83ce340SAdrian Chadd 
65*e83ce340SAdrian Chadd /* SPROM revision is always located at the second-to-last byte */
66*e83ce340SAdrian Chadd #define	SPROM_REV(_sc)		SPROM_READ_1((_sc), (_sc)->sp_size - 2)
67*e83ce340SAdrian Chadd 
68*e83ce340SAdrian Chadd /* SPROM CRC is always located at the last byte */
69*e83ce340SAdrian Chadd #define	SPROM_CRC_OFF(_sc)	SPROM_CRC_LEN(_sc)
70*e83ce340SAdrian Chadd 
71*e83ce340SAdrian Chadd /* SPROM CRC covers all but the final CRC byte */
72*e83ce340SAdrian Chadd #define	SPROM_CRC_LEN(_sc)	((_sc)->sp_size - 1)
73*e83ce340SAdrian Chadd 
74*e83ce340SAdrian Chadd /* SPROM shadow I/O (with byte-order translation) */
75*e83ce340SAdrian Chadd #define	SPROM_READ_1(_sc, _off)		SPROM_READ_ENC_1(_sc, _off)
76*e83ce340SAdrian Chadd #define	SPROM_READ_2(_sc, _off)		le16toh(SPROM_READ_ENC_2(_sc, _off))
77*e83ce340SAdrian Chadd #define	SPROM_READ_4(_sc, _off)		le32toh(SPROM_READ_ENC_4(_sc, _off))
78*e83ce340SAdrian Chadd 
79*e83ce340SAdrian Chadd #define	SPROM_WRITE_1(_sc, _off, _v)	SPROM_WRITE_ENC_1(_sc, _off, (_v))
80*e83ce340SAdrian Chadd #define	SPROM_WRITE_2(_sc, _off, _v)	SPROM_WRITE_ENC_2(_sc, _off,	\
81*e83ce340SAdrian Chadd     htole16(_v))
82*e83ce340SAdrian Chadd #define	SPROM_WRITE_4(_sc, _off, _v)	SPROM_WRITE_ENC_4(_sc, _off,	\
83*e83ce340SAdrian Chadd     htole32(_v))
84*e83ce340SAdrian Chadd 
85*e83ce340SAdrian Chadd /* SPROM shadow I/O (without byte-order translation) */
86*e83ce340SAdrian Chadd #define	SPROM_READ_ENC_1(_sc, _off)	(*(uint8_t *)((_sc)->sp_shadow + _off))
87*e83ce340SAdrian Chadd #define	SPROM_READ_ENC_2(_sc, _off)	(*(uint16_t *)((_sc)->sp_shadow + _off))
88*e83ce340SAdrian Chadd #define	SPROM_READ_ENC_4(_sc, _off)	(*(uint32_t *)((_sc)->sp_shadow + _off))
89*e83ce340SAdrian Chadd 
90*e83ce340SAdrian Chadd #define	SPROM_WRITE_ENC_1(_sc, _off, _v)	\
91*e83ce340SAdrian Chadd 	*((uint8_t *)((_sc)->sp_shadow + _off)) = (_v)
92*e83ce340SAdrian Chadd #define	SPROM_WRITE_ENC_2(_sc, _off, _v)	\
93*e83ce340SAdrian Chadd 	*((uint16_t *)((_sc)->sp_shadow + _off)) = (_v)
94*e83ce340SAdrian Chadd #define	SPROM_WRITE_ENC_4(_sc, _off, _v)	\
95*e83ce340SAdrian Chadd 	*((uint32_t *)((_sc)->sp_shadow + _off)) = (_v)
96*e83ce340SAdrian Chadd 
97*e83ce340SAdrian Chadd /* Call @p _next macro with the C type, widened (signed or unsigned) C
98*e83ce340SAdrian Chadd  * type, and width associated with @p _dtype */
99*e83ce340SAdrian Chadd #define	SPROM_SWITCH_TYPE(_dtype, _next, ...)				\
100*e83ce340SAdrian Chadd do {									\
101*e83ce340SAdrian Chadd 	switch (_dtype) {						\
102*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_UINT8:					\
103*e83ce340SAdrian Chadd 		_next (uint8_t,		uint32_t,	1,		\
104*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
105*e83ce340SAdrian Chadd 		break;							\
106*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_UINT16:					\
107*e83ce340SAdrian Chadd 		_next (uint16_t,	uint32_t,	2,		\
108*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
109*e83ce340SAdrian Chadd 		break;							\
110*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_UINT32:					\
111*e83ce340SAdrian Chadd 		_next (uint32_t,	uint32_t,	4,		\
112*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
113*e83ce340SAdrian Chadd 		break;							\
114*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_INT8:					\
115*e83ce340SAdrian Chadd 		_next (int8_t,		int32_t,	1,		\
116*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
117*e83ce340SAdrian Chadd 		break;							\
118*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_INT16:					\
119*e83ce340SAdrian Chadd 		_next (int16_t,		int32_t,	2,		\
120*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
121*e83ce340SAdrian Chadd 		break;							\
122*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_INT32:					\
123*e83ce340SAdrian Chadd 		_next (int32_t,		int32_t,	4,		\
124*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
125*e83ce340SAdrian Chadd 		break;							\
126*e83ce340SAdrian Chadd 	case BHND_NVRAM_DT_CHAR:					\
127*e83ce340SAdrian Chadd 		_next (uint8_t,		uint32_t,	1,		\
128*e83ce340SAdrian Chadd 		    ## __VA_ARGS__);					\
129*e83ce340SAdrian Chadd 		break;							\
130*e83ce340SAdrian Chadd 	}								\
131*e83ce340SAdrian Chadd } while (0)
132*e83ce340SAdrian Chadd 
133*e83ce340SAdrian Chadd /*
134*e83ce340SAdrian Chadd  * Table of supported SPROM image formats, sorted by image size, ascending.
135*e83ce340SAdrian Chadd  */
136*e83ce340SAdrian Chadd #define	SPROM_FMT(_sz, _revmin, _revmax, _sig)	\
137*e83ce340SAdrian Chadd 	{ SPROM_SZ_ ## _sz, _revmin, _revmax,	\
138*e83ce340SAdrian Chadd 	    SPROM_SIG_ ## _sig ## _OFF,		\
139*e83ce340SAdrian Chadd 	    SPROM_SIG_ ## _sig }
140*e83ce340SAdrian Chadd 
141*e83ce340SAdrian Chadd static const struct sprom_fmt {
142*e83ce340SAdrian Chadd 	size_t		size;
143*e83ce340SAdrian Chadd 	uint8_t		rev_min;
144*e83ce340SAdrian Chadd 	uint8_t		rev_max;
145*e83ce340SAdrian Chadd 	size_t		sig_offset;
146*e83ce340SAdrian Chadd 	uint16_t	sig_req;
147*e83ce340SAdrian Chadd } sprom_fmts[] = {
148*e83ce340SAdrian Chadd 	SPROM_FMT(R1_3,		1, 3,	NONE),
149*e83ce340SAdrian Chadd 	SPROM_FMT(R4_8_9,	4, 4,	R4),
150*e83ce340SAdrian Chadd 	SPROM_FMT(R4_8_9,	8, 9,	R8_9),
151*e83ce340SAdrian Chadd 	SPROM_FMT(R10,		10, 10,	R10),
152*e83ce340SAdrian Chadd 	SPROM_FMT(R11,		11, 11,	R11)
153*e83ce340SAdrian Chadd };
154*e83ce340SAdrian Chadd 
155*e83ce340SAdrian Chadd /**
156*e83ce340SAdrian Chadd  * Identify the SPROM format at @p offset within @p r, verify the CRC,
157*e83ce340SAdrian Chadd  * and allocate a local shadow copy of the SPROM data.
158*e83ce340SAdrian Chadd  *
159*e83ce340SAdrian Chadd  * After successful initialization, @p r will not be accessed; any pin
160*e83ce340SAdrian Chadd  * configuration required for SPROM access may be reset.
161*e83ce340SAdrian Chadd  *
162*e83ce340SAdrian Chadd  * @param[out] sprom On success, will be initialized with shadow of the SPROM
163*e83ce340SAdrian Chadd  * data.
164*e83ce340SAdrian Chadd  * @param r An active resource mapping the SPROM data.
165*e83ce340SAdrian Chadd  * @param offset Offset of the SPROM data within @p resource.
166*e83ce340SAdrian Chadd  */
167*e83ce340SAdrian Chadd int
168*e83ce340SAdrian Chadd bhnd_sprom_init(struct bhnd_sprom *sprom, struct bhnd_resource *r,
169*e83ce340SAdrian Chadd     bus_size_t offset)
170*e83ce340SAdrian Chadd {
171*e83ce340SAdrian Chadd 	bus_size_t	 res_size;
172*e83ce340SAdrian Chadd 	int		 error;
173*e83ce340SAdrian Chadd 
174*e83ce340SAdrian Chadd 	sprom->dev = rman_get_device(r->res);
175*e83ce340SAdrian Chadd 	sprom->sp_res = r;
176*e83ce340SAdrian Chadd 	sprom->sp_res_off = offset;
177*e83ce340SAdrian Chadd 
178*e83ce340SAdrian Chadd 	/* Determine maximum possible SPROM image size */
179*e83ce340SAdrian Chadd 	res_size = rman_get_size(r->res);
180*e83ce340SAdrian Chadd 	if (offset >= res_size)
181*e83ce340SAdrian Chadd 		return (EINVAL);
182*e83ce340SAdrian Chadd 
183*e83ce340SAdrian Chadd 	sprom->sp_size_max = MIN(res_size - offset, SPROM_SZ_MAX);
184*e83ce340SAdrian Chadd 
185*e83ce340SAdrian Chadd 	/* Allocate and populate SPROM shadow */
186*e83ce340SAdrian Chadd 	sprom->sp_size = 0;
187*e83ce340SAdrian Chadd 	sprom->sp_capacity = sprom->sp_size_max;
188*e83ce340SAdrian Chadd 	sprom->sp_shadow = malloc(sprom->sp_capacity, M_BHND, M_NOWAIT);
189*e83ce340SAdrian Chadd 	if (sprom->sp_shadow == NULL)
190*e83ce340SAdrian Chadd 		return (ENOMEM);
191*e83ce340SAdrian Chadd 
192*e83ce340SAdrian Chadd 	/* Read and identify SPROM image */
193*e83ce340SAdrian Chadd 	if ((error = sprom_populate_shadow(sprom)))
194*e83ce340SAdrian Chadd 		return (error);
195*e83ce340SAdrian Chadd 
196*e83ce340SAdrian Chadd 	return (0);
197*e83ce340SAdrian Chadd }
198*e83ce340SAdrian Chadd 
199*e83ce340SAdrian Chadd /**
200*e83ce340SAdrian Chadd  * Release all resources held by @p sprom.
201*e83ce340SAdrian Chadd  *
202*e83ce340SAdrian Chadd  * @param sprom A SPROM instance previously initialized via bhnd_sprom_init().
203*e83ce340SAdrian Chadd  */
204*e83ce340SAdrian Chadd void
205*e83ce340SAdrian Chadd bhnd_sprom_fini(struct bhnd_sprom *sprom)
206*e83ce340SAdrian Chadd {
207*e83ce340SAdrian Chadd 	free(sprom->sp_shadow, M_BHND);
208*e83ce340SAdrian Chadd }
209*e83ce340SAdrian Chadd 
210*e83ce340SAdrian Chadd /* Perform a read using a SPROM offset descriptor, safely widening the
211*e83ce340SAdrian Chadd  * result to its 32-bit representation before assigning it to @p _dest. */
212*e83ce340SAdrian Chadd #define	SPROM_GETVAR_READ(_type, _widen, _width, _sc, _off, _dest)	\
213*e83ce340SAdrian Chadd do {									\
214*e83ce340SAdrian Chadd 	_type _v = (_type)SPROM_READ_ ## _width(_sc, _off->offset);	\
215*e83ce340SAdrian Chadd 	if (_off->shift > 0) {						\
216*e83ce340SAdrian Chadd 		_v >>= _off->shift;					\
217*e83ce340SAdrian Chadd 	} else if (off->shift < 0) {					\
218*e83ce340SAdrian Chadd 		_v <<= -_off->shift;					\
219*e83ce340SAdrian Chadd 	}								\
220*e83ce340SAdrian Chadd 	_dest = ((uint32_t) (_widen) _v) & _off->mask;			\
221*e83ce340SAdrian Chadd } while(0)
222*e83ce340SAdrian Chadd 
223*e83ce340SAdrian Chadd /* Emit a value read using a SPROM offset descriptor, narrowing the
224*e83ce340SAdrian Chadd  * result output representation and, if necessary, OR'ing it with the
225*e83ce340SAdrian Chadd  * previously read value from @p _buf. */
226*e83ce340SAdrian Chadd #define	SPROM_GETVAR_WRITE(_type, _widen, _width, _off, _src, _buf)	\
227*e83ce340SAdrian Chadd do {									\
228*e83ce340SAdrian Chadd 	_type _v = (_type) (_widen) _src;				\
229*e83ce340SAdrian Chadd 	if (_off->cont)							\
230*e83ce340SAdrian Chadd 		_v |= *((_type *)_buf);					\
231*e83ce340SAdrian Chadd 	*((_type *)_buf) = _v;						\
232*e83ce340SAdrian Chadd } while(0)
233*e83ce340SAdrian Chadd 
234*e83ce340SAdrian Chadd /**
235*e83ce340SAdrian Chadd  * Read a SPROM variable, performing conversion to host byte order.
236*e83ce340SAdrian Chadd  *
237*e83ce340SAdrian Chadd  * @param		sc	The SPROM parser state.
238*e83ce340SAdrian Chadd  * @param		name	The SPROM variable name.
239*e83ce340SAdrian Chadd  * @param[out]		buf	On success, the requested value will be written
240*e83ce340SAdrian Chadd  *				to this buffer. This argment may be NULL if
241*e83ce340SAdrian Chadd  *				the value is not desired.
242*e83ce340SAdrian Chadd  * @param[in,out]	len	The capacity of @p buf. On success, will be set
243*e83ce340SAdrian Chadd  *				to the actual size of the requested value.
244*e83ce340SAdrian Chadd  *
245*e83ce340SAdrian Chadd  * @retval 0		success
246*e83ce340SAdrian Chadd  * @retval ENOENT	The requested variable was not found.
247*e83ce340SAdrian Chadd  * @retval ENOMEM	If @p buf is non-NULL and a buffer of @p len is too
248*e83ce340SAdrian Chadd  *			small to hold the requested value.
249*e83ce340SAdrian Chadd  * @retval non-zero	If reading @p name otherwise fails, a regular unix
250*e83ce340SAdrian Chadd  *			error code will be returned.
251*e83ce340SAdrian Chadd  */
252*e83ce340SAdrian Chadd int
253*e83ce340SAdrian Chadd bhnd_sprom_getvar(struct bhnd_sprom *sc, const char *name, void *buf,
254*e83ce340SAdrian Chadd     size_t *len)
255*e83ce340SAdrian Chadd {
256*e83ce340SAdrian Chadd 	const struct bhnd_nvram_var	*nv;
257*e83ce340SAdrian Chadd 	const struct bhnd_sprom_var	*sv;
258*e83ce340SAdrian Chadd 	size_t				 all1_offs;
259*e83ce340SAdrian Chadd 	size_t				 req_size;
260*e83ce340SAdrian Chadd 	int				 error;
261*e83ce340SAdrian Chadd 
262*e83ce340SAdrian Chadd 	if ((error = sprom_var_defn(sc, name, &nv, &sv, &req_size)))
263*e83ce340SAdrian Chadd 		return (error);
264*e83ce340SAdrian Chadd 
265*e83ce340SAdrian Chadd 	/* Provide required size */
266*e83ce340SAdrian Chadd 	if (buf == NULL) {
267*e83ce340SAdrian Chadd 		*len = req_size;
268*e83ce340SAdrian Chadd 		return (0);
269*e83ce340SAdrian Chadd 	}
270*e83ce340SAdrian Chadd 
271*e83ce340SAdrian Chadd 	/* Check (and update) target buffer len */
272*e83ce340SAdrian Chadd 	if (*len < req_size)
273*e83ce340SAdrian Chadd 		return (ENOMEM);
274*e83ce340SAdrian Chadd 	else
275*e83ce340SAdrian Chadd 		*len = req_size;
276*e83ce340SAdrian Chadd 
277*e83ce340SAdrian Chadd 	/* Read data */
278*e83ce340SAdrian Chadd 	all1_offs = 0;
279*e83ce340SAdrian Chadd 	for (size_t i = 0; i < sv->num_offsets; i++) {
280*e83ce340SAdrian Chadd 		const struct bhnd_sprom_offset	*off;
281*e83ce340SAdrian Chadd 		uint32_t			 val;
282*e83ce340SAdrian Chadd 
283*e83ce340SAdrian Chadd 		off = &sv->offsets[i];
284*e83ce340SAdrian Chadd 		KASSERT(!off->cont || i > 0, ("cont marked on first offset"));
285*e83ce340SAdrian Chadd 
286*e83ce340SAdrian Chadd 		/* If not a continuation, advance the output buffer */
287*e83ce340SAdrian Chadd 		if (i > 0 && !off->cont) {
288*e83ce340SAdrian Chadd 			buf = ((uint8_t *)buf) +
289*e83ce340SAdrian Chadd 			    bhnd_nvram_type_width(sv->offsets[i-1].type);
290*e83ce340SAdrian Chadd 		}
291*e83ce340SAdrian Chadd 
292*e83ce340SAdrian Chadd 		/* Read the value, widening to a common uint32
293*e83ce340SAdrian Chadd 		 * representation */
294*e83ce340SAdrian Chadd 		SPROM_SWITCH_TYPE(off->type, SPROM_GETVAR_READ, sc, off, val);
295*e83ce340SAdrian Chadd 
296*e83ce340SAdrian Chadd 		/* If IGNALL1, record whether value has all bits set. */
297*e83ce340SAdrian Chadd 		if (nv->flags & BHND_NVRAM_VF_IGNALL1) {
298*e83ce340SAdrian Chadd 			uint32_t	all1;
299*e83ce340SAdrian Chadd 
300*e83ce340SAdrian Chadd 			all1 = off->mask;
301*e83ce340SAdrian Chadd 			if (off->shift > 0)
302*e83ce340SAdrian Chadd 				all1 >>= off->shift;
303*e83ce340SAdrian Chadd 			else if (off->shift < 0)
304*e83ce340SAdrian Chadd 				all1 <<= -off->shift;
305*e83ce340SAdrian Chadd 
306*e83ce340SAdrian Chadd 			if ((val & all1) == all1)
307*e83ce340SAdrian Chadd 				all1_offs++;
308*e83ce340SAdrian Chadd 		}
309*e83ce340SAdrian Chadd 
310*e83ce340SAdrian Chadd 		/* Write the value, narrowing to the appropriate output
311*e83ce340SAdrian Chadd 		 * width. */
312*e83ce340SAdrian Chadd 		SPROM_SWITCH_TYPE(nv->type, SPROM_GETVAR_WRITE, off, val, buf);
313*e83ce340SAdrian Chadd 	}
314*e83ce340SAdrian Chadd 
315*e83ce340SAdrian Chadd 	/* Should value should be treated as uninitialized? */
316*e83ce340SAdrian Chadd 	if (nv->flags & BHND_NVRAM_VF_IGNALL1 && all1_offs == sv->num_offsets)
317*e83ce340SAdrian Chadd 		return (ENOENT);
318*e83ce340SAdrian Chadd 
319*e83ce340SAdrian Chadd 	return (0);
320*e83ce340SAdrian Chadd }
321*e83ce340SAdrian Chadd 
322*e83ce340SAdrian Chadd /* Perform a read of a variable offset from _src, safely widening the result
323*e83ce340SAdrian Chadd  * to its 32-bit representation before assigning it to @p
324*e83ce340SAdrian Chadd  * _dest. */
325*e83ce340SAdrian Chadd #define	SPROM_SETVAR_READ(_type, _widen, _width, _off, _src, _dest)	\
326*e83ce340SAdrian Chadd do {									\
327*e83ce340SAdrian Chadd 	_type _v = *(const _type *)_src;				\
328*e83ce340SAdrian Chadd 	if (_off->shift > 0) {						\
329*e83ce340SAdrian Chadd 		_v <<= _off->shift;					\
330*e83ce340SAdrian Chadd 	} else if (off->shift < 0) {					\
331*e83ce340SAdrian Chadd 		_v >>= -_off->shift;					\
332*e83ce340SAdrian Chadd 	}								\
333*e83ce340SAdrian Chadd 	_dest = ((uint32_t) (_widen) _v) & _off->mask;			\
334*e83ce340SAdrian Chadd } while(0)
335*e83ce340SAdrian Chadd 
336*e83ce340SAdrian Chadd 
337*e83ce340SAdrian Chadd /* Emit a value read using a SPROM offset descriptor, narrowing the
338*e83ce340SAdrian Chadd  * result output representation and, if necessary, OR'ing it with the
339*e83ce340SAdrian Chadd  * previously read value from @p _buf. */
340*e83ce340SAdrian Chadd #define	SPROM_SETVAR_WRITE(_type, _widen, _width, _sc, _off, _src)	\
341*e83ce340SAdrian Chadd do {									\
342*e83ce340SAdrian Chadd 	_type _v = (_type) (_widen) _src;				\
343*e83ce340SAdrian Chadd 	if (_off->cont)							\
344*e83ce340SAdrian Chadd 		_v |= SPROM_READ_ ## _width(_sc, _off->offset);		\
345*e83ce340SAdrian Chadd 	SPROM_WRITE_ ## _width(_sc, _off->offset, _v);			\
346*e83ce340SAdrian Chadd } while(0)
347*e83ce340SAdrian Chadd 
348*e83ce340SAdrian Chadd /**
349*e83ce340SAdrian Chadd  * Set a local value for a SPROM variable, performing conversion to SPROM byte
350*e83ce340SAdrian Chadd  * order.
351*e83ce340SAdrian Chadd  *
352*e83ce340SAdrian Chadd  * The new value will be written to the backing SPROM shadow.
353*e83ce340SAdrian Chadd  *
354*e83ce340SAdrian Chadd  * @param		sc	The SPROM parser state.
355*e83ce340SAdrian Chadd  * @param		name	The SPROM variable name.
356*e83ce340SAdrian Chadd  * @param[out]		buf	The new value.
357*e83ce340SAdrian Chadd  * @param[in,out]	len	The size of @p buf.
358*e83ce340SAdrian Chadd  *
359*e83ce340SAdrian Chadd  * @retval 0		success
360*e83ce340SAdrian Chadd  * @retval ENOENT	The requested variable was not found.
361*e83ce340SAdrian Chadd  * @retval EINVAL	If @p len does not match the expected variable size.
362*e83ce340SAdrian Chadd  */
363*e83ce340SAdrian Chadd int
364*e83ce340SAdrian Chadd bhnd_sprom_setvar(struct bhnd_sprom *sc, const char *name, const void *buf,
365*e83ce340SAdrian Chadd     size_t len)
366*e83ce340SAdrian Chadd {
367*e83ce340SAdrian Chadd 	const struct bhnd_nvram_var	*nv;
368*e83ce340SAdrian Chadd 	const struct bhnd_sprom_var	*sv;
369*e83ce340SAdrian Chadd 	size_t				 req_size;
370*e83ce340SAdrian Chadd 	int				 error;
371*e83ce340SAdrian Chadd 	uint8_t				 crc;
372*e83ce340SAdrian Chadd 
373*e83ce340SAdrian Chadd 	if ((error = sprom_var_defn(sc, name, &nv, &sv, &req_size)))
374*e83ce340SAdrian Chadd 		return (error);
375*e83ce340SAdrian Chadd 
376*e83ce340SAdrian Chadd 	/* Provide required size */
377*e83ce340SAdrian Chadd 	if (len != req_size)
378*e83ce340SAdrian Chadd 		return (EINVAL);
379*e83ce340SAdrian Chadd 
380*e83ce340SAdrian Chadd 	/* Write data */
381*e83ce340SAdrian Chadd 	for (size_t i = 0; i < sv->num_offsets; i++) {
382*e83ce340SAdrian Chadd 		const struct bhnd_sprom_offset	*off;
383*e83ce340SAdrian Chadd 		uint32_t			 val;
384*e83ce340SAdrian Chadd 
385*e83ce340SAdrian Chadd 		off = &sv->offsets[i];
386*e83ce340SAdrian Chadd 		KASSERT(!off->cont || i > 0, ("cont marked on first offset"));
387*e83ce340SAdrian Chadd 
388*e83ce340SAdrian Chadd 		/* If not a continuation, advance the input pointer */
389*e83ce340SAdrian Chadd 		if (i > 0 && !off->cont) {
390*e83ce340SAdrian Chadd 			buf = ((const uint8_t *)buf) +
391*e83ce340SAdrian Chadd 			    bhnd_nvram_type_width(sv->offsets[i-1].type);
392*e83ce340SAdrian Chadd 		}
393*e83ce340SAdrian Chadd 
394*e83ce340SAdrian Chadd 		/* Read the value, widening to a common uint32
395*e83ce340SAdrian Chadd 		 * representation */
396*e83ce340SAdrian Chadd 		SPROM_SWITCH_TYPE(nv->type, SPROM_SETVAR_READ, off, buf, val);
397*e83ce340SAdrian Chadd 
398*e83ce340SAdrian Chadd 		/* Write the value, narrowing to the appropriate output
399*e83ce340SAdrian Chadd 		 * width. */
400*e83ce340SAdrian Chadd 		SPROM_SWITCH_TYPE(off->type, SPROM_SETVAR_WRITE, sc, off, val);
401*e83ce340SAdrian Chadd 	}
402*e83ce340SAdrian Chadd 
403*e83ce340SAdrian Chadd 	/* Update CRC */
404*e83ce340SAdrian Chadd 	crc = ~bhnd_nvram_crc8(sc->sp_shadow, SPROM_CRC_LEN(sc),
405*e83ce340SAdrian Chadd 	    BHND_NVRAM_CRC8_INITIAL);
406*e83ce340SAdrian Chadd 	SPROM_WRITE_1(sc, SPROM_CRC_OFF(sc), crc);
407*e83ce340SAdrian Chadd 
408*e83ce340SAdrian Chadd 	return (0);
409*e83ce340SAdrian Chadd }
410*e83ce340SAdrian Chadd 
411*e83ce340SAdrian Chadd /* Read and identify the SPROM image by incrementally performing
412*e83ce340SAdrian Chadd  * read + CRC of all supported image formats */
413*e83ce340SAdrian Chadd static int
414*e83ce340SAdrian Chadd sprom_populate_shadow(struct bhnd_sprom *sc)
415*e83ce340SAdrian Chadd {
416*e83ce340SAdrian Chadd 	const struct sprom_fmt	*fmt;
417*e83ce340SAdrian Chadd 	int			 error;
418*e83ce340SAdrian Chadd 	uint16_t		 sig;
419*e83ce340SAdrian Chadd 	uint8_t			 srom_rev;
420*e83ce340SAdrian Chadd 	uint8_t			 crc;
421*e83ce340SAdrian Chadd 
422*e83ce340SAdrian Chadd 	crc = BHND_NVRAM_CRC8_INITIAL;
423*e83ce340SAdrian Chadd 
424*e83ce340SAdrian Chadd 	/* Identify the SPROM revision (and populate the SPROM shadow) */
425*e83ce340SAdrian Chadd 	for (size_t i = 0; i < nitems(sprom_fmts); i++) {
426*e83ce340SAdrian Chadd 		fmt = &sprom_fmts[i];
427*e83ce340SAdrian Chadd 
428*e83ce340SAdrian Chadd 		/* Read image data and check CRC */
429*e83ce340SAdrian Chadd 		if ((error = sprom_extend_shadow(sc, fmt->size, &crc)))
430*e83ce340SAdrian Chadd 			return (error);
431*e83ce340SAdrian Chadd 
432*e83ce340SAdrian Chadd 		/* Skip on invalid CRC */
433*e83ce340SAdrian Chadd 		if (crc != BHND_NVRAM_CRC8_VALID)
434*e83ce340SAdrian Chadd 			continue;
435*e83ce340SAdrian Chadd 
436*e83ce340SAdrian Chadd 		/* Fetch SROM revision */
437*e83ce340SAdrian Chadd 		srom_rev = SPROM_REV(sc);
438*e83ce340SAdrian Chadd 
439*e83ce340SAdrian Chadd 		/* Early sromrev 1 devices (specifically some BCM440x enet
440*e83ce340SAdrian Chadd 		 * cards) are reported to have been incorrectly programmed
441*e83ce340SAdrian Chadd 		 * with a revision of 0x10. */
442*e83ce340SAdrian Chadd 		if (fmt->size == SPROM_SZ_R1_3 && srom_rev == 0x10)
443*e83ce340SAdrian Chadd 			srom_rev = 0x1;
444*e83ce340SAdrian Chadd 
445*e83ce340SAdrian Chadd 		/* Verify revision range */
446*e83ce340SAdrian Chadd 		if (srom_rev < fmt->rev_min || srom_rev > fmt->rev_max)
447*e83ce340SAdrian Chadd 			continue;
448*e83ce340SAdrian Chadd 
449*e83ce340SAdrian Chadd 		/* Verify signature (if any) */
450*e83ce340SAdrian Chadd 		sig = SPROM_SIG_NONE;
451*e83ce340SAdrian Chadd 		if (fmt->sig_offset != SPROM_SIG_NONE_OFF)
452*e83ce340SAdrian Chadd 			sig = SPROM_READ_2(sc, fmt->sig_offset);
453*e83ce340SAdrian Chadd 
454*e83ce340SAdrian Chadd 		if (sig != fmt->sig_req) {
455*e83ce340SAdrian Chadd 			device_printf(sc->dev,
456*e83ce340SAdrian Chadd 			    "invalid sprom %hhu signature: 0x%hx "
457*e83ce340SAdrian Chadd 			    "(expected 0x%hx)\n",
458*e83ce340SAdrian Chadd 			    srom_rev, sig, fmt->sig_req);
459*e83ce340SAdrian Chadd 			return (EINVAL);
460*e83ce340SAdrian Chadd 		}
461*e83ce340SAdrian Chadd 
462*e83ce340SAdrian Chadd 		/* Identified */
463*e83ce340SAdrian Chadd 		sc->sp_rev = srom_rev;
464*e83ce340SAdrian Chadd 		return (0);
465*e83ce340SAdrian Chadd 	}
466*e83ce340SAdrian Chadd 
467*e83ce340SAdrian Chadd 	/* identification failed */
468*e83ce340SAdrian Chadd 	device_printf(sc->dev, "unrecognized sprom format\n");
469*e83ce340SAdrian Chadd 	return (EINVAL);
470*e83ce340SAdrian Chadd }
471*e83ce340SAdrian Chadd 
472*e83ce340SAdrian Chadd /*
473*e83ce340SAdrian Chadd  * Extend the shadowed SPROM buffer to image_size, reading any required
474*e83ce340SAdrian Chadd  * data from the backing SPROM resource and updating the CRC.
475*e83ce340SAdrian Chadd  */
476*e83ce340SAdrian Chadd static int
477*e83ce340SAdrian Chadd sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size,
478*e83ce340SAdrian Chadd     uint8_t *crc)
479*e83ce340SAdrian Chadd {
480*e83ce340SAdrian Chadd 	int	error;
481*e83ce340SAdrian Chadd 
482*e83ce340SAdrian Chadd 	KASSERT(image_size >= sc->sp_size, (("shadow truncation unsupported")));
483*e83ce340SAdrian Chadd 
484*e83ce340SAdrian Chadd 	/* Verify the request fits within our shadow buffer */
485*e83ce340SAdrian Chadd 	if (image_size > sc->sp_capacity)
486*e83ce340SAdrian Chadd 		return (ENOSPC);
487*e83ce340SAdrian Chadd 
488*e83ce340SAdrian Chadd 	/* Skip no-op requests */
489*e83ce340SAdrian Chadd 	if (sc->sp_size == image_size)
490*e83ce340SAdrian Chadd 		return (0);
491*e83ce340SAdrian Chadd 
492*e83ce340SAdrian Chadd 	/* Populate the extended range */
493*e83ce340SAdrian Chadd 	error = sprom_direct_read(sc, sc->sp_size, sc->sp_shadow + sc->sp_size,
494*e83ce340SAdrian Chadd 	     image_size - sc->sp_size, crc);
495*e83ce340SAdrian Chadd 	if (error)
496*e83ce340SAdrian Chadd 		return (error);
497*e83ce340SAdrian Chadd 
498*e83ce340SAdrian Chadd 	sc->sp_size = image_size;
499*e83ce340SAdrian Chadd 	return (0);
500*e83ce340SAdrian Chadd }
501*e83ce340SAdrian Chadd 
502*e83ce340SAdrian Chadd /**
503*e83ce340SAdrian Chadd  * Read nbytes at the given offset from the backing SPROM resource, and
504*e83ce340SAdrian Chadd  * update the CRC.
505*e83ce340SAdrian Chadd  */
506*e83ce340SAdrian Chadd static int
507*e83ce340SAdrian Chadd sprom_direct_read(struct bhnd_sprom *sc, size_t offset, void *buf,
508*e83ce340SAdrian Chadd     size_t nbytes, uint8_t *crc)
509*e83ce340SAdrian Chadd {
510*e83ce340SAdrian Chadd 	bus_size_t	 res_offset;
511*e83ce340SAdrian Chadd 	size_t		 nread;
512*e83ce340SAdrian Chadd 	uint16_t	*p;
513*e83ce340SAdrian Chadd 
514*e83ce340SAdrian Chadd 	KASSERT(nbytes % sizeof(uint16_t) == 0, ("unaligned sprom size"));
515*e83ce340SAdrian Chadd 	KASSERT(offset % sizeof(uint16_t) == 0, ("unaligned sprom offset"));
516*e83ce340SAdrian Chadd 
517*e83ce340SAdrian Chadd 	/* Check for read overrun */
518*e83ce340SAdrian Chadd 	if (offset >= sc->sp_size_max || sc->sp_size_max - offset < nbytes) {
519*e83ce340SAdrian Chadd 		device_printf(sc->dev, "requested SPROM read would overrun\n");
520*e83ce340SAdrian Chadd 		return (EINVAL);
521*e83ce340SAdrian Chadd 	}
522*e83ce340SAdrian Chadd 
523*e83ce340SAdrian Chadd 	p = (uint16_t *)buf;
524*e83ce340SAdrian Chadd 	res_offset = sc->sp_res_off + offset;
525*e83ce340SAdrian Chadd 
526*e83ce340SAdrian Chadd 	/* Perform read */
527*e83ce340SAdrian Chadd 	for (nread = 0; nread < nbytes; nread += 2) {
528*e83ce340SAdrian Chadd 		*p = bhnd_bus_read_stream_2(sc->sp_res, res_offset+nread);
529*e83ce340SAdrian Chadd 		*crc = bhnd_nvram_crc8(p, sizeof(*p), *crc);
530*e83ce340SAdrian Chadd 		p++;
531*e83ce340SAdrian Chadd 	};
532*e83ce340SAdrian Chadd 
533*e83ce340SAdrian Chadd 	return (0);
534*e83ce340SAdrian Chadd }
535*e83ce340SAdrian Chadd 
536*e83ce340SAdrian Chadd 
537*e83ce340SAdrian Chadd /**
538*e83ce340SAdrian Chadd  * Locate the variable and SPROM revision-specific definitions
539*e83ce340SAdrian Chadd  * for variable with @p name.
540*e83ce340SAdrian Chadd  */
541*e83ce340SAdrian Chadd static int
542*e83ce340SAdrian Chadd sprom_var_defn(struct bhnd_sprom *sc, const char *name,
543*e83ce340SAdrian Chadd     const struct bhnd_nvram_var **var,
544*e83ce340SAdrian Chadd     const struct bhnd_sprom_var **sprom,
545*e83ce340SAdrian Chadd     size_t *size)
546*e83ce340SAdrian Chadd {
547*e83ce340SAdrian Chadd 	/* Find variable definition */
548*e83ce340SAdrian Chadd 	*var = bhnd_nvram_var_defn(name);
549*e83ce340SAdrian Chadd 	if (*var == NULL)
550*e83ce340SAdrian Chadd 		return (ENOENT);
551*e83ce340SAdrian Chadd 
552*e83ce340SAdrian Chadd 	/* Find revision-specific SPROM definition */
553*e83ce340SAdrian Chadd 	for (size_t i = 0; i < (*var)->num_sp_descs; i++) {
554*e83ce340SAdrian Chadd 		const struct bhnd_sprom_var *sp = &(*var)->sprom_descs[i];
555*e83ce340SAdrian Chadd 
556*e83ce340SAdrian Chadd 		if (sc->sp_rev < sp->compat.first)
557*e83ce340SAdrian Chadd 			continue;
558*e83ce340SAdrian Chadd 
559*e83ce340SAdrian Chadd 		if (sc->sp_rev > sp->compat.last)
560*e83ce340SAdrian Chadd 			continue;
561*e83ce340SAdrian Chadd 
562*e83ce340SAdrian Chadd 		/* Found */
563*e83ce340SAdrian Chadd 		*sprom = sp;
564*e83ce340SAdrian Chadd 
565*e83ce340SAdrian Chadd 		/* Calculate size in bytes */
566*e83ce340SAdrian Chadd 		*size = bhnd_nvram_type_width((*var)->type) * sp->num_offsets;
567*e83ce340SAdrian Chadd 		return (0);
568*e83ce340SAdrian Chadd 	}
569*e83ce340SAdrian Chadd 
570*e83ce340SAdrian Chadd 	/* Not supported by this SPROM revision */
571*e83ce340SAdrian Chadd 	return (ENOENT);
572*e83ce340SAdrian Chadd }