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$");
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 */
102189654Ssam	if (sc->parent->sc_regions) {
103189654Ssam		/*
104189654Ssam		 * Multiple regions, use the last one.  This is a
105189654Ssam		 * total hack as it's (presently) used only by
106189654Ssam		 * geom_redboot to locate the FIS directory which
107189654Ssam		 * lies at the start of the last erase region.
108189654Ssam		 */
109189654Ssam		sc->disk->d_stripesize =
110189654Ssam		    sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz;
111189654Ssam	} else
112189606Ssam		sc->disk->d_stripesize = sc->disk->d_mediasize;
113189606Ssam	sc->disk->d_drv1 = sc;
114189606Ssam	disk_create(sc->disk, DISK_VERSION);
115189606Ssam
116189606Ssam	mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF);
117189606Ssam	bioq_init(&sc->bioq);
118189606Ssam
119189606Ssam	sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT,
120189606Ssam		taskqueue_thread_enqueue, &sc->tq);
121189606Ssam	taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq");
122189606Ssam
123189606Ssam	TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc);
124189606Ssam
125189606Ssam	return 0;
126189606Ssam}
127189606Ssam
128189606Ssamstatic int
129189606Ssamcfi_disk_detach(device_t dev)
130189606Ssam{
131189606Ssam	struct cfi_disk_softc *sc = device_get_softc(dev);
132189606Ssam
133189606Ssam	if (sc->flags & CFI_DISK_OPEN)
134189606Ssam		return EBUSY;
135189606Ssam	taskqueue_free(sc->tq);
136189606Ssam	/* XXX drain bioq */
137189606Ssam	disk_destroy(sc->disk);
138189606Ssam	mtx_destroy(&sc->qlock);
139189606Ssam	return 0;
140189606Ssam}
141189606Ssam
142189606Ssamstatic int
143189606Ssamcfi_disk_open(struct disk *dp)
144189606Ssam{
145189606Ssam	struct cfi_disk_softc *sc = dp->d_drv1;
146189606Ssam
147189606Ssam	/* XXX no interlock with /dev/cfi */
148189606Ssam	sc->flags |= CFI_DISK_OPEN;
149189606Ssam	return 0;
150189606Ssam}
151189606Ssam
152189606Ssamstatic int
153189606Ssamcfi_disk_close(struct disk *dp)
154189606Ssam{
155189606Ssam	struct cfi_disk_softc *sc = dp->d_drv1;
156189606Ssam
157189606Ssam	sc->flags &= ~CFI_DISK_OPEN;
158189606Ssam	return 0;
159189606Ssam}
160189606Ssam
161189606Ssamstatic void
162189606Ssamcfi_disk_read(struct cfi_softc *sc, struct bio *bp)
163189606Ssam{
164189606Ssam	long resid;
165189606Ssam
166189606Ssam	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
167189606Ssam	    ("sc_width %d", sc->sc_width));
168189606Ssam
169189606Ssam	if (sc->sc_writing) {
170189606Ssam		bp->bio_error = cfi_block_finish(sc);
171189606Ssam		if (bp->bio_error) {
172189606Ssam			bp->bio_flags |= BIO_ERROR;
173189606Ssam			goto done;
174189606Ssam		}
175189606Ssam	}
176189606Ssam	if (bp->bio_offset > sc->sc_size) {
177189606Ssam		bp->bio_flags |= BIO_ERROR;
178189606Ssam		bp->bio_error = EIO;
179189606Ssam		goto done;
180189606Ssam	}
181189606Ssam	resid = bp->bio_bcount;
182189606Ssam	if (sc->sc_width == 1) {
183189606Ssam		uint8_t *dp = (uint8_t *)bp->bio_data;
184189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
185189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
186189606Ssam			bp->bio_offset += 1, resid -= 1;
187189606Ssam		}
188189606Ssam	} else if (sc->sc_width == 2) {
189189606Ssam		uint16_t *dp = (uint16_t *)bp->bio_data;
190189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
191189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
192189606Ssam			bp->bio_offset += 2, resid -= 2;
193189606Ssam		}
194189606Ssam	} else {
195189606Ssam		uint32_t *dp = (uint32_t *)bp->bio_data;
196189606Ssam		while (resid > 0 && bp->bio_offset < sc->sc_size) {
197189606Ssam			*dp++ = cfi_read(sc, bp->bio_offset);
198189606Ssam			bp->bio_offset += 4, resid -= 4;
199189606Ssam		}
200189606Ssam	}
201189606Ssam	bp->bio_resid = resid;
202189606Ssamdone:
203189606Ssam	biodone(bp);
204189606Ssam}
205189606Ssam
206189606Ssamstatic void
207189606Ssamcfi_disk_write(struct cfi_softc *sc, struct bio *bp)
208189606Ssam{
209189606Ssam	long resid;
210189606Ssam	u_int top;
211189606Ssam
212189606Ssam	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
213189606Ssam	    ("sc_width %d", sc->sc_width));
214189606Ssam
215189606Ssam	if (bp->bio_offset > sc->sc_size) {
216189606Ssam		bp->bio_flags |= BIO_ERROR;
217189606Ssam		bp->bio_error = EIO;
218189606Ssam		goto done;
219189606Ssam	}
220189606Ssam	resid = bp->bio_bcount;
221189606Ssam	while (resid > 0) {
222189606Ssam		/*
223189606Ssam		 * Finish the current block if we're about to write
224189606Ssam		 * to a different block.
225189606Ssam		 */
226189606Ssam		if (sc->sc_writing) {
227189606Ssam			top = sc->sc_wrofs + sc->sc_wrbufsz;
228189606Ssam			if (bp->bio_offset < sc->sc_wrofs ||
229189606Ssam			    bp->bio_offset >= top)
230189606Ssam				cfi_block_finish(sc);
231189606Ssam		}
232189606Ssam
233189606Ssam		/* Start writing to a (new) block if applicable. */
234189606Ssam		if (!sc->sc_writing) {
235189606Ssam			bp->bio_error = cfi_block_start(sc, bp->bio_offset);
236189606Ssam			if (bp->bio_error) {
237189606Ssam				bp->bio_flags |= BIO_ERROR;
238189606Ssam				goto done;
239189606Ssam			}
240189606Ssam		}
241189606Ssam
242189606Ssam		top = sc->sc_wrofs + sc->sc_wrbufsz;
243189606Ssam		bcopy(bp->bio_data,
244189606Ssam		    sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs,
245189606Ssam		    MIN(top - bp->bio_offset, resid));
246189606Ssam		resid -= MIN(top - bp->bio_offset, resid);
247189606Ssam	}
248189606Ssam	bp->bio_resid = resid;
249189606Ssamdone:
250189606Ssam	biodone(bp);
251189606Ssam}
252189606Ssam
253189606Ssamstatic void
254189606Ssamcfi_io_proc(void *arg, int pending)
255189606Ssam{
256189606Ssam	struct cfi_disk_softc *sc = arg;
257189606Ssam	struct cfi_softc *cfi = sc->parent;
258189606Ssam	struct bio *bp;
259189606Ssam
260189606Ssam	for (;;) {
261189606Ssam		mtx_lock(&sc->qlock);
262189606Ssam		bp = bioq_takefirst(&sc->bioq);
263189606Ssam		mtx_unlock(&sc->qlock);
264189606Ssam		if (bp == NULL)
265189606Ssam			break;
266189606Ssam
267189606Ssam		switch (bp->bio_cmd) {
268189606Ssam		case BIO_READ:
269189606Ssam			cfi_disk_read(cfi, bp);
270189606Ssam			break;
271189606Ssam		case BIO_WRITE:
272189606Ssam			cfi_disk_write(cfi, bp);
273189606Ssam			break;
274189606Ssam		}
275189606Ssam	}
276189606Ssam}
277189606Ssam
278189606Ssamstatic void
279189606Ssamcfi_disk_strategy(struct bio *bp)
280189606Ssam{
281189606Ssam	struct cfi_disk_softc *sc = bp->bio_disk->d_drv1;
282189606Ssam
283189606Ssam	if (sc == NULL)
284189606Ssam		goto invalid;
285189606Ssam	if (bp->bio_bcount == 0) {
286189606Ssam		bp->bio_resid = bp->bio_bcount;
287189606Ssam		biodone(bp);
288189606Ssam		return;
289189606Ssam	}
290189606Ssam	switch (bp->bio_cmd) {
291189606Ssam	case BIO_READ:
292189606Ssam	case BIO_WRITE:
293189606Ssam		mtx_lock(&sc->qlock);
294189606Ssam		/* no value in sorting requests? */
295189606Ssam		bioq_insert_tail(&sc->bioq, bp);
296189606Ssam		mtx_unlock(&sc->qlock);
297189606Ssam		taskqueue_enqueue(sc->tq, &sc->iotask);
298189606Ssam		return;
299189606Ssam	}
300189606Ssam	/* fall thru... */
301189606Ssaminvalid:
302189606Ssam	bp->bio_flags |= BIO_ERROR;
303189606Ssam	bp->bio_error = EINVAL;
304189606Ssam	biodone(bp);
305189606Ssam}
306189606Ssam
307189606Ssamstatic int
308189606Ssamcfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag,
309189606Ssam	struct thread *td)
310189606Ssam{
311189606Ssam	return EINVAL;
312189606Ssam}
313189606Ssam
314189606Ssamstatic device_method_t cfi_disk_methods[] = {
315189606Ssam	DEVMETHOD(device_probe,		cfi_disk_probe),
316189606Ssam	DEVMETHOD(device_attach,	cfi_disk_attach),
317189606Ssam	DEVMETHOD(device_detach,	cfi_disk_detach),
318189606Ssam
319189606Ssam	{ 0, 0 }
320189606Ssam};
321189606Ssamstatic driver_t cfi_disk_driver = {
322189606Ssam	"cfid",
323189606Ssam	cfi_disk_methods,
324189606Ssam	sizeof(struct cfi_disk_softc),
325189606Ssam};
326189606SsamDRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL);
327