xref: /freebsd/sys/dev/cfi/cfi_dev.c (revision 81ad6265)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2007, Juniper Networks, Inc.
5  * Copyright (c) 2012-2013, SRI International
6  * All rights reserved.
7  *
8  * Portions of this software were developed by SRI International and the
9  * University of Cambridge Computer Laboratory under DARPA/AFRL contract
10  * (FA8750-10-C-0237) ("CTSRD"), as part of the DARPA CRASH research
11  * programme.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  * 3. Neither the name of the author nor the names of any co-contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
26  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
27  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
28  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
29  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
32  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
33  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 
38 #include <sys/cdefs.h>
39 __FBSDID("$FreeBSD$");
40 
41 #include "opt_cfi.h"
42 
43 #include <sys/param.h>
44 #include <sys/systm.h>
45 #include <sys/bus.h>
46 #include <sys/conf.h>
47 #include <sys/ioccom.h>
48 #include <sys/kernel.h>
49 #include <sys/limits.h>
50 #include <sys/malloc.h>
51 #include <sys/proc.h>
52 #include <sys/sysctl.h>
53 #include <sys/types.h>
54 #include <sys/uio.h>
55 
56 #include <sys/cfictl.h>
57 
58 #include <machine/atomic.h>
59 #include <machine/bus.h>
60 
61 #include <dev/cfi/cfi_var.h>
62 
63 static d_open_t cfi_devopen;
64 static d_close_t cfi_devclose;
65 static d_read_t cfi_devread;
66 static d_write_t cfi_devwrite;
67 static d_ioctl_t cfi_devioctl;
68 
69 struct cdevsw cfi_cdevsw = {
70 	.d_version	=	D_VERSION,
71 	.d_flags	=	0,
72 	.d_name		=	cfi_driver_name,
73 	.d_open		=	cfi_devopen,
74 	.d_close	=	cfi_devclose,
75 	.d_read		=	cfi_devread,
76 	.d_write	=	cfi_devwrite,
77 	.d_ioctl	=	cfi_devioctl,
78 };
79 
80 /*
81  * Begin writing into a new block/sector.  We read the sector into
82  * memory and keep updating that, until we move into another sector
83  * or the process stops writing. At that time we write the whole
84  * sector to flash (see cfi_block_finish).  To avoid unneeded erase
85  * cycles, keep a pristine copy of the sector on hand.
86  */
87 int
88 cfi_block_start(struct cfi_softc *sc, u_int ofs)
89 {
90 	union {
91 		uint8_t		*x8;
92 		uint16_t	*x16;
93 		uint32_t	*x32;
94 	} ptr;
95 	u_int rofs, rsz;
96 	uint32_t val;
97 	int r;
98 
99 	rofs = 0;
100 	for (r = 0; r < sc->sc_regions; r++) {
101 		rsz = sc->sc_region[r].r_blocks * sc->sc_region[r].r_blksz;
102 		if (ofs < rofs + rsz)
103 			break;
104 		rofs += rsz;
105 	}
106 	if (r == sc->sc_regions)
107 		return (EFAULT);
108 
109 	sc->sc_wrbufsz = sc->sc_region[r].r_blksz;
110 	sc->sc_wrbuf = malloc(sc->sc_wrbufsz, M_TEMP, M_WAITOK);
111 	sc->sc_wrofs = ofs - (ofs - rofs) % sc->sc_wrbufsz;
112 
113 	/* Read the block from flash for byte-serving. */
114 	ptr.x8 = sc->sc_wrbuf;
115 	for (r = 0; r < sc->sc_wrbufsz; r += sc->sc_width) {
116 		val = cfi_read_raw(sc, sc->sc_wrofs + r);
117 		switch (sc->sc_width) {
118 		case 1:
119 			*(ptr.x8)++ = val;
120 			break;
121 		case 2:
122 			*(ptr.x16)++ = val;
123 			break;
124 		case 4:
125 			*(ptr.x32)++ = val;
126 			break;
127 		}
128 	}
129 	sc->sc_wrbufcpy = malloc(sc->sc_wrbufsz, M_TEMP, M_WAITOK);
130 	memcpy(sc->sc_wrbufcpy, sc->sc_wrbuf, sc->sc_wrbufsz);
131 	sc->sc_writing = 1;
132 	return (0);
133 }
134 
135 /*
136  * Finish updating the current block/sector by writing the compound
137  * set of changes to the flash.
138  */
139 int
140 cfi_block_finish(struct cfi_softc *sc)
141 {
142 	int error;
143 
144 	error = cfi_write_block(sc);
145 	free(sc->sc_wrbuf, M_TEMP);
146 	free(sc->sc_wrbufcpy, M_TEMP);
147 	sc->sc_wrbuf = NULL;
148 	sc->sc_wrbufsz = 0;
149 	sc->sc_wrofs = 0;
150 	sc->sc_writing = 0;
151 	return (error);
152 }
153 
154 static int
155 cfi_devopen(struct cdev *dev, int oflags, int devtype, struct thread *td)
156 {
157 	struct cfi_softc *sc;
158 
159 	sc = dev->si_drv1;
160 	/* We allow only 1 open. */
161 	if (!atomic_cmpset_acq_ptr((uintptr_t *)&sc->sc_opened,
162 	    (uintptr_t)NULL, (uintptr_t)td->td_proc))
163 		return (EBUSY);
164 	return (0);
165 }
166 
167 static int
168 cfi_devclose(struct cdev *dev, int fflag, int devtype, struct thread *td)
169 {
170 	struct cfi_softc *sc;
171 	int error;
172 
173 	sc = dev->si_drv1;
174 	/* Sanity. Not really necessary. */
175 	if (sc->sc_opened != td->td_proc)
176 		return (ENXIO);
177 
178 	error = (sc->sc_writing) ? cfi_block_finish(sc) : 0;
179 	sc->sc_opened = NULL;
180 	return (error);
181 }
182 
183 static int
184 cfi_devread(struct cdev *dev, struct uio *uio, int ioflag)
185 {
186 	union {
187 		uint8_t		x8[4];
188 		uint16_t	x16[2];
189 		uint32_t	x32[1];
190 	} buf;
191 	struct cfi_softc *sc;
192 	u_int ofs;
193 	uint32_t val;
194 	int error;
195 
196 	sc = dev->si_drv1;
197 
198 	error = (sc->sc_writing) ? cfi_block_finish(sc) : 0;
199 	if (!error)
200 		error = (uio->uio_offset > sc->sc_size) ? EIO : 0;
201 
202 	while (error == 0 && uio->uio_resid > 0 &&
203 	    uio->uio_offset < sc->sc_size) {
204 		ofs = uio->uio_offset;
205 		val = cfi_read_raw(sc, ofs);
206 		switch (sc->sc_width) {
207 		case 1:
208 			buf.x8[0] = val;
209 			break;
210 		case 2:
211 			buf.x16[0] = val;
212 			break;
213 		case 4:
214 			buf.x32[0] = val;
215 			break;
216 		}
217 		ofs &= sc->sc_width - 1;
218 		error = uiomove(buf.x8 + ofs,
219 		    MIN(uio->uio_resid, sc->sc_width - ofs), uio);
220 	}
221 	return (error);
222 }
223 
224 static int
225 cfi_devwrite(struct cdev *dev, struct uio *uio, int ioflag)
226 {
227 	struct cfi_softc *sc;
228 	u_int ofs, top;
229 	int error;
230 
231 	sc = dev->si_drv1;
232 
233 	error = (uio->uio_offset > sc->sc_size) ? EIO : 0;
234 	while (error == 0 && uio->uio_resid > 0 &&
235 	    uio->uio_offset < sc->sc_size) {
236 		ofs = uio->uio_offset;
237 
238 		/*
239 		 * Finish the current block if we're about to write
240 		 * to a different block.
241 		 */
242 		if (sc->sc_writing) {
243 			top = sc->sc_wrofs + sc->sc_wrbufsz;
244 			if (ofs < sc->sc_wrofs || ofs >= top)
245 				cfi_block_finish(sc);
246 		}
247 
248 		/* Start writing to a (new) block if applicable. */
249 		if (!sc->sc_writing) {
250 			error = cfi_block_start(sc, uio->uio_offset);
251 			if (error)
252 				break;
253 		}
254 
255 		top = sc->sc_wrofs + sc->sc_wrbufsz;
256 		error = uiomove(sc->sc_wrbuf + ofs - sc->sc_wrofs,
257 		    MIN(top - ofs, uio->uio_resid), uio);
258 	}
259 	return (error);
260 }
261 
262 static int
263 cfi_devioctl(struct cdev *dev, u_long cmd, caddr_t data, int fflag,
264     struct thread *td)
265 {
266 	struct cfi_softc *sc;
267 	struct cfiocqry *rq;
268 	int error;
269 	u_char val;
270 
271 	sc = dev->si_drv1;
272 	error = 0;
273 
274 	switch (cmd) {
275 	case CFIOCQRY:
276 		if (sc->sc_writing) {
277 			error = cfi_block_finish(sc);
278 			if (error)
279 				break;
280 		}
281 		rq = (struct cfiocqry *)data;
282 		if (rq->offset >= sc->sc_size / sc->sc_width)
283 			return (ESPIPE);
284 		if (rq->offset > ULONG_MAX - rq->count ||
285 		    rq->offset + rq->count > sc->sc_size / sc->sc_width)
286 			return (ENOSPC);
287 
288 		while (!error && rq->count--) {
289 			val = cfi_read_qry(sc, rq->offset++);
290 			error = copyout(&val, rq->buffer++, 1);
291 		}
292 		break;
293 #ifdef CFI_SUPPORT_STRATAFLASH
294 	case CFIOCGFACTORYPR:
295 		error = cfi_intel_get_factory_pr(sc, (uint64_t *)data);
296 		break;
297 	case CFIOCGOEMPR:
298 		error = cfi_intel_get_oem_pr(sc, (uint64_t *)data);
299 		break;
300 	case CFIOCSOEMPR:
301 		error = cfi_intel_set_oem_pr(sc, *(uint64_t *)data);
302 		break;
303 	case CFIOCGPLR:
304 		error = cfi_intel_get_plr(sc, (uint32_t *)data);
305 		break;
306 	case CFIOCSPLR:
307 		error = cfi_intel_set_plr(sc);
308 		break;
309 #endif /* CFI_SUPPORT_STRATAFLASH */
310 	default:
311 		error = ENOIOCTL;
312 		break;
313 	}
314 	return (error);
315 }
316