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: stable/11/sys/dev/bhnd/nvram/bhnd_sprom_subr.c 330923 2018-03-14 08:45:19Z eadler $");
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 Parser
51 *
52 * Provides identification, decoding, and encoding of BHND SPROM data.
53 */
54
55static int	sprom_direct_read(struct bhnd_sprom *sc, size_t offset,
56		    void *buf, size_t nbytes, uint8_t *crc);
57static int	sprom_extend_shadow(struct bhnd_sprom *sc, size_t image_size,
58		    uint8_t *crc);
59static int	sprom_populate_shadow(struct bhnd_sprom *sc);
60
61static 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, ...)				\
100do {									\
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
141static 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 */
167int
168bhnd_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 */
204void
205bhnd_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)	\
213do {									\
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)	\
227do {									\
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 */
252int
253bhnd_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)	\
326do {									\
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)	\
341do {									\
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 */
363int
364bhnd_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 */
413static int
414sprom_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 */
476static int
477sprom_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 */
506static int
507sprom_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,
527	    (nbytes / sizeof(uint16_t)));
528	*crc = bhnd_nvram_crc8(p, nbytes, *crc);
529
530	return (0);
531}
532
533
534/**
535 * Locate the variable and SPROM revision-specific definitions
536 * for variable with @p name.
537 */
538static int
539sprom_var_defn(struct bhnd_sprom *sc, const char *name,
540    const struct bhnd_nvram_var **var,
541    const struct bhnd_sprom_var **sprom,
542    size_t *size)
543{
544	/* Find variable definition */
545	*var = bhnd_nvram_var_defn(name);
546	if (*var == NULL)
547		return (ENOENT);
548
549	/* Find revision-specific SPROM definition */
550	for (size_t i = 0; i < (*var)->num_sp_descs; i++) {
551		const struct bhnd_sprom_var *sp = &(*var)->sprom_descs[i];
552
553		if (sc->sp_rev < sp->compat.first)
554			continue;
555
556		if (sc->sp_rev > sp->compat.last)
557			continue;
558
559		/* Found */
560		*sprom = sp;
561
562		/* Calculate size in bytes */
563		*size = bhnd_nvram_type_width((*var)->type) * sp->num_offsets;
564		return (0);
565	}
566
567	/* Not supported by this SPROM revision */
568	return (ENOENT);
569}
570