1235537Sgber/*-
2235537Sgber * Copyright (C) 2009-2012 Semihalf
3235537Sgber * All rights reserved.
4235537Sgber *
5235537Sgber * Redistribution and use in source and binary forms, with or without
6235537Sgber * modification, are permitted provided that the following conditions
7235537Sgber * are met:
8235537Sgber * 1. Redistributions of source code must retain the above copyright
9235537Sgber *    notice, this list of conditions and the following disclaimer.
10235537Sgber * 2. Redistributions in binary form must reproduce the above copyright
11235537Sgber *    notice, this list of conditions and the following disclaimer in the
12235537Sgber *    documentation and/or other materials provided with the distribution.
13235537Sgber *
14235537Sgber * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15235537Sgber * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16235537Sgber * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17235537Sgber * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18235537Sgber * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19235537Sgber * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20235537Sgber * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21235537Sgber * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22235537Sgber * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23235537Sgber * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24235537Sgber * SUCH DAMAGE.
25235537Sgber */
26235537Sgber
27235537Sgber#include <sys/cdefs.h>
28235537Sgber__FBSDID("$FreeBSD: stable/10/sys/dev/nand/nand_geom.c 313525 2017-02-10 05:35:30Z ngie $");
29235537Sgber
30235537Sgber#include <sys/param.h>
31235537Sgber#include <sys/systm.h>
32235537Sgber#include <sys/conf.h>
33235537Sgber#include <sys/bus.h>
34235537Sgber#include <sys/malloc.h>
35235537Sgber#include <sys/uio.h>
36235537Sgber#include <sys/bio.h>
37235537Sgber#include <geom/geom.h>
38235537Sgber#include <geom/geom_disk.h>
39235537Sgber
40235537Sgber#include <dev/nand/nand.h>
41235537Sgber#include <dev/nand/nandbus.h>
42235537Sgber#include <dev/nand/nand_dev.h>
43235537Sgber#include "nand_if.h"
44235537Sgber#include "nandbus_if.h"
45235537Sgber
46235537Sgber#define	BIO_NAND_STD	((void *)1)
47235537Sgber#define	BIO_NAND_RAW	((void *)2)
48235537Sgber
49235537Sgberstatic disk_ioctl_t nand_ioctl;
50235537Sgberstatic disk_getattr_t nand_getattr;
51235537Sgberstatic disk_strategy_t nand_strategy;
52235537Sgberstatic disk_strategy_t nand_strategy_raw;
53235537Sgber
54235537Sgberstatic int
55235537Sgbernand_read(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len)
56235537Sgber{
57235537Sgber
58235537Sgber	nand_debug(NDBG_GEOM, "Read from chip %d [%p] at %d", chip->num, chip,
59235537Sgber	    offset);
60235537Sgber
61235537Sgber	return (nand_read_pages(chip, offset, buf, len));
62235537Sgber}
63235537Sgber
64235537Sgberstatic int
65235537Sgbernand_write(struct nand_chip *chip, uint32_t offset, void* buf, uint32_t len)
66235537Sgber{
67235537Sgber
68235537Sgber	nand_debug(NDBG_GEOM, "Write to chip %d [%p] at %d", chip->num, chip,
69235537Sgber	    offset);
70235537Sgber
71235537Sgber	return (nand_prog_pages(chip, offset, buf, len));
72235537Sgber}
73235537Sgber
74235537Sgberstatic int
75235537Sgbernand_read_raw(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len)
76235537Sgber{
77235537Sgber	nand_debug(NDBG_GEOM, "Raw read from chip %d [%p] at %d", chip->num,
78235537Sgber	    chip, offset);
79235537Sgber
80235537Sgber	return (nand_read_pages_raw(chip, offset, buf, len));
81235537Sgber}
82235537Sgber
83235537Sgberstatic int
84235537Sgbernand_write_raw(struct nand_chip *chip, uint32_t offset, void *buf, uint32_t len)
85235537Sgber{
86235537Sgber
87235537Sgber	nand_debug(NDBG_GEOM, "Raw write to chip %d [%p] at %d", chip->num,
88235537Sgber	    chip, offset);
89235537Sgber
90235537Sgber	return (nand_prog_pages_raw(chip, offset, buf, len));
91235537Sgber}
92235537Sgber
93235537Sgberstatic void
94235537Sgbernand_strategy(struct bio *bp)
95235537Sgber{
96235537Sgber	struct nand_chip *chip;
97235537Sgber
98235537Sgber	chip = (struct nand_chip *)bp->bio_disk->d_drv1;
99235537Sgber
100235537Sgber	bp->bio_driver1 = BIO_NAND_STD;
101235537Sgber
102235537Sgber	nand_debug(NDBG_GEOM, "Strategy %s on chip %d [%p]",
103235537Sgber	    (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" :
104235537Sgber	    ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE ? "WRITE" :
105235537Sgber	    ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE ? "DELETE" : "UNKNOWN")),
106235537Sgber	    chip->num, chip);
107235537Sgber
108235537Sgber	mtx_lock(&chip->qlock);
109235537Sgber	bioq_insert_tail(&chip->bioq, bp);
110235537Sgber	mtx_unlock(&chip->qlock);
111235537Sgber	taskqueue_enqueue(chip->tq, &chip->iotask);
112235537Sgber}
113235537Sgber
114235537Sgberstatic void
115235537Sgbernand_strategy_raw(struct bio *bp)
116235537Sgber{
117235537Sgber	struct nand_chip *chip;
118235537Sgber
119235537Sgber	chip = (struct nand_chip *)bp->bio_disk->d_drv1;
120235537Sgber
121235537Sgber	/* Inform taskqueue that it's a raw access */
122235537Sgber	bp->bio_driver1 = BIO_NAND_RAW;
123235537Sgber
124235537Sgber	nand_debug(NDBG_GEOM, "Strategy %s on chip %d [%p]",
125235537Sgber	    (bp->bio_cmd & BIO_READ) == BIO_READ ? "READ" :
126235537Sgber	    ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE ? "WRITE" :
127235537Sgber	    ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE ? "DELETE" : "UNKNOWN")),
128235537Sgber	    chip->num, chip);
129235537Sgber
130235537Sgber	mtx_lock(&chip->qlock);
131235537Sgber	bioq_insert_tail(&chip->bioq, bp);
132235537Sgber	mtx_unlock(&chip->qlock);
133235537Sgber	taskqueue_enqueue(chip->tq, &chip->iotask);
134235537Sgber}
135235537Sgber
136235537Sgberstatic int
137235537Sgbernand_oob_access(struct nand_chip *chip, uint32_t page, uint32_t offset,
138235537Sgber    uint32_t len, uint8_t *data, uint8_t write)
139235537Sgber{
140235537Sgber	struct chip_geom *cg;
141235537Sgber	int ret = 0;
142235537Sgber
143235537Sgber	cg = &chip->chip_geom;
144235537Sgber
145235537Sgber	if (!write)
146235537Sgber		ret = nand_read_oob(chip, page, data, cg->oob_size);
147235537Sgber	else
148235537Sgber		ret = nand_prog_oob(chip, page, data, cg->oob_size);
149235537Sgber
150235537Sgber	return (ret);
151235537Sgber}
152235537Sgber
153235537Sgberstatic int
154235537Sgbernand_getattr(struct bio *bp)
155235537Sgber{
156235537Sgber	struct nand_chip *chip;
157235537Sgber	struct chip_geom *cg;
158235537Sgber	device_t dev;
159251651Smav	int val;
160235537Sgber
161235537Sgber	if (bp->bio_disk == NULL || bp->bio_disk->d_drv1 == NULL)
162235537Sgber		return (ENXIO);
163235537Sgber
164235537Sgber	chip = (struct nand_chip *)bp->bio_disk->d_drv1;
165235537Sgber	cg = &(chip->chip_geom);
166235537Sgber
167235537Sgber	dev = device_get_parent(chip->dev);
168235537Sgber	dev = device_get_parent(dev);
169235537Sgber
170251651Smav	if (strcmp(bp->bio_attribute, "NAND::device") == 0) {
171251651Smav		if (bp->bio_length != sizeof(dev))
172251651Smav			return (EFAULT);
173251651Smav		bcopy(&dev, bp->bio_data, sizeof(dev));
174251651Smav	} else {
175251651Smav		if (strcmp(bp->bio_attribute, "NAND::oobsize") == 0)
176251651Smav			val = cg->oob_size;
177251651Smav		else if (strcmp(bp->bio_attribute, "NAND::pagesize") == 0)
178251651Smav			val = cg->page_size;
179251651Smav		else if (strcmp(bp->bio_attribute, "NAND::blocksize") == 0)
180251651Smav			val = cg->block_size;
181251651Smav		else
182251651Smav			return (-1);
183251651Smav		if (bp->bio_length != sizeof(val))
184251651Smav			return (EFAULT);
185251651Smav		bcopy(&val, bp->bio_data, sizeof(val));
186251651Smav	}
187251651Smav	bp->bio_completed = bp->bio_length;
188251651Smav	return (0);
189235537Sgber}
190235537Sgber
191235537Sgberstatic int
192235537Sgbernand_ioctl(struct disk *ndisk, u_long cmd, void *data, int fflag,
193235537Sgber    struct thread *td)
194235537Sgber{
195235537Sgber	struct nand_chip *chip;
196258554Sgber	struct chip_geom  *cg;
197235537Sgber	struct nand_oob_rw *oob_rw = NULL;
198235537Sgber	struct nand_raw_rw *raw_rw = NULL;
199235537Sgber	device_t nandbus;
200258554Sgber	size_t bufsize = 0, len = 0;
201258554Sgber	size_t raw_size;
202258554Sgber	off_t off;
203235537Sgber	uint8_t *buf = NULL;
204235537Sgber	int ret = 0;
205235537Sgber	uint8_t status;
206235537Sgber
207235537Sgber	chip = (struct nand_chip *)ndisk->d_drv1;
208258554Sgber	cg = &chip->chip_geom;
209235537Sgber	nandbus = device_get_parent(chip->dev);
210235537Sgber
211235537Sgber	if ((cmd == NAND_IO_RAW_READ) || (cmd == NAND_IO_RAW_PROG)) {
212235537Sgber		raw_rw = (struct nand_raw_rw *)data;
213258554Sgber		raw_size =  cg->pgs_per_blk * (cg->page_size + cg->oob_size);
214258554Sgber
215258554Sgber		/* Check if len is not bigger than chip size */
216258554Sgber		if (raw_rw->len > raw_size)
217258554Sgber			return (EFBIG);
218258554Sgber
219258554Sgber		/*
220258554Sgber		 * Do not ask for too much memory, in case of large transfers
221258554Sgber		 * read/write in 16-pages chunks
222258554Sgber		 */
223258554Sgber		bufsize = 16 * (cg->page_size + cg->oob_size);
224258554Sgber		if (raw_rw->len < bufsize)
225258554Sgber			bufsize = raw_rw->len;
226258554Sgber
227258554Sgber		buf = malloc(bufsize, M_NAND, M_WAITOK);
228258554Sgber		len = raw_rw->len;
229258554Sgber		off = 0;
230235537Sgber	}
231258554Sgber
232235537Sgber	switch (cmd) {
233235537Sgber	case NAND_IO_ERASE:
234235537Sgber		ret = nand_erase_blocks(chip, ((off_t *)data)[0],
235235537Sgber		    ((off_t *)data)[1]);
236235537Sgber		break;
237235537Sgber
238235537Sgber	case NAND_IO_OOB_READ:
239235537Sgber		oob_rw = (struct nand_oob_rw *)data;
240235537Sgber		ret = nand_oob_access(chip, oob_rw->page, 0,
241235537Sgber		    oob_rw->len, oob_rw->data, 0);
242235537Sgber		break;
243235537Sgber
244235537Sgber	case NAND_IO_OOB_PROG:
245235537Sgber		oob_rw = (struct nand_oob_rw *)data;
246235537Sgber		ret = nand_oob_access(chip, oob_rw->page, 0,
247235537Sgber		    oob_rw->len, oob_rw->data, 1);
248235537Sgber		break;
249235537Sgber
250235537Sgber	case NAND_IO_GET_STATUS:
251235537Sgber		NANDBUS_LOCK(nandbus);
252235537Sgber		ret = NANDBUS_GET_STATUS(nandbus, &status);
253235537Sgber		if (ret == 0)
254235537Sgber			*(uint8_t *)data = status;
255235537Sgber		NANDBUS_UNLOCK(nandbus);
256235537Sgber		break;
257235537Sgber
258235537Sgber	case NAND_IO_RAW_PROG:
259258554Sgber		while (len > 0) {
260258554Sgber			if (len < bufsize)
261258554Sgber				bufsize = len;
262258554Sgber
263258554Sgber			ret = copyin(raw_rw->data + off, buf, bufsize);
264258554Sgber			if (ret)
265258554Sgber				break;
266258554Sgber			ret = nand_prog_pages_raw(chip, raw_rw->off + off, buf,
267258554Sgber			    bufsize);
268258554Sgber			if (ret)
269258554Sgber				break;
270258554Sgber			len -= bufsize;
271258554Sgber			off += bufsize;
272258554Sgber		}
273235537Sgber		break;
274235537Sgber
275235537Sgber	case NAND_IO_RAW_READ:
276258554Sgber		while (len > 0) {
277258554Sgber			if (len < bufsize)
278258554Sgber				bufsize = len;
279258554Sgber
280258554Sgber			ret = nand_read_pages_raw(chip, raw_rw->off + off, buf,
281258554Sgber			    bufsize);
282258554Sgber			if (ret)
283258554Sgber				break;
284258554Sgber
285258554Sgber			ret = copyout(buf, raw_rw->data + off, bufsize);
286258554Sgber			if (ret)
287258554Sgber				break;
288258554Sgber			len -= bufsize;
289258554Sgber			off += bufsize;
290258554Sgber		}
291235537Sgber		break;
292235537Sgber
293235537Sgber	case NAND_IO_GET_CHIP_PARAM:
294235537Sgber		nand_get_chip_param(chip, (struct chip_param_io *)data);
295235537Sgber		break;
296235537Sgber
297235537Sgber	default:
298235537Sgber		printf("Unknown nand_ioctl request \n");
299235537Sgber		ret = EIO;
300235537Sgber	}
301235537Sgber
302235537Sgber	if (buf)
303235537Sgber		free(buf, M_NAND);
304235537Sgber
305235537Sgber	return (ret);
306235537Sgber}
307235537Sgber
308235537Sgberstatic void
309235537Sgbernand_io_proc(void *arg, int pending)
310235537Sgber{
311235537Sgber	struct nand_chip *chip = arg;
312235537Sgber	struct bio *bp;
313235537Sgber	int err = 0;
314235537Sgber
315235537Sgber	for (;;) {
316235537Sgber		mtx_lock(&chip->qlock);
317235537Sgber		bp = bioq_takefirst(&chip->bioq);
318235537Sgber		mtx_unlock(&chip->qlock);
319235537Sgber		if (bp == NULL)
320235537Sgber			break;
321235537Sgber
322235537Sgber		if (bp->bio_driver1 == BIO_NAND_STD) {
323235537Sgber			if ((bp->bio_cmd & BIO_READ) == BIO_READ) {
324235537Sgber				err = nand_read(chip,
325235537Sgber				    bp->bio_offset & 0xffffffff,
326235537Sgber				    bp->bio_data, bp->bio_bcount);
327235537Sgber			} else if ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE) {
328235537Sgber				err = nand_write(chip,
329235537Sgber				    bp->bio_offset & 0xffffffff,
330235537Sgber				    bp->bio_data, bp->bio_bcount);
331235537Sgber			}
332235537Sgber		} else if (bp->bio_driver1 == BIO_NAND_RAW) {
333235537Sgber			if ((bp->bio_cmd & BIO_READ) == BIO_READ) {
334235537Sgber				err = nand_read_raw(chip,
335235537Sgber				    bp->bio_offset & 0xffffffff,
336235537Sgber				    bp->bio_data, bp->bio_bcount);
337235537Sgber			} else if ((bp->bio_cmd & BIO_WRITE) == BIO_WRITE) {
338235537Sgber				err = nand_write_raw(chip,
339235537Sgber				    bp->bio_offset & 0xffffffff,
340235537Sgber				    bp->bio_data, bp->bio_bcount);
341235537Sgber			}
342235537Sgber		} else
343235537Sgber			panic("Unknown access type in bio->bio_driver1\n");
344235537Sgber
345235537Sgber		if ((bp->bio_cmd & BIO_DELETE) == BIO_DELETE) {
346235537Sgber			nand_debug(NDBG_GEOM, "Delete on chip%d offset %lld "
347235537Sgber			    "length %ld\n", chip->num, bp->bio_offset,
348235537Sgber			    bp->bio_bcount);
349235537Sgber			err = nand_erase_blocks(chip,
350235537Sgber			    bp->bio_offset & 0xffffffff,
351235537Sgber			    bp->bio_bcount);
352235537Sgber		}
353235537Sgber
354235537Sgber		if (err == 0 || err == ECC_CORRECTABLE)
355235537Sgber			bp->bio_resid = 0;
356235537Sgber		else {
357235537Sgber			nand_debug(NDBG_GEOM,"nand_[read|write|erase_blocks] "
358235537Sgber			    "error: %d\n", err);
359235537Sgber
360235537Sgber			bp->bio_error = EIO;
361235537Sgber			bp->bio_flags |= BIO_ERROR;
362235537Sgber			bp->bio_resid = bp->bio_bcount;
363235537Sgber		}
364235537Sgber		biodone(bp);
365235537Sgber	}
366235537Sgber}
367235537Sgber
368235537Sgberint
369235537Sgbercreate_geom_disk(struct nand_chip *chip)
370235537Sgber{
371235537Sgber	struct disk *ndisk, *rdisk;
372235537Sgber
373235537Sgber	/* Create the disk device */
374235537Sgber	ndisk = disk_alloc();
375235537Sgber	ndisk->d_strategy = nand_strategy;
376235537Sgber	ndisk->d_ioctl = nand_ioctl;
377235537Sgber	ndisk->d_getattr = nand_getattr;
378235537Sgber	ndisk->d_name = "gnand";
379235537Sgber	ndisk->d_drv1 = chip;
380235537Sgber	ndisk->d_maxsize = chip->chip_geom.block_size;
381235537Sgber	ndisk->d_sectorsize = chip->chip_geom.page_size;
382235537Sgber	ndisk->d_mediasize = chip->chip_geom.chip_size;
383235537Sgber	ndisk->d_unit = chip->num +
384235537Sgber	    10 * device_get_unit(device_get_parent(chip->dev));
385235537Sgber
386235537Sgber	/*
387235537Sgber	 * When using BBT, make two last blocks of device unavailable
388235537Sgber	 * to user (because those are used to store BBT table).
389235537Sgber	 */
390235537Sgber	if (chip->bbt != NULL)
391235537Sgber		ndisk->d_mediasize -= (2 * chip->chip_geom.block_size);
392235537Sgber
393235537Sgber	ndisk->d_flags = DISKFLAG_CANDELETE;
394235537Sgber
395235537Sgber	snprintf(ndisk->d_ident, sizeof(ndisk->d_ident),
396235537Sgber	    "nand: Man:0x%02x Dev:0x%02x", chip->id.man_id, chip->id.dev_id);
397312406Smav	ndisk->d_rotation_rate = DISK_RR_NON_ROTATING;
398235537Sgber
399235537Sgber	disk_create(ndisk, DISK_VERSION);
400235537Sgber
401235537Sgber	/* Create the RAW disk device */
402235537Sgber	rdisk = disk_alloc();
403235537Sgber	rdisk->d_strategy = nand_strategy_raw;
404235537Sgber	rdisk->d_ioctl = nand_ioctl;
405235537Sgber	rdisk->d_getattr = nand_getattr;
406235537Sgber	rdisk->d_name = "gnand.raw";
407235537Sgber	rdisk->d_drv1 = chip;
408235537Sgber	rdisk->d_maxsize = chip->chip_geom.block_size;
409235537Sgber	rdisk->d_sectorsize = chip->chip_geom.page_size;
410235537Sgber	rdisk->d_mediasize = chip->chip_geom.chip_size;
411235537Sgber	rdisk->d_unit = chip->num +
412235537Sgber	    10 * device_get_unit(device_get_parent(chip->dev));
413235537Sgber
414235537Sgber	rdisk->d_flags = DISKFLAG_CANDELETE;
415235537Sgber
416235537Sgber	snprintf(rdisk->d_ident, sizeof(rdisk->d_ident),
417235537Sgber	    "nand_raw: Man:0x%02x Dev:0x%02x", chip->id.man_id,
418235537Sgber	    chip->id.dev_id);
419313525Sngie	rdisk->d_rotation_rate = DISK_RR_NON_ROTATING;
420235537Sgber
421235537Sgber	disk_create(rdisk, DISK_VERSION);
422235537Sgber
423235537Sgber	chip->ndisk = ndisk;
424235537Sgber	chip->rdisk = rdisk;
425235537Sgber
426235537Sgber	mtx_init(&chip->qlock, "NAND I/O lock", NULL, MTX_DEF);
427235537Sgber	bioq_init(&chip->bioq);
428235537Sgber
429235537Sgber	TASK_INIT(&chip->iotask, 0, nand_io_proc, chip);
430235537Sgber	chip->tq = taskqueue_create("nand_taskq", M_WAITOK,
431235537Sgber	    taskqueue_thread_enqueue, &chip->tq);
432235537Sgber	taskqueue_start_threads(&chip->tq, 1, PI_DISK, "nand taskq");
433235537Sgber
434235537Sgber	if (bootverbose)
435235537Sgber		device_printf(chip->dev, "Created gnand%d for chip [0x%0x, "
436235537Sgber		    "0x%0x]\n", ndisk->d_unit, chip->id.man_id,
437235537Sgber		    chip->id.dev_id);
438235537Sgber
439235537Sgber	return (0);
440235537Sgber}
441235537Sgber
442235537Sgbervoid
443235537Sgberdestroy_geom_disk(struct nand_chip *chip)
444235537Sgber{
445235537Sgber	struct bio *bp;
446235537Sgber
447235537Sgber	taskqueue_free(chip->tq);
448235537Sgber	disk_destroy(chip->ndisk);
449235537Sgber	disk_destroy(chip->rdisk);
450235537Sgber
451235537Sgber	mtx_lock(&chip->qlock);
452235537Sgber	for (;;) {
453235537Sgber		bp = bioq_takefirst(&chip->bioq);
454235537Sgber		if (bp == NULL)
455235537Sgber			break;
456235537Sgber		bp->bio_error = EIO;
457235537Sgber		bp->bio_flags |= BIO_ERROR;
458235537Sgber		bp->bio_resid = bp->bio_bcount;
459235537Sgber
460235537Sgber		biodone(bp);
461235537Sgber	}
462235537Sgber	mtx_unlock(&chip->qlock);
463235537Sgber
464235537Sgber	mtx_destroy(&chip->qlock);
465235537Sgber}
466