1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2019 Justin Hibbits
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
19 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
23 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
25 * POSSIBILITY OF SUCH DAMAGE.
26 *
27 * $FreeBSD$
28 */
29
30#include <sys/param.h>
31#include <sys/systm.h>
32#include <sys/module.h>
33#include <sys/bus.h>
34#include <sys/conf.h>
35#include <sys/disk.h>
36#include <sys/kernel.h>
37#include <sys/mutex.h>
38#include <sys/uio.h>
39
40#include <dev/ofw/openfirm.h>
41#include <dev/ofw/ofw_bus.h>
42#include <dev/ofw/ofw_bus_subr.h>
43
44#include <machine/bus.h>
45#include <machine/md_var.h>
46#include <machine/pio.h>
47#include <machine/resource.h>
48
49#include "opal.h"
50
51#include <sys/rman.h>
52
53#include <vm/vm.h>
54#include <vm/pmap.h>
55
56#define	NVRAM_BUFSIZE	(65536)	/* 64k blocks */
57
58struct opal_nvram_softc {
59	device_t	 sc_dev;
60	struct mtx	 sc_mtx;
61	uint32_t	 sc_size;
62	uint8_t		*sc_buf;
63	vm_paddr_t	 sc_buf_phys;
64
65	struct cdev 	*sc_cdev;
66	int		 sc_isopen;
67};
68
69#define	NVRAM_LOCK(sc)		mtx_lock(&sc->sc_mtx)
70#define	NVRAM_UNLOCK(sc)	mtx_unlock(&sc->sc_mtx)
71
72/*
73 * Device interface.
74 */
75static int		opal_nvram_probe(device_t);
76static int		opal_nvram_attach(device_t);
77static int		opal_nvram_detach(device_t);
78
79/*
80 * Driver methods.
81 */
82static device_method_t	opal_nvram_methods[] = {
83	/* Device interface */
84	DEVMETHOD(device_probe,		opal_nvram_probe),
85	DEVMETHOD(device_attach,	opal_nvram_attach),
86	DEVMETHOD(device_detach,	opal_nvram_detach),
87	{ 0, 0 }
88};
89
90static driver_t	opal_nvram_driver = {
91	"opal_nvram",
92	opal_nvram_methods,
93	sizeof(struct opal_nvram_softc)
94};
95
96static devclass_t opal_nvram_devclass;
97
98DRIVER_MODULE(opal_nvram, opal, opal_nvram_driver, opal_nvram_devclass, 0, 0);
99
100/*
101 * Cdev methods.
102 */
103
104static	d_open_t	opal_nvram_open;
105static	d_close_t	opal_nvram_close;
106static	d_read_t	opal_nvram_read;
107static	d_write_t	opal_nvram_write;
108static	d_ioctl_t	opal_nvram_ioctl;
109
110static struct cdevsw opal_nvram_cdevsw = {
111	.d_version =	D_VERSION,
112	.d_open =	opal_nvram_open,
113	.d_close =	opal_nvram_close,
114	.d_read =	opal_nvram_read,
115	.d_write =	opal_nvram_write,
116	.d_ioctl =	opal_nvram_ioctl,
117	.d_name =	"nvram",
118};
119
120static int
121opal_nvram_probe(device_t dev)
122{
123
124	if (!ofw_bus_is_compatible(dev, "ibm,opal-nvram"))
125		return (ENXIO);
126
127	device_set_desc(dev, "OPAL NVRAM");
128	return (BUS_PROBE_DEFAULT);
129}
130
131static int
132opal_nvram_attach(device_t dev)
133{
134	struct opal_nvram_softc *sc;
135	phandle_t node;
136	int err;
137
138	node = ofw_bus_get_node(dev);
139	sc = device_get_softc(dev);
140
141	sc->sc_dev = dev;
142
143	err = OF_getencprop(node, "#bytes", &sc->sc_size,
144	    sizeof(sc->sc_size));
145
146	if (err < 0)
147		return (ENXIO);
148
149	sc->sc_buf = contigmalloc(NVRAM_BUFSIZE, M_DEVBUF, M_WAITOK,
150	    0, BUS_SPACE_MAXADDR, PAGE_SIZE, 0);
151	if (sc->sc_buf == NULL) {
152		device_printf(dev, "No memory for buffer.\n");
153		return (ENXIO);
154	}
155	sc->sc_buf_phys = pmap_kextract((vm_offset_t)sc->sc_buf);
156	sc->sc_cdev = make_dev(&opal_nvram_cdevsw, 0, 0, 0, 0600,
157	    "nvram");
158	sc->sc_cdev->si_drv1 = sc;
159
160	mtx_init(&sc->sc_mtx, "opal_nvram", 0, MTX_DEF);
161
162	return (0);
163}
164
165static int
166opal_nvram_detach(device_t dev)
167{
168	struct opal_nvram_softc *sc;
169
170	sc = device_get_softc(dev);
171
172	if (sc->sc_cdev != NULL)
173		destroy_dev(sc->sc_cdev);
174	if (sc->sc_buf != NULL)
175		contigfree(sc->sc_buf, NVRAM_BUFSIZE, M_DEVBUF);
176
177	mtx_destroy(&sc->sc_mtx);
178
179	return (0);
180}
181
182static int
183opal_nvram_open(struct cdev *dev, int flags, int fmt, struct thread *td)
184{
185	struct opal_nvram_softc *sc = dev->si_drv1;
186	int err;
187
188	err = 0;
189
190	NVRAM_LOCK(sc);
191	if (sc->sc_isopen)
192		err = EBUSY;
193	else
194		sc->sc_isopen = 1;
195	NVRAM_UNLOCK(sc);
196
197	return (err);
198}
199
200static int
201opal_nvram_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
202{
203	struct opal_nvram_softc *sc = dev->si_drv1;
204
205	NVRAM_LOCK(sc);
206	sc->sc_isopen = 0;
207	NVRAM_UNLOCK(sc);
208
209	return (0);
210}
211
212static int
213opal_nvram_read(struct cdev *dev, struct uio *uio, int ioflag)
214{
215	struct opal_nvram_softc *sc = dev->si_drv1;
216	int rv, amnt;
217
218	rv = 0;
219
220	NVRAM_LOCK(sc);
221	while (uio->uio_resid > 0) {
222		amnt = MIN(uio->uio_resid, sc->sc_size - uio->uio_offset);
223		amnt = MIN(amnt, NVRAM_BUFSIZE);
224		if (amnt == 0)
225			break;
226
227		rv = opal_call(OPAL_READ_NVRAM, sc->sc_buf_phys,
228		    amnt, uio->uio_offset);
229		if (rv != OPAL_SUCCESS) {
230			switch (rv) {
231			case OPAL_HARDWARE:
232				rv = EIO;
233				break;
234			case OPAL_PARAMETER:
235				rv = EINVAL;
236				break;
237			}
238			break;
239		}
240		rv = uiomove(sc->sc_buf, amnt, uio);
241		if (rv != 0)
242			break;
243	}
244	NVRAM_UNLOCK(sc);
245
246	return (rv);
247}
248
249static int
250opal_nvram_write(struct cdev *dev, struct uio *uio, int ioflag)
251{
252	off_t offset;
253	int rv, amnt;
254	struct opal_nvram_softc *sc = dev->si_drv1;
255
256	rv = 0;
257
258	NVRAM_LOCK(sc);
259	while (uio->uio_resid > 0) {
260		amnt = MIN(uio->uio_resid, sc->sc_size - uio->uio_offset);
261		amnt = MIN(amnt, NVRAM_BUFSIZE);
262		if (amnt == 0) {
263			rv = ENOSPC;
264			break;
265		}
266		offset = uio->uio_offset;
267		rv = uiomove(sc->sc_buf, amnt, uio);
268		if (rv != 0)
269			break;
270		rv = opal_call(OPAL_WRITE_NVRAM, sc->sc_buf_phys, amnt,
271		    offset);
272		if (rv != OPAL_SUCCESS) {
273			switch (rv) {
274			case OPAL_HARDWARE:
275				rv = EIO;
276				break;
277			case OPAL_PARAMETER:
278				rv = EINVAL;
279				break;
280			}
281			break;
282		}
283	}
284
285	NVRAM_UNLOCK(sc);
286
287	return (rv);
288}
289
290static int
291opal_nvram_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
292    struct thread *td)
293{
294	struct opal_nvram_softc *sc = dev->si_drv1;
295
296	switch (cmd) {
297	case DIOCGMEDIASIZE:
298		*(off_t *)data = sc->sc_size;
299		return (0);
300	}
301	return (EINVAL);
302}
303