1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2009 Sam Leffler, Errno Consulting
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 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <sys/cdefs.h>
35__FBSDID("$FreeBSD$");
36
37#include <sys/param.h>
38#include <sys/systm.h>
39#include <sys/bio.h>
40#include <sys/bus.h>
41#include <sys/conf.h>
42#include <sys/kernel.h>
43#include <sys/malloc.h>
44#include <sys/lock.h>
45#include <sys/mutex.h>
46#include <sys/module.h>
47#include <sys/rman.h>
48#include <sys/sysctl.h>
49#include <sys/taskqueue.h>
50
51#include <machine/bus.h>
52
53#include <dev/cfi/cfi_var.h>
54
55#include <geom/geom.h>
56#include <geom/geom_disk.h>
57
58struct cfi_disk_softc {
59	struct cfi_softc *parent;
60	struct disk	*disk;
61	int		flags;
62#define	CFI_DISK_OPEN	0x0001
63	struct bio_queue_head bioq;	/* bio queue */
64	struct mtx	qlock;		/* bioq lock */
65	struct taskqueue *tq;		/* private task queue for i/o request */
66	struct task	iotask;		/* i/o processing */
67};
68
69#define	CFI_DISK_SECSIZE	512
70#define	CFI_DISK_MAXIOSIZE	65536
71
72static int cfi_disk_detach(device_t);
73static int cfi_disk_open(struct disk *);
74static int cfi_disk_close(struct disk *);
75static void cfi_io_proc(void *, int);
76static int cfi_disk_getattr(struct bio *);
77static void cfi_disk_strategy(struct bio *);
78static int cfi_disk_ioctl(struct disk *, u_long, void *, int, struct thread *);
79
80static int
81cfi_disk_probe(device_t dev)
82{
83	return 0;
84}
85
86static int
87cfi_disk_attach(device_t dev)
88{
89	struct cfi_disk_softc *sc = device_get_softc(dev);
90
91	sc->parent = device_get_softc(device_get_parent(dev));
92	/* validate interface width; assumed by other code */
93	if (sc->parent->sc_width != 1 &&
94	    sc->parent->sc_width != 2 &&
95	    sc->parent->sc_width != 4)
96		return EINVAL;
97
98	sc->disk = disk_alloc();
99	if (sc->disk == NULL)
100		return ENOMEM;
101	sc->disk->d_name = "cfid";
102	sc->disk->d_unit = device_get_unit(dev);
103	sc->disk->d_open = cfi_disk_open;
104	sc->disk->d_close = cfi_disk_close;
105	sc->disk->d_strategy = cfi_disk_strategy;
106	sc->disk->d_ioctl = cfi_disk_ioctl;
107	sc->disk->d_dump = NULL;		/* NB: no dumps */
108	sc->disk->d_getattr = cfi_disk_getattr;
109	sc->disk->d_sectorsize = CFI_DISK_SECSIZE;
110	sc->disk->d_mediasize = sc->parent->sc_size;
111	sc->disk->d_maxsize = CFI_DISK_MAXIOSIZE;
112	/* NB: use stripesize to hold the erase/region size */
113	if (sc->parent->sc_regions) {
114		/*
115		 * Multiple regions, use the last one.  This is a
116		 * total hack as it's (presently) used only by
117		 * geom_redboot to locate the FIS directory which
118		 * lies at the start of the last erase region.
119		 */
120		sc->disk->d_stripesize =
121		    sc->parent->sc_region[sc->parent->sc_regions-1].r_blksz;
122	} else
123		sc->disk->d_stripesize = sc->disk->d_mediasize;
124	sc->disk->d_drv1 = sc;
125	disk_create(sc->disk, DISK_VERSION);
126
127	mtx_init(&sc->qlock, "CFID I/O lock", NULL, MTX_DEF);
128	bioq_init(&sc->bioq);
129
130	sc->tq = taskqueue_create("cfid_taskq", M_NOWAIT,
131		taskqueue_thread_enqueue, &sc->tq);
132	taskqueue_start_threads(&sc->tq, 1, PI_DISK, "cfid taskq");
133
134	TASK_INIT(&sc->iotask, 0, cfi_io_proc, sc);
135
136	return 0;
137}
138
139static int
140cfi_disk_detach(device_t dev)
141{
142	struct cfi_disk_softc *sc = device_get_softc(dev);
143
144	if (sc->flags & CFI_DISK_OPEN)
145		return EBUSY;
146	taskqueue_free(sc->tq);
147	/* XXX drain bioq */
148	disk_destroy(sc->disk);
149	mtx_destroy(&sc->qlock);
150	return 0;
151}
152
153static int
154cfi_disk_open(struct disk *dp)
155{
156	struct cfi_disk_softc *sc = dp->d_drv1;
157
158	/* XXX no interlock with /dev/cfi */
159	sc->flags |= CFI_DISK_OPEN;
160	return 0;
161}
162
163static int
164cfi_disk_close(struct disk *dp)
165{
166	struct cfi_disk_softc *sc = dp->d_drv1;
167
168	sc->flags &= ~CFI_DISK_OPEN;
169	return 0;
170}
171
172static void
173cfi_disk_read(struct cfi_softc *sc, struct bio *bp)
174{
175	long resid;
176
177	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
178	    ("sc_width %d", sc->sc_width));
179
180	if (sc->sc_writing) {
181		bp->bio_error = cfi_block_finish(sc);
182		if (bp->bio_error) {
183			bp->bio_flags |= BIO_ERROR;
184			goto done;
185		}
186	}
187	if (bp->bio_offset > sc->sc_size) {
188		bp->bio_flags |= BIO_ERROR;
189		bp->bio_error = EIO;
190		goto done;
191	}
192	resid = bp->bio_bcount;
193	if (sc->sc_width == 1) {
194		uint8_t *dp = (uint8_t *)bp->bio_data;
195		while (resid > 0 && bp->bio_offset < sc->sc_size) {
196			*dp++ = cfi_read_raw(sc, bp->bio_offset);
197			bp->bio_offset += 1, resid -= 1;
198		}
199	} else if (sc->sc_width == 2) {
200		uint16_t *dp = (uint16_t *)bp->bio_data;
201		while (resid > 0 && bp->bio_offset < sc->sc_size) {
202			*dp++ = cfi_read_raw(sc, bp->bio_offset);
203			bp->bio_offset += 2, resid -= 2;
204		}
205	} else {
206		uint32_t *dp = (uint32_t *)bp->bio_data;
207		while (resid > 0 && bp->bio_offset < sc->sc_size) {
208			*dp++ = cfi_read_raw(sc, bp->bio_offset);
209			bp->bio_offset += 4, resid -= 4;
210		}
211	}
212	bp->bio_resid = resid;
213done:
214	biodone(bp);
215}
216
217static void
218cfi_disk_write(struct cfi_softc *sc, struct bio *bp)
219{
220	long resid;
221	u_int top;
222
223	KASSERT(sc->sc_width == 1 || sc->sc_width == 2 || sc->sc_width == 4,
224	    ("sc_width %d", sc->sc_width));
225
226	if (bp->bio_offset > sc->sc_size) {
227		bp->bio_flags |= BIO_ERROR;
228		bp->bio_error = EIO;
229		goto done;
230	}
231	resid = bp->bio_bcount;
232	while (resid > 0) {
233		/*
234		 * Finish the current block if we're about to write
235		 * to a different block.
236		 */
237		if (sc->sc_writing) {
238			top = sc->sc_wrofs + sc->sc_wrbufsz;
239			if (bp->bio_offset < sc->sc_wrofs ||
240			    bp->bio_offset >= top)
241				cfi_block_finish(sc);
242		}
243
244		/* Start writing to a (new) block if applicable. */
245		if (!sc->sc_writing) {
246			bp->bio_error = cfi_block_start(sc, bp->bio_offset);
247			if (bp->bio_error) {
248				bp->bio_flags |= BIO_ERROR;
249				goto done;
250			}
251		}
252
253		top = sc->sc_wrofs + sc->sc_wrbufsz;
254		bcopy(bp->bio_data,
255		    sc->sc_wrbuf + bp->bio_offset - sc->sc_wrofs,
256		    MIN(top - bp->bio_offset, resid));
257		resid -= MIN(top - bp->bio_offset, resid);
258	}
259	bp->bio_resid = resid;
260done:
261	biodone(bp);
262}
263
264static void
265cfi_io_proc(void *arg, int pending)
266{
267	struct cfi_disk_softc *sc = arg;
268	struct cfi_softc *cfi = sc->parent;
269	struct bio *bp;
270
271	for (;;) {
272		mtx_lock(&sc->qlock);
273		bp = bioq_takefirst(&sc->bioq);
274		mtx_unlock(&sc->qlock);
275		if (bp == NULL)
276			break;
277
278		switch (bp->bio_cmd) {
279		case BIO_READ:
280			cfi_disk_read(cfi, bp);
281			break;
282		case BIO_WRITE:
283			cfi_disk_write(cfi, bp);
284			break;
285		}
286	}
287}
288
289static int
290cfi_disk_getattr(struct bio *bp)
291{
292	struct cfi_disk_softc *dsc;
293	struct cfi_softc *sc;
294	device_t dev;
295
296	if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL)
297		return (ENXIO);
298
299	dsc = bp->bio_disk->d_drv1;
300	sc = dsc->parent;
301	dev = sc->sc_dev;
302
303	if (strcmp(bp->bio_attribute, "CFI::device") == 0) {
304		if (bp->bio_length != sizeof(dev))
305			return (EFAULT);
306		bcopy(&dev, bp->bio_data, sizeof(dev));
307	} else
308		return (-1);
309	return (0);
310}
311
312
313static void
314cfi_disk_strategy(struct bio *bp)
315{
316	struct cfi_disk_softc *sc = bp->bio_disk->d_drv1;
317
318	if (sc == NULL)
319		goto invalid;
320	if (bp->bio_bcount == 0) {
321		bp->bio_resid = bp->bio_bcount;
322		biodone(bp);
323		return;
324	}
325	switch (bp->bio_cmd) {
326	case BIO_READ:
327	case BIO_WRITE:
328		mtx_lock(&sc->qlock);
329		/* no value in sorting requests? */
330		bioq_insert_tail(&sc->bioq, bp);
331		mtx_unlock(&sc->qlock);
332		taskqueue_enqueue(sc->tq, &sc->iotask);
333		return;
334	}
335	/* fall thru... */
336invalid:
337	bp->bio_flags |= BIO_ERROR;
338	bp->bio_error = EINVAL;
339	biodone(bp);
340}
341
342static int
343cfi_disk_ioctl(struct disk *dp, u_long cmd, void *data, int fflag,
344	struct thread *td)
345{
346	return EINVAL;
347}
348
349static device_method_t cfi_disk_methods[] = {
350	DEVMETHOD(device_probe,		cfi_disk_probe),
351	DEVMETHOD(device_attach,	cfi_disk_attach),
352	DEVMETHOD(device_detach,	cfi_disk_detach),
353
354	{ 0, 0 }
355};
356static driver_t cfi_disk_driver = {
357	"cfid",
358	cfi_disk_methods,
359	sizeof(struct cfi_disk_softc),
360};
361DRIVER_MODULE(cfid, cfi, cfi_disk_driver, cfi_diskclass, 0, NULL);
362