1/*-
2 * Copyright (c) 2016 Landon Fuller <landonf@FreeBSD.org>
3 * Copyright (c) 2017 The FreeBSD Foundation
4 * All rights reserved.
5 *
6 * Portions of this software were developed by Landon Fuller
7 * under sponsorship from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 *    notice, this list of conditions and the following disclaimer,
14 *    without modification.
15 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
16 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
17 *    redistribution must be conditioned upon including a substantially
18 *    similar Disclaimer requirement for further binary redistribution.
19 *
20 * NO WARRANTY
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
24 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
25 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
26 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31 * THE POSSIBILITY OF SUCH DAMAGES.
32 */
33
34#include <sys/cdefs.h>
35__FBSDID("$FreeBSD$");
36
37/*
38 * BHND CFE NVRAM driver.
39 *
40 * Provides access to device NVRAM via CFE.
41 */
42
43#include <sys/param.h>
44#include <sys/kernel.h>
45#include <sys/bus.h>
46#include <sys/limits.h>
47#include <sys/malloc.h>
48#include <sys/module.h>
49#include <sys/systm.h>
50
51#include <machine/bus.h>
52#include <sys/rman.h>
53#include <machine/resource.h>
54
55#include <dev/bhnd/bhnd.h>
56
57#include <dev/cfe/cfe_api.h>
58#include <dev/cfe/cfe_error.h>
59#include <dev/cfe/cfe_ioctl.h>
60
61#include "bhnd_nvram_if.h"
62
63#include "bcm_machdep.h"
64#include "bcm_nvram_cfevar.h"
65
66BHND_NVRAM_IOPS_DEFN(iocfe)
67
68#define IOCFE_LOG(_io, _fmt, ...)	\
69	printf("%s/%s: " _fmt, __FUNCTION__, (_io)->dname, ##__VA_ARGS__)
70
71static int	bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe,
72		    char *dname);
73
74/** Known CFE NVRAM device names, in probe order. */
75static char *nvram_cfe_devs[] = {
76	"nflash0.nvram",	/* NAND */
77	"nflash1.nvram",
78	"flash0.nvram",
79	"flash1.nvram",
80};
81
82/** Supported CFE NVRAM formats, in probe order. */
83static bhnd_nvram_data_class * const nvram_cfe_fmts[] = {
84	&bhnd_nvram_bcm_class,
85	&bhnd_nvram_tlv_class
86};
87
88static int
89bhnd_nvram_cfe_probe(device_t dev)
90{
91	struct bcm_platform *bp;
92
93	/* Fetch platform NVRAM I/O context */
94	bp = bcm_get_platform();
95	if (bp->nvram_io == NULL)
96		return (ENXIO);
97
98	KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class"));
99
100	/* Set the device description */
101	device_set_desc(dev, bhnd_nvram_data_class_desc(bp->nvram_cls));
102
103	/* Refuse wildcard attachments */
104	return (BUS_PROBE_NOWILDCARD);
105}
106
107
108static int
109bhnd_nvram_cfe_attach(device_t dev)
110{
111	struct bcm_platform		*bp;
112	struct bhnd_nvram_cfe_softc	*sc;
113	int				 error;
114
115	bp = bcm_get_platform();
116	KASSERT(bp->nvram_io != NULL, ("missing NVRAM I/O context"));
117	KASSERT(bp->nvram_cls != NULL, ("missing NVRAM class"));
118
119	sc = device_get_softc(dev);
120	sc->dev = dev;
121
122	error = bhnd_nvram_store_parse_new(&sc->store, bp->nvram_io,
123	    bp->nvram_cls);
124	if (error)
125		return (error);
126
127	error = bhnd_service_registry_add(&bp->services, dev,
128	    BHND_SERVICE_NVRAM, 0);
129	if (error) {
130		bhnd_nvram_store_free(sc->store);
131		return (error);
132	}
133
134	return (error);
135}
136
137static int
138bhnd_nvram_cfe_resume(device_t dev)
139{
140	return (0);
141}
142
143static int
144bhnd_nvram_cfe_suspend(device_t dev)
145{
146	return (0);
147}
148
149static int
150bhnd_nvram_cfe_detach(device_t dev)
151{
152	struct bcm_platform		*bp;
153	struct bhnd_nvram_cfe_softc	*sc;
154	int				 error;
155
156	bp = bcm_get_platform();
157	sc = device_get_softc(dev);
158
159	error = bhnd_service_registry_remove(&bp->services, dev,
160	    BHND_SERVICE_ANY);
161	if (error)
162		return (error);
163
164	bhnd_nvram_store_free(sc->store);
165
166	return (0);
167}
168
169static int
170bhnd_nvram_cfe_getvar(device_t dev, const char *name, void *buf, size_t *len,
171    bhnd_nvram_type type)
172{
173	struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev);
174
175	return (bhnd_nvram_store_getvar(sc->store, name, buf, len, type));
176}
177
178static int
179bhnd_nvram_cfe_setvar(device_t dev, const char *name, const void *buf,
180    size_t len, bhnd_nvram_type type)
181{
182	struct bhnd_nvram_cfe_softc *sc = device_get_softc(dev);
183
184	return (bhnd_nvram_store_setvar(sc->store, name, buf, len, type));
185}
186
187/**
188 * Find, open, identify, and initialize an I/O context mapping the CFE NVRAM
189 * device.
190 *
191 * @param[out]	iocfe		On success, an I/O context mapping the CFE NVRAM
192 *				device.
193 * @param[out]	cls		On success, the identified NVRAM data format
194 *				class.
195 *
196 * @retval 0		success. the caller inherits ownership of @p iocfe.
197 * @retval non-zero	if no usable CFE NVRAM device can be found, a standard
198 *			unix error will be returned.
199 */
200int
201bcm_nvram_find_cfedev(struct bcm_nvram_iocfe *iocfe,
202    bhnd_nvram_data_class **cls)
203{
204	char	*dname;
205	int	 devinfo;
206	int	 error, result;
207
208	for (u_int i = 0; i < nitems(nvram_cfe_fmts); i++) {
209		*cls = nvram_cfe_fmts[i];
210
211		for (u_int j = 0; j < nitems(nvram_cfe_devs); j++) {
212			dname = nvram_cfe_devs[j];
213
214			/* Does the device exist? */
215			if ((devinfo = cfe_getdevinfo(dname)) < 0) {
216				if (devinfo != CFE_ERR_DEVNOTFOUND) {
217					BCM_ERR("cfe_getdevinfo(%s) failed: "
218					    "%d\n", dname, devinfo);
219				}
220
221				continue;
222			}
223
224			/* Open for reading */
225			if ((error = bcm_nvram_iocfe_init(iocfe, dname)))
226				continue;
227
228			/* Probe */
229			result = bhnd_nvram_data_probe(*cls, &iocfe->io);
230			if (result <= 0) {
231				/* Found a supporting NVRAM data class */
232				return (0);
233			}
234
235			/* Keep searching */
236			bhnd_nvram_io_free(&iocfe->io);
237		}
238	}
239
240	return (ENODEV);
241}
242
243
244/**
245 * Initialize a new CFE device-backed I/O context.
246 *
247 * The caller is responsible for releasing all resources held by the returned
248 * I/O context via bhnd_nvram_io_free().
249 *
250 * @param[out]	io	On success, will be initialized as an I/O context for
251 *			CFE device @p dname.
252 * @param	dname	The name of the CFE device to be opened for reading.
253 *
254 * @retval 0		success.
255 * @retval non-zero	if opening @p dname otherwise fails, a standard unix
256 *			error will be returned.
257 */
258static int
259bcm_nvram_iocfe_init(struct bcm_nvram_iocfe *iocfe, char *dname)
260{
261	nvram_info_t		 nvram_info;
262	int			 cerr, devinfo, dtype, rlen;
263	int64_t			 nv_offset;
264	u_int			 nv_size;
265	bool			 req_blk_erase;
266	int			 error;
267
268	iocfe->io.iops = &bhnd_nvram_iocfe_ops;
269	iocfe->dname = dname;
270
271	/* Try to open the device */
272	iocfe->fd = cfe_open(dname);
273	if (iocfe->fd <= 0) {
274		IOCFE_LOG(iocfe, "cfe_open() failed: %d\n", iocfe->fd);
275
276		return (ENXIO);
277	}
278
279	/* Try to fetch device info */
280	if ((devinfo = cfe_getdevinfo(iocfe->dname)) < 0) {
281		IOCFE_LOG(iocfe, "cfe_getdevinfo() failed: %d\n", devinfo);
282		error = ENXIO;
283		goto failed;
284	}
285
286	/* Verify device type */
287	dtype = devinfo & CFE_DEV_MASK;
288	switch (dtype) {
289	case CFE_DEV_FLASH:
290	case CFE_DEV_NVRAM:
291		/* Valid device type */
292		break;
293	default:
294		IOCFE_LOG(iocfe, "unknown device type: %d\n", dtype);
295		error = ENXIO;
296		goto failed;
297	}
298
299	/* Try to fetch nvram info from CFE */
300	cerr = cfe_ioctl(iocfe->fd, IOCTL_NVRAM_GETINFO,
301	    (unsigned char *)&nvram_info, sizeof(nvram_info), &rlen, 0);
302	if (cerr == CFE_OK) {
303		/* Sanity check the result; must not be a negative integer */
304		if (nvram_info.nvram_size < 0 ||
305		    nvram_info.nvram_offset < 0)
306		{
307			IOCFE_LOG(iocfe, "invalid NVRAM layout (%d/%d)\n",
308			    nvram_info.nvram_size, nvram_info.nvram_offset);
309			error = ENXIO;
310			goto failed;
311		}
312
313		nv_offset	= nvram_info.nvram_offset;
314		nv_size		= nvram_info.nvram_size;
315		req_blk_erase	= (nvram_info.nvram_eraseflg != 0);
316	} else if (cerr != CFE_OK && cerr != CFE_ERR_INV_COMMAND) {
317		IOCFE_LOG(iocfe, "IOCTL_NVRAM_GETINFO failed: %d\n", cerr);
318		error = ENXIO;
319		goto failed;
320	}
321
322	/* Fall back on flash info.
323	 *
324	 * This is known to be required on the Asus RT-N53 (CFE 5.70.55.33,
325	 * BBP 1.0.37, BCM5358UB0), where IOCTL_NVRAM_GETINFO returns
326	 * CFE_ERR_INV_COMMAND.
327	 */
328	if (cerr == CFE_ERR_INV_COMMAND) {
329		flash_info_t fi;
330
331		cerr = cfe_ioctl(iocfe->fd, IOCTL_FLASH_GETINFO,
332		    (unsigned char *)&fi, sizeof(fi), &rlen, 0);
333
334		if (cerr != CFE_OK) {
335			IOCFE_LOG(iocfe, "IOCTL_FLASH_GETINFO failed %d\n",
336			    cerr);
337			error = ENXIO;
338			goto failed;
339		}
340
341		nv_offset	= 0x0;
342		nv_size		= fi.flash_size;
343		req_blk_erase	= !(fi.flash_flags & FLASH_FLAG_NOERASE);
344	}
345
346
347	/* Verify that the full NVRAM layout can be represented via size_t */
348	if (nv_size > SIZE_MAX || SIZE_MAX - nv_size < nv_offset) {
349		IOCFE_LOG(iocfe, "invalid NVRAM layout (%#x/%#jx)\n",
350		    nv_size, (intmax_t)nv_offset);
351		error = ENXIO;
352		goto failed;
353	}
354
355	iocfe->offset = nv_offset;
356	iocfe->size = nv_size;
357	iocfe->req_blk_erase = req_blk_erase;
358
359	return (CFE_OK);
360
361failed:
362	if (iocfe->fd >= 0)
363		cfe_close(iocfe->fd);
364
365	return (error);
366}
367
368static void
369bhnd_nvram_iocfe_free(struct bhnd_nvram_io *io)
370{
371	struct bcm_nvram_iocfe	*iocfe = (struct bcm_nvram_iocfe *)io;
372
373	/* CFE I/O instances are statically allocated; we do not need to free
374	 * the instance itself */
375	cfe_close(iocfe->fd);
376}
377
378static size_t
379bhnd_nvram_iocfe_getsize(struct bhnd_nvram_io *io)
380{
381	struct bcm_nvram_iocfe	*iocfe = (struct bcm_nvram_iocfe *)io;
382	return (iocfe->size);
383}
384
385static int
386bhnd_nvram_iocfe_setsize(struct bhnd_nvram_io *io, size_t size)
387{
388	/* unsupported */
389	return (ENODEV);
390}
391
392static int
393bhnd_nvram_iocfe_read_ptr(struct bhnd_nvram_io *io, size_t offset,
394    const void **ptr, size_t nbytes, size_t *navail)
395{
396	/* unsupported */
397	return (ENODEV);
398}
399
400static int
401bhnd_nvram_iocfe_write_ptr(struct bhnd_nvram_io *io, size_t offset,
402    void **ptr, size_t nbytes, size_t *navail)
403{
404	/* unsupported */
405	return (ENODEV);
406}
407
408static int
409bhnd_nvram_iocfe_write(struct bhnd_nvram_io *io, size_t offset, void *buffer,
410    size_t nbytes)
411{
412	/* unsupported */
413	return (ENODEV);
414}
415
416static int
417bhnd_nvram_iocfe_read(struct bhnd_nvram_io *io, size_t offset, void *buffer,
418    size_t nbytes)
419{
420	struct bcm_nvram_iocfe	*iocfe;
421	size_t			 remain;
422	int64_t			 cfe_offset;
423	int			 nr, nreq;
424
425	iocfe = (struct bcm_nvram_iocfe *)io;
426
427	/* Determine (and validate) the base CFE offset */
428#if (SIZE_MAX > INT64_MAX)
429	if (iocfe->offset > INT64_MAX || offset > INT64_MAX)
430		return (ENXIO);
431#endif
432
433	if (INT64_MAX - offset < iocfe->offset)
434		return (ENXIO);
435
436	cfe_offset = iocfe->offset + offset;
437
438	/* Verify that cfe_offset + nbytes is representable */
439	if (INT64_MAX - cfe_offset < nbytes)
440		return (ENXIO);
441
442	/* Perform the read */
443	for (remain = nbytes; remain > 0;) {
444		void	*p;
445		size_t	 nread;
446		int64_t	 cfe_noff;
447
448		nread = (nbytes - remain);
449		cfe_noff = cfe_offset + nread;
450		p = ((uint8_t *)buffer + nread);
451		nreq = ummin(INT_MAX, remain);
452
453		nr = cfe_readblk(iocfe->fd, cfe_noff, p, nreq);
454		if (nr < 0) {
455			IOCFE_LOG(iocfe, "cfe_readblk() failed: %d\n", nr);
456			return (ENXIO);
457		}
458
459		/* Check for unexpected short read */
460		if (nr == 0 && remain > 0) {
461			/* If the request fits entirely within the CFE
462			 * device range, we shouldn't hit EOF */
463			if (remain < iocfe->size &&
464			    iocfe->size - remain > offset)
465			{
466				IOCFE_LOG(iocfe, "cfe_readblk() returned "
467				    "unexpected short read (%d/%d)\n", nr,
468				    nreq);
469				return (ENXIO);
470			}
471		}
472
473		if (nr == 0)
474			break;
475
476		remain -= nr;
477	}
478
479	/* Check for short read */
480	if (remain > 0)
481		return (ENXIO);
482
483	return (0);
484}
485
486static device_method_t bhnd_nvram_cfe_methods[] = {
487	/* Device interface */
488	DEVMETHOD(device_probe,		bhnd_nvram_cfe_probe),
489	DEVMETHOD(device_attach,	bhnd_nvram_cfe_attach),
490	DEVMETHOD(device_resume,	bhnd_nvram_cfe_resume),
491	DEVMETHOD(device_suspend,	bhnd_nvram_cfe_suspend),
492	DEVMETHOD(device_detach,	bhnd_nvram_cfe_detach),
493
494	/* NVRAM interface */
495	DEVMETHOD(bhnd_nvram_getvar,	bhnd_nvram_cfe_getvar),
496	DEVMETHOD(bhnd_nvram_setvar,	bhnd_nvram_cfe_setvar),
497
498	DEVMETHOD_END
499};
500
501DEFINE_CLASS_0(bhnd_nvram, bhnd_nvram_cfe, bhnd_nvram_cfe_methods,
502    sizeof(struct bhnd_nvram_cfe_softc));
503EARLY_DRIVER_MODULE(bhnd_nvram_cfe, nexus, bhnd_nvram_cfe,
504    bhnd_nvram_devclass, NULL, NULL, BUS_PASS_BUS + BUS_PASS_ORDER_EARLY);
505