cfi_disk.c revision 189606
1189606Ssam/*-
2189606Ssam * Copyright (c) 2009 Sam Leffler, Errno Consulting
3189606Ssam * All rights reserved.
4189606Ssam *
5189606Ssam * Redistribution and use in source and binary forms, with or without
6189606Ssam * modification, are permitted provided that the following conditions
7189606Ssam * are met:
8189606Ssam * 1. Redistributions of source code must retain the above copyright
9189606Ssam *    notice, this list of conditions and the following disclaimer.
10189606Ssam * 2. Redistributions in binary form must reproduce the above copyright
11189606Ssam *    notice, this list of conditions and the following disclaimer in the
12189606Ssam *    documentation and/or other materials provided with the distribution.
13189606Ssam *
14189606Ssam * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15189606Ssam * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16189606Ssam * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17189606Ssam * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18189606Ssam * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
19189606Ssam * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20189606Ssam * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21189606Ssam * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22189606Ssam * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
23189606Ssam * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24189606Ssam */
25189606Ssam
26189606Ssam#include <sys/cdefs.h>
27189606Ssam__FBSDID("$FreeBSD: head/sys/dev/cfi/cfi_disk.c 189606 2009-03-09 23:16:02Z sam $");
28189606Ssam
29189606Ssam#include <sys/param.h>
30189606Ssam#include <sys/systm.h>
31189606Ssam#include <sys/bio.h>
32189606Ssam#include <sys/bus.h>
33189606Ssam#include <sys/conf.h>
34189606Ssam#include <sys/kernel.h>
35189606Ssam#include <sys/malloc.h>
36189606Ssam#include <sys/lock.h>
37189606Ssam#include <sys/mutex.h>
38189606Ssam#include <sys/module.h>
39189606Ssam#include <sys/rman.h>
40189606Ssam#include <sys/sysctl.h>
41189606Ssam#include <sys/taskqueue.h>
42189606Ssam
43189606Ssam#include <machine/bus.h>
44189606Ssam
45189606Ssam#include <dev/cfi/cfi_var.h>
46189606Ssam
47189606Ssam#include <geom/geom_disk.h>
48189606Ssam
49189606Ssamstruct cfi_disk_softc {
50189606Ssam	struct cfi_softc *parent;
51189606Ssam	struct disk	*disk;
52189606Ssam	int		flags;
53189606Ssam#define	CFI_DISK_OPEN	0x0001
54189606Ssam	struct bio_queue_head bioq;	/* bio queue */
55189606Ssam	struct mtx	qlock;		/* bioq lock */
56189606Ssam	struct taskqueue *tq;		/* private task queue for i/o request */
57189606Ssam	struct task	iotask;		/* i/o processing */
58189606Ssam};
59189606Ssam
60189606Ssam#define	CFI_DISK_SECSIZE	512
61189606Ssam#define	CFI_DISK_MAXIOSIZE	65536
62189606Ssam
63189606Ssamstatic int cfi_disk_detach(device_t);
64189606Ssamstatic int cfi_disk_open(struct disk *);
65189606Ssamstatic int cfi_disk_close(struct disk *);
66189606Ssamstatic void cfi_io_proc(void *, int);
67189606Ssamstatic void cfi_disk_strategy(struct bio *);
68189606Ssamstatic int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *);
69189606Ssam
70189606Ssamstatic int
71189606Ssamcfi_disk_probe(device_t dev)
72189606Ssam{
73189606Ssam	return 0;
74189606Ssam}
75189606Ssam
76189606Ssamstatic int
77189606Ssamcfi_disk_attach(device_t dev)
78189606Ssam{
79189606Ssam	struct cfi_disk_softc *sc = device_get_softc(dev);
80189606Ssam
81189606Ssam	sc->parent = device_get_softc(device_get_parent(dev));
82189606Ssam	/* validate interface width; assumed by other code */
83189606Ssam	if (sc->parent->sc_width != 1 &&
84189606Ssam	    sc->parent->sc_width != 2 &&
85189606Ssam	    sc->parent->sc_width != 4)
86189606Ssam		return EINVAL;
87189606Ssam
88189606Ssam	sc->disk = disk_alloc();
89189606Ssam	if (sc->disk == NULL)
90189606Ssam		return ENOMEM;
91189606Ssam	sc->disk->d_name = "cfid";
92189606Ssam	sc->disk->d_unit = device_get_unit(dev);
93189606Ssam	sc->disk->d_open = cfi_disk_open;
94189606Ssam	sc->disk->d_close = cfi_disk_close;
95189606Ssam	sc->disk->d_strategy = cfi_disk_strategy;
96189606Ssam	sc->disk->d_ioctl = cfi_disk_ioctl;
97189606Ssam	sc->disk->d_dump = NULL;		/* NB: no dumps */
98189606Ssam	sc->disk->d_sectorsize = CFI_DISK_SECSIZE;
99189606Ssam	sc->disk->d_mediasize = sc->parent->sc_size;
100189606Ssam	sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE;
101189606Ssam	/* NB: use stripesize to hold the erase/region size */
102189606Ssam	if (sc->parent->sc_regions)
103189606Ssam		sc->disk->d_stripesize = sc->parent->sc_region->r_blksz;
104189606Ssam	else
105189606Ssam		sc->disk->d_stripesize = sc->disk->d_mediasize;
106189606Ssam	sc->disk->d_drv1 = sc;
107189606Ssam	disk_create(sc->disk, DISK_VERSION);
108189606Ssam
109189606Ssam	mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF);
110189606Ssam	bioq_init(&sc->bioq);
111189606Ssam
112189606Ssam	sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT,
113189606Ssam		taskqueue_thread_enqueue, &sc->tq);
114189606Ssam	taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq");
115189606Ssam
116189606Ssam	TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc);
117189606Ssam
118189606Ssam	return 0;
119189606Ssam}
120189606Ssam
121189606Ssamstatic int
122189606Ssamcfi_disk_detach(device_t dev)
123189606Ssam{
124189606Ssam	struct cfi_disk_softc *sc = device_get_softc(dev);
125189606Ssam
126189606Ssam	if (sc->flags & CFI_DISK_OPEN)
127189606Ssam		return EBUSY;
128189606Ssam	taskqueue_free(sc->tq);
129189606Ssam	/* XXX drain bioq */
130189606Ssam	disk_destroy(sc->disk);
131189606Ssam	mtx_destroy(&sc->qlock);
132189606Ssam	return 0;
133189606Ssam}
134189606Ssam
135189606Ssamstatic int
136189606Ssamcfi_disk_open(struct disk *dp)
137189606Ssam{
138189606Ssam	struct cfi_disk_softc *sc = dp->d_drv1;
139189606Ssam
140189606Ssam	/* XXX no interlock with /dev/cfi */
141189606Ssam	sc->flags |= CFI_DISK_OPEN;
142189606Ssam	return 0;
143189606Ssam}
144189606Ssam
145189606Ssamstatic int
146189606Ssamcfi_disk_close(struct disk *dp)
147189606Ssam{
148189606Ssam	struct cfi_disk_softc *sc = dp->d_drv1;
149189606Ssam
150189606Ssam	sc->flags &= ~CFI_DISK_OPEN;
151189606Ssam	return 0;
152189606Ssam}
153189606Ssam
154189606Ssamstatic void
155189606Ssamcfi_disk_read(struct cfi_softc *sc, struct bio *bp)
156189606Ssam{
157189606Ssam	long resid;
158189606Ssam
159189606Ssam	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
160189606Ssam	    ("sc_width %d", sc->sc_width));
161189606Ssam
162189606Ssam	if (sc->sc_writing) {
163189606Ssam		bp->bio_error = cfi_block_finish(sc);
164189606Ssam		if (bp->bio_error) {
165189606Ssam			bp->bio_flags |= BIO_ERROR;
166189606Ssam			goto done;
167189606Ssam		}
168189606Ssam	}
169189606Ssam	if (bp->bio_offset > sc->sc_size) {
170189606Ssam		bp->bio_flags |= BIO_ERROR;
171189606Ssam		bp->bio_error = EIO;
172189606Ssam		goto done;
173189606Ssam	}
174189606Ssam	resid = bp->bio_bcount;
175189606Ssam	if (sc->sc_width == 1) {
176189606Ssam		uint8_t *dp = (uint8_t *)bp->bio_data;
177189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
178189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
179189606Ssam			bp->bio_offset += 1, resid -= 1;
180189606Ssam		}
181189606Ssam	} else if (sc->sc_width == 2) {
182189606Ssam		uint16_t *dp = (uint16_t *)bp->bio_data;
183189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
184189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
185189606Ssam			bp->bio_offset += 2, resid -= 2;
186189606Ssam		}
187189606Ssam	} else {
188189606Ssam		uint32_t *dp = (uint32_t *)bp->bio_data;
189189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
190189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
191189606Ssam			bp->bio_offset += 4, resid -= 4;
192189606Ssam		}
193189606Ssam	}
194189606Ssam	bp->bio_resid = resid;
195189606Ssamdone:
196189606Ssam	biodone(bp);
197189606Ssam}
198189606Ssam
199189606Ssamstatic void
200189606Ssamcfi_disk_write(struct cfi_softc *sc, struct bio *bp)
201189606Ssam{
202189606Ssam	long resid;
203189606Ssam	u_int top;
204189606Ssam
205189606Ssam	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
206189606Ssam	    ("sc_width %d", sc->sc_width));
207189606Ssam
208189606Ssam	if (bp->bio_offset > sc->sc_size) {
209189606Ssam		bp->bio_flags |= BIO_ERROR;
210189606Ssam		bp->bio_error = EIO;
211189606Ssam		goto done;
212189606Ssam	}
213189606Ssam	resid = bp->bio_bcount;
214189606Ssam	while (resid > 0) {
215189606Ssam		/*
216189606Ssam		 * Finish the current block if we're about to write
217189606Ssam		 * to a different block.
218189606Ssam		 */
219189606Ssam		if (sc->sc_writing) {
220189606Ssam			top = sc->sc_wrofs + sc->sc_wrbufsz;
221189606Ssam			if (bp->bio_offset < sc->sc_wrofs ||
222189606Ssam			    bp->bio_offset >= top)
223189606Ssam				cfi_block_finish(sc);
224189606Ssam		}
225189606Ssam
226189606Ssam		/* Start writing to a (new) block if applicable. */
227189606Ssam		if (!sc->sc_writing) {
228189606Ssam			bp->bio_error = cfi_block_start(sc, bp->bio_offset);
229189606Ssam			if (bp->bio_error) {
230189606Ssam				bp->bio_flags |= BIO_ERROR;
231189606Ssam				goto done;
232189606Ssam			}
233189606Ssam		}
234189606Ssam
235189606Ssam		top = sc->sc_wrofs + sc->sc_wrbufsz;
236189606Ssam		bcopy(bp->bio_data,
237189606Ssam		    sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs,
238189606Ssam		    MIN(top - bp->bio_offset, resid));
239189606Ssam		resid -= MIN(top - bp->bio_offset, resid);
240189606Ssam	}
241189606Ssam	bp->bio_resid = resid;
242189606Ssamdone:
243189606Ssam	biodone(bp);
244189606Ssam}
245189606Ssam
246189606Ssamstatic void
247189606Ssamcfi_io_proc(void *arg, int pending)
248189606Ssam{
249189606Ssam	struct cfi_disk_softc *sc = arg;
250189606Ssam	struct cfi_softc *cfi = sc->parent;
251189606Ssam	struct bio *bp;
252189606Ssam
253189606Ssam	for (;;) {
254189606Ssam		mtx_lock(&sc->qlock);
255189606Ssam		bp = bioq_takefirst(&sc->bioq);
256189606Ssam		mtx_unlock(&sc->qlock);
257189606Ssam		if (bp == NULL)
258189606Ssam			break;
259189606Ssam
260189606Ssam		switch (bp->bio_cmd) {
261189606Ssam		case BIO_READ:
262189606Ssam			cfi_disk_read(cfi, bp);
263189606Ssam			break;
264189606Ssam		case BIO_WRITE:
265189606Ssam			cfi_disk_write(cfi, bp);
266189606Ssam			break;
267189606Ssam		}
268189606Ssam	}
269189606Ssam}
270189606Ssam
271189606Ssamstatic void
272189606Ssamcfi_disk_strategy(struct bio *bp)
273189606Ssam{
274189606Ssam	struct cfi_disk_softc *sc = bp->bio_disk->d_drv1;
275189606Ssam
276189606Ssam	if (sc == NULL)
277189606Ssam		goto invalid;
278189606Ssam	if (bp->bio_bcount == 0) {
279189606Ssam		bp->bio_resid = bp->bio_bcount;
280189606Ssam		biodone(bp);
281189606Ssam		return;
282189606Ssam	}
283189606Ssam	switch (bp->bio_cmd) {
284189606Ssam	case BIO_READ:
285189606Ssam	case BIO_WRITE:
286189606Ssam		mtx_lock(&sc->qlock);
287189606Ssam		/* no value in sorting requests? */
288189606Ssam		bioq_insert_tail(&sc->bioq, bp);
289189606Ssam		mtx_unlock(&sc->qlock);
290189606Ssam		taskqueue_enqueue(sc->tq, &sc->iotask);
291189606Ssam		return;
292189606Ssam	}
293189606Ssam	/* fall thru... */
294189606Ssaminvalid:
295189606Ssam	bp->bio_flags |= BIO_ERROR;
296189606Ssam	bp->bio_error = EINVAL;
297189606Ssam	biodone(bp);
298189606Ssam}
299189606Ssam
300189606Ssamstatic int
301189606Ssamcfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag,
302189606Ssam	struct thread *td)
303189606Ssam{
304189606Ssam	return EINVAL;
305189606Ssam}
306189606Ssam
307189606Ssamstatic device_method_t cfi_disk_methods[] = {
308189606Ssam	DEVMETHOD(device_probe,		cfi_disk_probe),
309189606Ssam	DEVMETHOD(device_attach,	cfi_disk_attach),
310189606Ssam	DEVMETHOD(device_detach,	cfi_disk_detach),
311189606Ssam
312189606Ssam	{ 0, 0 }
313189606Ssam};
314189606Ssamstatic driver_t cfi_disk_driver = {
315189606Ssam	"cfid",
316189606Ssam	cfi_disk_methods,
317189606Ssam	sizeof(struct cfi_disk_softc),
318189606Ssam};
319189606SsamDRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL);
320