Edit

IABSD.fr/src/sys/dev/flash.c

Branch :

  • Show log

    Commit

  • Author : deraadt
    Date : 2017-05-04 22:47:27
    Hash : d40269af
    Message : Also pass the blk offset to disk_unbusy(), so that it can pass it to the random subsystem as entropy. This value is pretty much unknown, and anyways our entropy input ring does not saturate from knowns. ok mikeb djm

  • sys/dev/flash.c
  • /*	$OpenBSD: flash.c,v 1.32 2017/05/04 22:47:27 deraadt Exp $	*/
    
    /*
     * Copyright (c) 2005 Uwe Stuehler <uwe@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/buf.h>
    #include <sys/conf.h>
    #include <sys/device.h>
    #include <sys/disk.h>
    #include <sys/disklabel.h>
    #include <sys/dkio.h>
    #include <sys/kernel.h>
    #include <sys/stat.h>
    #include <sys/systm.h>
    
    #include <dev/flashvar.h>
    
    #include <ufs/ffs/fs.h>		/* XXX */
    
    /* Samsung command set */
    #define SAMSUNG_CMD_PTRLO	0x00
    #define SAMSUNG_CMD_PTRHI	0x01
    #define SAMSUNG_CMD_PTROOB	0x50
    #define SAMSUNG_CMD_READ	0x30
    #define SAMSUNG_CMD_SEQIN	0x80
    #define SAMSUNG_CMD_WRITE	0x10
    #define SAMSUNG_CMD_ERASE0	0x60
    #define SAMSUNG_CMD_ERASE1	0xd0
    #define SAMSUNG_CMD_STATUS	0x70
    #define  STATUS_FAIL		(1<<0)
    #define  STATUS_READY		(1<<6)
    #define  STATUS_NWP		(1<<7)
    #define SAMSUNG_CMD_READID	0x90
    #define SAMSUNG_CMD_RESET	0xff
    
    int	 flash_wait_ready(struct flash_softc *);
    int	 flash_wait_complete(struct flash_softc *);
    
    /* XXX: these should go elsewhere */
    cdev_decl(flash);
    bdev_decl(flash);
    
    #define flashlookup(unit) \
    	(struct flash_softc *)device_lookup(&flash_cd, (unit))
    
    void	flashminphys(struct buf *);
    void	flashstart(struct flash_softc *);
    void	_flashstart(struct flash_softc *, struct buf *);
    void	flashdone(void *);
    
    int	flashsafestrategy(struct flash_softc *, struct buf *);
    void	flashgetdefaultlabel(dev_t, struct flash_softc *,
        struct disklabel *);
    int	flashgetdisklabel(dev_t, struct flash_softc *, struct disklabel *, int);
    
    /*
     * Driver attachment glue
     */
    
    struct flashvendor {
    	u_int8_t	 vendor;
    	const char	*name;
    };
    
    static const struct flashvendor flashvendors[] = {
    	{ FLASH_VENDOR_SAMSUNG, "Samsung" }
    };
    #define	FLASH_NVENDORS (sizeof(flashvendors) / sizeof(flashvendors[0]))
    
    static const struct flashdev flashdevs[] = {
    	{ FLASH_DEVICE_SAMSUNG_K9F2808U0C, "K9F2808U0C 16Mx8 3.3V",
    	   512, 16, 32, 32768 },
    	{ FLASH_DEVICE_SAMSUNG_K9F1G08U0A, "K9F1G08U0A 128Mx8 3.3V",
    	  2048, 64, 64, 65536 },
    };
    #define	FLASH_NDEVS (sizeof(flashdevs) / sizeof(flashdevs[0]))
    
    struct cfdriver flash_cd = {
    	NULL, "flash", DV_DISK
    };
    
    void
    flashattach(struct flash_softc *sc, struct flash_ctl_tag *tag,
        void *cookie)
    {
    	u_int8_t vendor, device;
    	u_int16_t id;
    	int i;
    
    	sc->sc_tag = tag;
    	sc->sc_cookie = cookie;
    
    	if (sc->sc_maxwaitready <= 0)
    		sc->sc_maxwaitready = 1000;      /* 1ms */
    	if (sc->sc_maxwaitcomplete <= 0)
    		sc->sc_maxwaitcomplete = 200000; /* 200ms */
    
    	flash_chip_enable(sc);
    
    	/* Identify the flash device. */
    	if (flash_chip_identify(sc, &vendor, &device) != 0) {
    		printf(": identification failed\n");
    		flash_chip_disable(sc);
    		return;
    	}
    	id = (vendor << 8) | device;
    
    	/* Look up device characteristics, abort if not recognized. */
    	for (i = 0; i < FLASH_NVENDORS; i++) {
    		if (flashvendors[i].vendor == vendor) {
    			printf(": %s", flashvendors[i].name);
    			break;
    		}
    	}
    	if (i == FLASH_NVENDORS)
    		printf(": vendor 0x%02x", vendor);
    	for (i = 0; i < FLASH_NDEVS; i++) {
    		if (flashdevs[i].id == id) {
    			printf(" %s\n", flashdevs[i].longname);
    			break;
    		}
    	}
    	if (i == FLASH_NDEVS) {
    		/* Need to add this device to flashdevs first. */
    		printf(" device 0x%02x\n", device);
    		flash_chip_disable(sc);
    		return;
    	}
    	sc->sc_flashdev = &flashdevs[i];
    
    	/* Check if the device really works or fail early. */
    	if (flash_chip_reset(sc) != 0) {
    		printf("%s: reset failed\n", sc->sc_dev.dv_xname);
    		flash_chip_disable(sc);
    		return;
    	}
    
    	flash_chip_disable(sc);
    
    	/*
    	 * Initialize and attach the disk structure.
    	 */
    	sc->sc_dk.dk_name = sc->sc_dev.dv_xname;
    	bufq_init(&sc->sc_bufq, BUFQ_FIFO);
    	disk_attach(&sc->sc_dev, &sc->sc_dk);
    
    	/* XXX establish shutdown hook to finish any commands. */
    }
    
    int
    flashdetach(struct device *self, int flags)
    {
    	struct flash_softc *sc = (struct flash_softc *)self;
    
    	/* Detach disk. */
    	disk_detach(&sc->sc_dk);
    
    	/* XXX more resources need to be freed here. */
    	return 0;
    }
    
    /*
     * Flash controller and chip functions
     */
    
    u_int8_t
    flash_reg8_read(struct flash_softc *sc, int reg)
    {
    	return sc->sc_tag->reg8_read(sc->sc_cookie, reg);
    }
    
    void
    flash_reg8_read_page(struct flash_softc *sc, caddr_t data, caddr_t oob)
    {
    	int i;
    
    	for (i = 0; i < sc->sc_flashdev->pagesize; i++)
    		data[i] = flash_reg8_read(sc, FLASH_REG_DATA);
    
    	if (oob != NULL)
    		for (i = 0; i < sc->sc_flashdev->oobsize; i++)
    			oob[i] = flash_reg8_read(sc, FLASH_REG_DATA);
    }
    
    void
    flash_reg8_write(struct flash_softc *sc, int reg, u_int8_t value)
    {
    	sc->sc_tag->reg8_write(sc->sc_cookie, reg, value);
    }
    
    void
    flash_reg8_write_page(struct flash_softc *sc, caddr_t data, caddr_t oob)
    {
    	int i;
    
    	for (i = 0; i < sc->sc_flashdev->pagesize; i++)
    		flash_reg8_write(sc, FLASH_REG_DATA, data[i]);
    
    	if (oob != NULL)
    		for (i = 0; i < sc->sc_flashdev->oobsize; i++)
    			flash_reg8_write(sc, FLASH_REG_DATA, oob[i]);
    }
    
    /*
     * Wait for the "Ready/Busy" signal to go high, indicating that the
     * device is ready to accept another command.
     */
    int
    flash_wait_ready(struct flash_softc *sc)
    {
    	int timo = sc->sc_maxwaitready;
    	u_int8_t ready;
    
    	ready = flash_reg8_read(sc, FLASH_REG_READY);
    	while (ready == 0 && timo-- > 0) {
    		delay(1);
    		ready = flash_reg8_read(sc, FLASH_REG_READY);
    	}
    	return (ready == 0 ? EIO : 0);
    }
    
    /*
     * Similar to flash_wait_ready() but looks at IO 6 and IO 0 signals
     * besides R/B to decide whether the last operation was successful.
     */
    int
    flash_wait_complete(struct flash_softc *sc)
    {
    	int timo = sc->sc_maxwaitcomplete;
    	u_int8_t status;
    
    	(void)flash_wait_ready(sc);
    
    	flash_reg8_write(sc, FLASH_REG_CLE, 1);
    	flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_STATUS);
    	flash_reg8_write(sc, FLASH_REG_CLE, 0);
    
    	status = flash_reg8_read(sc, FLASH_REG_DATA);
    	while ((status & STATUS_READY) == 0 && timo-- > 0) {
    		if (flash_reg8_read(sc, FLASH_REG_READY))
    			break;
    		delay(1);
    		status = flash_reg8_read(sc, FLASH_REG_DATA);
    	}
    
    	status = flash_reg8_read(sc, FLASH_REG_DATA);
    	return ((status & STATUS_FAIL) != 0 ? EIO : 0);
    }
    
    void
    flash_chip_enable(struct flash_softc *sc)
    {
    	/* XXX aquire the lock. */
    	flash_reg8_write(sc, FLASH_REG_CE, 1);
    }
    
    void
    flash_chip_disable(struct flash_softc *sc)
    {
    	flash_reg8_write(sc, FLASH_REG_CE, 0);
    	/* XXX release the lock. */
    }
    
    int
    flash_chip_reset(struct flash_softc *sc)
    {
    	flash_reg8_write(sc, FLASH_REG_CLE, 1);
    	flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_RESET);
    	flash_reg8_write(sc, FLASH_REG_CLE, 0);
    
    	return flash_wait_ready(sc);
    }
    
    int
    flash_chip_identify(struct flash_softc *sc, u_int8_t *vendor,
        u_int8_t *device)
    {
    	int error;
    
    	(void)flash_wait_ready(sc);
    
    	flash_reg8_write(sc, FLASH_REG_CLE, 1);
    	flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READID);
    	flash_reg8_write(sc, FLASH_REG_CLE, 0);
    
    	error = flash_wait_ready(sc);
    	if (error == 0) {
    		*vendor = flash_reg8_read(sc, FLASH_REG_DATA);
    		*device = flash_reg8_read(sc, FLASH_REG_DATA);
    	}
    	return error;
    }
    
    int
    flash_chip_erase_block(struct flash_softc *sc, long blkno)
    {
    	long pageno = blkno * sc->sc_flashdev->blkpages;
    	int error;
    
    	(void)flash_wait_ready(sc);
    
    	/* Disable write-protection. */
    	flash_reg8_write(sc, FLASH_REG_WP, 0);
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE0);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_ERASE1);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	error = flash_wait_complete(sc);
    
    	/* Re-enable write-protection. */
    	flash_reg8_write(sc, FLASH_REG_WP, 1);
    
    	return error;
    }
    
    int
    flash_chip_read_block(struct flash_softc *sc, long blkno, caddr_t data)
    {
    	long pageno;
    	long blkend;
    	int error;
    
    	pageno = blkno * sc->sc_flashdev->blkpages;
    	blkend = pageno + sc->sc_flashdev->blkpages;
    
    	while (pageno < blkend) {
    		error = flash_chip_read_page(sc, pageno, data, NULL);
    		if (error != 0)
    			return error;
    		data += sc->sc_flashdev->pagesize;
    		pageno++;
    	}
    	return 0;
    }
    
    int
    flash_chip_read_page(struct flash_softc *sc, long pageno, caddr_t data,
        caddr_t oob)
    {
    	int error;
    
    	(void)flash_wait_ready(sc);
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	if ((error = flash_wait_ready(sc)) != 0)
    		return error;
    
    	/* Support hardware ECC calculation. */
    	if (sc->sc_tag->regx_read_page) {
    		error = sc->sc_tag->regx_read_page(sc->sc_cookie, data,
    		    oob);
    		if (error != 0)
    			return error;
    	} else
    		flash_reg8_read_page(sc, data, oob);
    
    	return 0;
    }
    
    int
    flash_chip_read_oob(struct flash_softc *sc, long pageno, caddr_t oob)
    {
    	u_int8_t *p = (u_int8_t *)oob;
    	int error;
    	int i;
    
    	(void)flash_wait_ready(sc);
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTROOB);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x08);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_READ);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	if ((error = flash_wait_ready(sc)) != 0)
    		return error;
    
    	for (i = 0; i < sc->sc_flashdev->oobsize; i++)
    		p[i] = flash_reg8_read(sc, FLASH_REG_DATA);
    
    	return 0;
    }
    
    int
    flash_chip_write_block(struct flash_softc *sc, long blkno, caddr_t data,
        caddr_t oob)
    {
    	long pageno;
    	long blkend;
    	caddr_t p;
    	int error;
    
    	pageno = blkno * sc->sc_flashdev->blkpages;
    	blkend = pageno + sc->sc_flashdev->blkpages;
    
    	p = data;
    	while (pageno < blkend) {
    		error = flash_chip_write_page(sc, pageno, p, oob);
    		if (error != 0)
    			return error;
    		p += sc->sc_flashdev->pagesize;
    		pageno++;
    	}
    
    	/* Verify the newly written block. */
    	return flash_chip_verify_block(sc, blkno, data, oob);
    }
    
    int
    flash_chip_write_page(struct flash_softc *sc, long pageno, caddr_t data,
        caddr_t oob)
    {
    	int error;
    
    	(void)flash_wait_ready(sc);
    
    	/* Disable write-protection. */
    	flash_reg8_write(sc, FLASH_REG_WP, 0);
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_PTRLO);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_SEQIN);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_COL, 0x00);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_ALE, 1);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno);
    		flash_reg8_write(sc, FLASH_REG_ROW, pageno >> 8);
    		flash_reg8_write(sc, FLASH_REG_ALE, 0);
    		break;
    	}
    
    	/* Support hardware ECC calculation. */
    	if (sc->sc_tag->regx_write_page) {
    		error = sc->sc_tag->regx_write_page(sc->sc_cookie, data,
    		    oob);
    		if (error != 0)
    			return error;
    	} else
    		flash_reg8_write_page(sc, data, oob);
    
    	switch (sc->sc_flashdev->id) {
    	case FLASH_DEVICE_SAMSUNG_K9F2808U0C:
    	case FLASH_DEVICE_SAMSUNG_K9F1G08U0A:
    		flash_reg8_write(sc, FLASH_REG_CLE, 1);
    		flash_reg8_write(sc, FLASH_REG_CMD, SAMSUNG_CMD_WRITE);
    		flash_reg8_write(sc, FLASH_REG_CLE, 0);
    		break;
    	}
    
    	/*
    	 * Wait for the write operation to complete although this can
    	 * take up to 700 us for the K9F1G08U0A flash type.
    	 */
    	error = flash_wait_complete(sc);
    
    	/* Re-enable write-protection. */
    	flash_reg8_write(sc, FLASH_REG_WP, 1);
    
    	return error;
    }
    
    int
    flash_chip_verify_block(struct flash_softc *sc, long blkno, caddr_t data,
        caddr_t oob)
    {
    	long pageno;
    	long blkend;
    	int error;
    
    	pageno = blkno * sc->sc_flashdev->blkpages;
    	blkend = pageno + sc->sc_flashdev->blkpages;
    
    	while (pageno < blkend) {
    		error = flash_chip_verify_page(sc, pageno, data, oob);
    		if (error != 0) {
    			printf("block %ld page %ld verify failed\n",
    			    blkno, pageno);
    			return error;
    		}
    		data += sc->sc_flashdev->pagesize;
    		pageno++;
    	}
    	return 0;
    }
    
    int
    flash_chip_verify_page(struct flash_softc *sc, long pageno, caddr_t data,
        caddr_t oob)
    {
    	static u_char rbuf[FLASH_MAXPAGESIZE];
    	static u_char roob[FLASH_MAXOOBSIZE];
    	int error;
    
    	error = flash_chip_read_page(sc, pageno, rbuf,
    	    oob == NULL ? NULL : roob);
    	if (error != 0)
    		return error;
    
    	if (memcmp((const void *)&rbuf[0], (const void *)data,
    	    sc->sc_flashdev->pagesize) != 0)
    		return EIO;
    
    	if (oob != NULL && memcmp((const void *)&roob[0],
    	    (const void *)oob, sc->sc_flashdev->oobsize) != 0)
    		return EIO;
    
    	return 0;
    }
    
    /*
     * Block device functions
     */
    
    int
    flashopen(dev_t dev, int oflags, int devtype, struct proc *p)
    {
    	struct flash_softc *sc;
    	int error;
    	int part;
    
    	sc = flashlookup(flashunit(dev));
    	if (sc == NULL)
    		return ENXIO;
    
    	if ((error = disk_lock(&sc->sc_dk)) != 0) {
    		device_unref(&sc->sc_dev);
    		return error;
    	}
    
    	/*
    	 * If no partition is open load the partition info if it is
    	 * not already valid.  If partitions are already open, allow
    	 * opens only for the same kind of device.
    	 */
    	if (sc->sc_dk.dk_openmask == 0) {
    		if ((sc->sc_flags & FDK_LOADED) == 0 ||
    		    ((sc->sc_flags & FDK_SAFE) == 0) !=
    		    (flashsafe(dev) == 0)) {
    			sc->sc_flags &= ~FDK_SAFE;
    			sc->sc_flags |= FDK_LOADED;
    			if (flashsafe(dev))
    				sc->sc_flags |= FDK_SAFE;
    			if ((error = flashgetdisklabel(dev, sc, 
    			    sc->sc_dk.dk_label, 0)) != 0) {
    				disk_unlock(&sc->sc_dk);
    				device_unref(&sc->sc_dev);
    				return error;
    			}
    		}
    	} else if (((sc->sc_flags & FDK_SAFE) == 0) !=
    	    (flashsafe(dev) == 0)) {
    		disk_unlock(&sc->sc_dk);
    		device_unref(&sc->sc_dev);
    		return EBUSY;
    	}
    
    	/* Check that the partition exists. */
    	part = flashpart(dev);
    	if (part != RAW_PART &&
    	    (part >= sc->sc_dk.dk_label->d_npartitions ||
    	    sc->sc_dk.dk_label->d_partitions[part].p_fstype == FS_UNUSED)) {
    		disk_unlock(&sc->sc_dk);
    		device_unref(&sc->sc_dev);
    		return ENXIO;
    	}
    
    	/* Prevent our unit from being deconfigured while open. */
    	switch (devtype) {
    	case S_IFCHR:
    		sc->sc_dk.dk_copenmask |= (1 << part);
    		break;
    	case S_IFBLK:
    		sc->sc_dk.dk_bopenmask |= (1 << part);
    		break;
    	}
    	sc->sc_dk.dk_openmask =
    	    sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
    
    	disk_unlock(&sc->sc_dk);
    	device_unref(&sc->sc_dev);
    	return 0;
    }
    
    int
    flashclose(dev_t dev, int fflag, int devtype, struct proc *p)
    {
    	struct flash_softc *sc;
    	int part;
    
    	sc = flashlookup(flashunit(dev));
    	if (sc == NULL)
    		return ENXIO;
    
    	disk_lock_nointr(&sc->sc_dk);
    
    	/* Close one open partition. */
    	part = flashpart(dev);
    	switch (devtype) {
    	case S_IFCHR:
    		sc->sc_dk.dk_copenmask &= ~(1 << part);
    		break;
    	case S_IFBLK:
    		sc->sc_dk.dk_bopenmask &= ~(1 << part);
    		break;
    	}
    	sc->sc_dk.dk_openmask =
    	    sc->sc_dk.dk_copenmask | sc->sc_dk.dk_bopenmask;
    
    	if (sc->sc_dk.dk_openmask == 0) {
    		/* XXX wait for I/O to complete? */
    	}
    
    	disk_unlock(&sc->sc_dk);
    	device_unref(&sc->sc_dev);
    	return 0;
    }
    
    /*
     * Queue the transfer of one or more flash pages.
     */
    void
    flashstrategy(struct buf *bp)
    {
    	struct flash_softc *sc;
    	int s;
    
    	sc = flashlookup(flashunit(bp->b_dev));
    	if (sc == NULL) {
    		bp->b_error = ENXIO;
    		goto bad;
    	}
    
    	/* If the device has been invalidated, error out. */
    	if ((sc->sc_flags & FDK_LOADED) == 0) {
    		bp->b_error = EIO;
    		goto bad;
    	}
    
    	/* Translate logical block numbers to physical. */
    	if (flashsafe(bp->b_dev) && flashsafestrategy(sc, bp) <= 0) {
    		if (bp->b_flags & B_ERROR)
    			bp->b_resid = bp->b_bcount;
    		goto done;
    	}
    
    	/* Validate the request. */
    	if (bounds_check_with_label(bp, sc->sc_dk.dk_label) == -1)
    		goto done;
    
    	/* Queue the transfer. */
    	bufq_queue(&sc->sc_bufq, bp);
    
    	s = splbio();
    	flashstart(sc);
    	splx(s);
    
    	device_unref(&sc->sc_dev);
    	return;
    
     bad:
    	bp->b_flags |= B_ERROR;
    	bp->b_resid = bp->b_bcount;
     done:
    	s = splbio();
    	biodone(bp);
    	splx(s);
    	if (sc != NULL)
    		device_unref(&sc->sc_dev);
    }
    
    int
    flashioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
    {
    	struct flash_softc *sc;
    	int error = 0;
    
    	sc = flashlookup(flashunit(dev));
    	if (sc == NULL)
    		return ENXIO;
    
    	if ((sc->sc_flags & FDK_LOADED) == 0) {
    		device_unref(&sc->sc_dev);
    		return EIO;
    	}
    
    	switch (cmd) {
    	case DIOCGPDINFO:
    		flashgetdisklabel(dev, sc, (struct disklabel *)data, 1);
    		break;
    
    	case DIOCGDINFO:
    		*(struct disklabel *)data = *sc->sc_dk.dk_label;
    		break;
    
    	default:
    		error = ENOTTY;
    		break;
    	}
    
    	device_unref(&sc->sc_dev);
    	return error;
    }
    
    int
    flashdump(dev_t dev, daddr_t blkno, caddr_t va, size_t size)
    {
    	printf("flashdump\n");
    	return ENODEV;
    }
    
    daddr_t
    flashsize(dev_t dev)
    {
    	printf("flashsize\n");
    	return ENODEV;
    }
    
    void
    flashstart(struct flash_softc *sc)
    {
    	struct buf *bp;
    
    	while ((bp = bufq_dequeue(&sc->sc_bufq)) != NULL) {
    		/* Transfer this buffer now. */
    		_flashstart(sc, bp);
    	}
    }
    
    void
    _flashstart(struct flash_softc *sc, struct buf *bp)
    {
    	struct disklabel *lp;
    	int part;
    	daddr_t offset;
    	long pgno;
    
    	part = flashpart(bp->b_dev);
    
    	lp = sc->sc_dk.dk_label;
    	offset = DL_SECTOBLK(lp, DL_GETPOFFSET(&lp->d_partitions[part])) +
    	    bp->b_blkno;
    	pgno = offset / (sc->sc_flashdev->pagesize / DEV_BSIZE);
    
    	/*
    	 * If the requested page is exactly at the end of flash and it
    	 * is an "unsafe" device, return EOF, else error out.
    	 */
    	if (!flashsafe(bp->b_dev) && pgno == sc->sc_flashdev->capacity) {
    		bp->b_resid = bp->b_bcount;
    		biodone(bp);
    		return;
    	} else if (pgno >= sc->sc_flashdev->capacity) {
    		bp->b_error = EINVAL;
    		bp->b_flags |= B_ERROR;
    		biodone(bp);
    		return;
    	}
    
    	sc->sc_bp = bp;
    
    	/* Instrumentation. */
    	disk_busy(&sc->sc_dk);
    
    	/* XXX this should be done asynchronously. */
    	flash_chip_enable(sc);
    	if ((bp->b_flags & B_READ) != 0)
    		bp->b_error = flash_chip_read_page(sc, pgno, bp->b_data,
    		    NULL);
    	else
    		bp->b_error = flash_chip_write_page(sc, pgno, bp->b_data,
    		    NULL);
    	if (bp->b_error == 0)
    		bp->b_resid = bp->b_bcount - sc->sc_flashdev->pagesize;
    	flash_chip_disable(sc);
    	flashdone(sc);
    }
    
    void
    flashdone(void *v)
    {
    	struct flash_softc *sc = v;
    	struct buf *bp = sc->sc_bp;
    
    	/* Instrumentation. */
    	disk_unbusy(&sc->sc_dk, bp->b_bcount - bp->b_resid,
    	    bp->b_blkno, (bp->b_flags & B_READ) != 0);
    
    	if (bp->b_error != 0)
    		bp->b_flags |= B_ERROR;
    
    	biodone(bp);
    	flashstart(sc);
    }
    
    void
    flashgetdefaultlabel(dev_t dev, struct flash_softc *sc,
        struct disklabel *lp)
    {
    	size_t len;
    
    	bzero(lp, sizeof(struct disklabel));
    
    	lp->d_type = 0;
    	strncpy(lp->d_typename, "NAND flash", sizeof(lp->d_typename));
    
    	/* Use the product name up to the first space. */
    	strncpy(lp->d_packname, sc->sc_flashdev->longname,
    	    sizeof(lp->d_packname));
    	for (len = 0; len < sizeof(lp->d_packname); len++)
    		if (lp->d_packname[len] == ' ') {
    			lp->d_packname[len] = '\0';
    			break;
    		}
    
    	/* Fake the disk geometry. */
    	lp->d_ncylinders = 1;
    	lp->d_ntracks = 16;
    	lp->d_secsize = sc->sc_flashdev->pagesize;
    	lp->d_nsectors = sc->sc_flashdev->capacity / lp->d_ntracks
    	    / lp->d_ncylinders;
    	lp->d_secpercyl = lp->d_ntracks * lp->d_nsectors;
    	DL_SETDSIZE(lp, (u_int64_t)lp->d_ncylinders * lp->d_secpercyl);
    
    	lp->d_version = 1;
    
    	/* XXX these values assume ffs. */
    	lp->d_bbsize = BBSIZE;
    	lp->d_sbsize = SBSIZE;
    
    	/* Wrap it up. */
    	lp->d_magic = DISKMAGIC;
    	lp->d_magic2 = DISKMAGIC;
    	lp->d_checksum = dkcksum(lp);
    }
    
    int
    flashgetdisklabel(dev_t dev, struct flash_softc *sc,
        struct disklabel *lp, int spoofonly)
    {
    	dev_t labeldev;
    
    	flashgetdefaultlabel(dev, sc, lp);
    
    	if (sc->sc_tag->default_disklabel != NULL)
    		sc->sc_tag->default_disklabel(sc->sc_cookie, dev, lp);
    
    	/* Call the generic disklabel extraction routine. */
    	labeldev = flashlabeldev(dev);
    	return readdisklabel(labeldev, flashstrategy, lp, spoofonly);
    }
    
    /*
     * Character device functions
     */
    
    void
    flashminphys(struct buf *bp)
    {
    	struct flash_softc *sc;
    
    	sc = flashlookup(flashunit(bp->b_dev));
    
    	if (bp->b_bcount > sc->sc_flashdev->pagesize)
    		bp->b_bcount = sc->sc_flashdev->pagesize;
    }
    
    int
    flashread(dev_t dev, struct uio *uio, int ioflag)
    {
    	return physio(flashstrategy, dev, B_READ, flashminphys, uio);
    }
    
    int
    flashwrite(dev_t dev, struct uio *uio, int ioflag)
    {
    	return physio(flashstrategy, dev, B_WRITE, flashminphys, uio);
    }
    
    /*
     * Physical access strategy "fixup" routines for transparent bad
     * blocks management, wear-leveling, etc.
     */
    
    /*
     * Call the machine-specific routine if there is any or use just a
     * default strategy for bad blocks management.
     */
    int
    flashsafestrategy(struct flash_softc *sc, struct buf *bp)
    {
    	if (sc->sc_tag->safe_strategy) {
    		return sc->sc_tag->safe_strategy(sc->sc_cookie, bp);
    	}
    
    	/* XXX no default bad blocks management strategy yet */
    	return 1;
    }
    
    void dumppage(u_char *);
    void dumppage(u_char *buf)
    {
    	int i;
    	for (i = 0; i < 512; i++) {
    		if ((i % 16) == 0)
    			printf("%04x: ", i);
    		if ((i % 16) == 8)
    			printf(" ");
    		printf(" %02x", buf[i]);
    		if ((i % 16) == 15)
    			printf("\n");
    	}
    	if ((i % 16) != 0)
    		printf("\n");
    }