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