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