/* $OpenBSD: flash.c,v 1.2 1997/08/24 12:01:13 pefo Exp $ */ /* * Copyright (c) 1997 Per Fogelstrom * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed under OpenBSD by * Per Fogelstrom for Willowglen Singapore. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern int cputype; void flattach __P((struct device *, struct device *, void *)); int flmatch __P((struct device *, void *, void *)); #define FLUNIT(dev) ((dev & 0xf0) >> 4) #define FLPART(dev) (minor(dev) & 0x0f) #define MAKEFLDEV(maj, unit, part) MAKEDISKDEV(maj, unit, part) #define FLLABELDEV(dev) (MAKEFLDEV(major(dev), FLUNIT(dev), RAW_PART)) /* * Flash cache stuff */ #define MAX_BLKS 64 /* Define size of cache */ #ifdef IS_THERE_A_16BIT_STORE_PROBLEM struct flctag { u_int16_t next; /* Pointer to next on list */ u_int16_t offset; /* Flash memory block offset */ u_int16_t age; /* LSW of time last updated */ u_int16_t stat; /* Status */ }; struct flcache { int magic; u_int16_t free; /* List of free blocks */ u_int16_t nfree; /* Number of free blocks left */ u_int16_t lbusy; /* Last busy block */ u_int16_t stat; /* Cache status */ char *cache; /* Pointer to cache data area */ struct flctag flcblk[MAX_BLKS]; /* Flash cache block tags */ }; #else struct flctag { u_int32_t next; /* Pointer to next on list */ u_int32_t offset; /* Flash memory block offset */ u_int32_t age; /* LSW of time last updated */ u_int32_t stat; /* Status */ }; struct flcache { int magic; u_int32_t free; /* List of free blocks */ u_int32_t nfree; /* Number of free blocks left */ u_int32_t lbusy; /* Last busy block */ u_int32_t stat; /* Cache status */ char *cache; /* Pointer to cache data area */ struct flctag flcblk[MAX_BLKS]; /* Flash cache block tags */ }; #endif #define FLC_MAGIC 0xf1cac3e0 #define BLK_SFREE 0x0001 /* Sanity check blk on free */ #define BLK_SBUSY 0x0002 /* Sanity check blk on busy */ #define BLK_ONFREE 0x0010 /* Block is on free list */ #define BLK_ONBUSY 0x0020 /* Block is on busy list */ #define BLK_FIND 0 /* Serach cmd "find" */ #define BLK_FORCE 1 /* Search cmd "must find" */ #define BLK_LAST 2 /* Search cmd "put last" if found */ /* Flash memory type descriptor */ struct fltype { char *fl_name; int fl_size; int fl_blksz; u_char fl_manf; u_char fl_type; }; struct fltype fllist[] = { { "Samsung KM29N16000", 2097152, 4096, 0xec, 0x64 }, { NULL }, }; struct flsoftc { struct device sc_dev; int sc_prot; size_t sc_size; int sc_present; int sc_opncnt; struct flcache *sc_cache; struct fltype *sc_ftype; struct disklabel sc_label; }; struct cfattach fl_ca = { sizeof(struct flsoftc), flmatch, flattach }; struct cfdriver fl_cd = { NULL, "fl", DV_DISK, 0 /* Yes! We want is as root device */ }; static void flgetdisklabel __P((dev_t, struct flsoftc *)); static void flinitcache __P((struct flsoftc *)); void flstrategy __P((struct buf *)); int flmatch(parent, cf, args) struct device *parent; void *cf; void *args; { struct confargs *ca = args; if(!BUS_MATCHNAME(ca, "fl")) return(0); return(1); } void flattach(parent, self, args) struct device *parent, *self; void *args; { struct confargs *ca = args; struct flsoftc *sc = (struct flsoftc *)self; struct fltype *flist; int i, manf, type; u_int32_t next; struct flctag *blkbase; switch(cputype) { case WGRISC9100: /* WGRISC9100 can have 4 chips */ sc->sc_opncnt = 0; sc->sc_present = 0; sc->sc_ftype = NULL; OUT_FL_CTRL(0, 0); /* All CS lines high */ for(i = 0; i < 4; i++) { OUT_FL_CLE(FL_READID, (1 << i)); OUT_FL_ALE1(0, (1 << i)); manf = IN_FL_DATA; type = IN_FL_DATA; flist = fllist; while(flist->fl_name != 0) { if(flist->fl_manf == manf && flist->fl_type == type) { sc->sc_present |= 1 << i; sc->sc_size += flist->fl_size; if(sc->sc_ftype == NULL) { sc->sc_ftype = flist; } else if(sc->sc_ftype == flist) { } /* XXX Protection test type dependent ? */ OUT_FL_CLE(FL_READSTAT, (1 << i)); if(!(IN_FL_DATA & FLST_UNPROT)) { sc->sc_prot = 1; } break; } flist++; } } break; default: printf("fl: Unknown cputype '%d'", cputype); } if(sc->sc_ftype != NULL) { printf(" %s, %d*%d bytes%s.", sc->sc_ftype->fl_name, sc->sc_size / sc->sc_ftype->fl_size, sc->sc_ftype->fl_size, sc->sc_prot ? " Write protected" : ""); } else { printf("WARNING! Flash type not identified!"); } printf("\n"); /* * Now set up the sram cache. We usually have 128k sram * on the board and we use up 25% or 32kb of this for the * flash cache. */ sc->sc_cache = (struct flcache *)(RISC_SRAM_START+16); blkbase = &sc->sc_cache->flcblk[0]; if(sc->sc_cache->magic != FLC_MAGIC) { printf("fl0: *WARNING* flash cache not initialized!"); printf(" Initializing to %d blocks.\n", MAX_BLKS-1); flinitcache(sc); } else { /* Cache initialized. Make sanity check. */ for(i = 1; i < MAX_BLKS; i++) { blkbase[i].stat &= ~(BLK_SFREE | BLK_SBUSY); } next = sc->sc_cache->free; i = 0; while(next && (next < MAX_BLKS) && (i < MAX_BLKS)) { blkbase[next].stat |= BLK_SFREE; next = blkbase[next].next; i++; } i = 0; next = blkbase[0].next; while(next && (next < MAX_BLKS) && (i < MAX_BLKS)) { blkbase[next].stat |= BLK_SBUSY; next = blkbase[next].next; i++; } for(i = 1; i < MAX_BLKS; i++) { switch(blkbase[i].stat & (BLK_SBUSY|BLK_SFREE|BLK_ONFREE|BLK_ONBUSY)) { case BLK_SBUSY|BLK_ONBUSY: case BLK_SFREE|BLK_ONFREE: break; default: printf("fl0: cache sanity err blk %d stat %x\n", i, blkbase[i].stat); break; } } } } static void flinitcache(sc) struct flsoftc *sc; { int i; struct flctag *blkbase; blkbase = &sc->sc_cache->flcblk[0]; for(i = 1; i < MAX_BLKS; i++) { blkbase[i].next = i+1; blkbase[i].stat = BLK_ONFREE; } blkbase[MAX_BLKS-1].next = 0; /* mark end */ sc->sc_cache->free = 1; /* first free */ sc->sc_cache->nfree = MAX_BLKS-1; blkbase[0].next = 0; /* no busy blocks */ sc->sc_cache->lbusy = 0; /* no busy blocks */ sc->sc_cache->stat = 0; sc->sc_cache->cache = (char *)sc->sc_cache + sizeof(struct flcache); sc->sc_cache->magic = FLC_MAGIC; } /* * Return index to cached block if in cache. * Also execute command. */ static int flblkincache(sc, offs, command) struct flsoftc *sc; size_t offs; int command; { u_int32_t next, prev, offset; struct flctag *blkbase; offset = offs >> DEV_BSHIFT; blkbase = &sc->sc_cache->flcblk[0]; next = blkbase[0].next; prev = 0; while(next && (blkbase[next].offset != offset)) { prev = next; next = blkbase[next].next; } if(next && (command == BLK_LAST)) { if(sc->sc_cache->lbusy != next) { blkbase[prev].next = blkbase[next].next; blkbase[next].next = 0; blkbase[sc->sc_cache->lbusy].next = next; sc->sc_cache->lbusy = next; } return(next); /* already last */ } if(next || (command != BLK_FORCE)) return(next); /* BLK_FORCE */ printf("fl0: expected offset %x not found in cache!!\n", offset); next = blkbase[0].next; while(next) { printf("%d/%x ",next, blkbase[next].offset); next = blkbase[next].next; } printf("\n"); next = sc->sc_cache->free; while(next) { printf("%d/%x ",next, blkbase[next].offset); next = blkbase[next].next; } printf("\n"); mdbpanic(); return(0); } /* * Return block erase status. */ static int flblkerased(sc, blk, offs, cnt) struct flsoftc *sc; size_t offs; size_t cnt; { int chip; u_int32_t blkadr; int erased = TRUE; chip = 1 << (offs / sc->sc_ftype->fl_size); blkadr = offs % sc->sc_ftype->fl_size; OUT_FL_CLE(FL_READ1, chip); OUT_FL_ALE3(blkadr, chip); WAIT_FL_RDY; while(cnt--) { if(IN_FL_DATA != 0xff) erased = FALSE; } return(erased); } static int flgetblk(sc, blk, offs, cnt) struct flsoftc *sc; char *blk; size_t offs; size_t cnt; { int chip; u_int32_t blkadr; chip = 1 << (offs / sc->sc_ftype->fl_size); blkadr = offs % sc->sc_ftype->fl_size; OUT_FL_CLE(FL_READ1, chip); OUT_FL_ALE3(blkadr, chip); WAIT_FL_RDY; while(cnt--) { *blk++ = IN_FL_DATA; } return(0); } static int flputblk(sc, blk, offs, cnt) struct flsoftc *sc; char *blk; size_t offs; size_t cnt; { int chip; u_int32_t blkadr; chip = 1 << (offs / sc->sc_ftype->fl_size); blkadr = offs % sc->sc_ftype->fl_size; OUT_FL_CLE(FL_SEQDI, chip); OUT_FL_ALE3(blkadr, chip); while(cnt--) { OUT_FL_DATA(*blk); blk++; } OUT_FL_CLE(FL_PGPROG, chip); WAIT_FL_RDY; OUT_FL_CLE(FL_READSTAT, chip); if(IN_FL_DATA & FLST_ERROR) { return(-1); } return(0); } static int fleraseblk(sc, offs) struct flsoftc *sc; size_t offs; { int chip; u_int32_t blkadr; chip = 1 << (offs / sc->sc_ftype->fl_size); blkadr = offs % sc->sc_ftype->fl_size; OUT_FL_CLE(FL_BLERASE, chip); OUT_FL_ALE2(blkadr, chip); OUT_FL_CLE(FL_REERASE, chip); WAIT_FL_RDY; OUT_FL_CLE(FL_READSTAT, chip); if(IN_FL_DATA & FLST_ERROR) { return(-1); } return(0); } /* * Get next free block and put it last on busy list. */ static int flgetfree(sc, offs) struct flsoftc *sc; size_t offs; { u_int32_t blkno; struct flctag *blkbase; blkbase = &sc->sc_cache->flcblk[0]; blkno = sc->sc_cache->free; if(blkno) { sc->sc_cache->free = blkbase[blkno].next; sc->sc_cache->nfree--; blkbase[blkno].next = 0; blkbase[blkno].stat = BLK_ONBUSY; blkbase[blkno].offset = offs >> DEV_BSHIFT; blkbase[blkno].age = 0; /* XXX ticks! */ blkbase[sc->sc_cache->lbusy].next = blkno; sc->sc_cache->lbusy = blkno; } return(blkno); } /* * Put block from busy list on free list. */ static void flputfree(sc, blkno) struct flsoftc *sc; u_int32_t blkno; { struct flctag *cblk, *rblk; if(blkno) { cblk = &sc->sc_cache->flcblk[0]; rblk = &sc->sc_cache->flcblk[blkno]; while(cblk->next && cblk->next != blkno) { cblk = &sc->sc_cache->flcblk[cblk->next]; } cblk->next = rblk->next; if(sc->sc_cache->lbusy == blkno) { sc->sc_cache->lbusy = cblk - &sc->sc_cache->flcblk[0]; } rblk->next = sc->sc_cache->free; rblk->stat = BLK_ONFREE; sc->sc_cache->free = blkno; sc->sc_cache->nfree++; } } /* * Push back a block (4k) to the flash. We first need to get * any used pages not in cache before erasing. */ static int flpushblk(sc, offs) struct flsoftc *sc; size_t offs; { int i; u_int32_t blkno, offset; int error = 0; char *blk; printf("fl: pushing block %d to flash.\n", offs); offset = offs; for(i = 0; i < 8; i++) { if(flblkincache(sc, offset, BLK_FIND) == 0) { if((blkno = flgetfree(sc, offset)) != 0) { blk = &sc->sc_cache->cache[blkno*512]; flgetblk(sc, blk, offset, 256); flgetblk(sc, blk + 256, offset + 256, 256); } else { error = EIO; } } offset += DEV_BSIZE; } if(error == 0 && fleraseblk(sc, offs)) error = EIO; if(error == 0) { offset = offs; for(i = 0; i < 8; i++) { blkno = flblkincache(sc, offset, BLK_FORCE); blk = &sc->sc_cache->cache[blkno*512]; if(flputblk(sc, blk, offset, 256)) error = EIO; if(flputblk(sc, blk + 256, offset + 256, 256)) error = EIO; offset += DEV_BSIZE; flputfree(sc, blkno); } } return(error); } /* * Read a block (512 bytes) either from cache or from flash. */ static int flreadblk(sc, blk, offs) struct flsoftc *sc; char *blk; size_t offs; { int error = 0; u_int32_t blkno; if((blkno = flblkincache(sc, offs, BLK_FIND)) != 0) { bcopy(&sc->sc_cache->cache[blkno*512], blk, 512); } else { error |= flgetblk(sc, blk, offs, 256); error |= flgetblk(sc, blk+256, offs+256, 256); } return(error); } /* * Write a block (512 bytes) to cache. If no room in cache * make room by pushing data back into the flash memory. */ static int flwriteblk(sc, blk, offs) struct flsoftc *sc; char *blk; size_t offs; { int error = 0; u_int32_t blkno, offset; /* In cache? */ if((blkno = flblkincache(sc, offs, BLK_LAST)) != 0) { bcopy(blk, &sc->sc_cache->cache[blkno*512], 512); } /* Add to cache */ else { if(sc->sc_cache->nfree < 8) { /* Push busy blocks to get a free one */ /* XXX We just pick first busy not in same 4k * XXX as the one we need space for, now */ blkno = sc->sc_cache->flcblk[0].next; do { offset = sc->sc_cache->flcblk[blkno].offset; offset <<= DEV_BSHIFT; offset &= ~4095; blkno = sc->sc_cache->flcblk[blkno].next; } while(offset == (offs & ~4095)); error = flpushblk(sc, offset); } blkno = flgetfree(sc, offs); if(blkno) { bcopy(blk, &sc->sc_cache->cache[blkno*512], 512); } else { error = EIO; } } return(error); } /*ARGSUSED*/ int flopen(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { struct flsoftc *sc; if (FLUNIT(dev) >= fl_cd.cd_ndevs || fl_cd.cd_devs[FLUNIT(dev)] == NULL) return (ENODEV); sc = (struct flsoftc *) fl_cd.cd_devs[FLUNIT(dev)]; flgetdisklabel(dev, sc); sc->sc_opncnt++; return(0); } /*ARGSUSED*/ int flclose(dev, flag, mode, p) dev_t dev; int flag, mode; struct proc *p; { struct flsoftc *sc; u_int32_t offset, blkno; sc = (struct flsoftc *) fl_cd.cd_devs[FLUNIT(dev)]; sc->sc_opncnt--; if(sc->sc_opncnt == 0) { while((blkno = sc->sc_cache->flcblk[0].next) != 0) { offset = sc->sc_cache->flcblk[blkno].offset; offset <<= DEV_BSHIFT; offset &= ~4095; flpushblk(sc, offset); } } return(0); } /*ARGSUSED*/ int flioctl(dev, cmd, data, flag, p) dev_t dev; u_char *data; int cmd, flag; struct proc *p; { int unit = FLUNIT(dev); struct flsoftc *sc = (struct flsoftc *) fl_cd.cd_devs[unit]; struct cpu_disklabel clp; struct disklabel lp, *lpp; int error = 0; switch (cmd) { /* * Very basic disklabel handling. */ case DIOCGDINFO: *(struct disklabel *)data = sc->sc_label; break; case DIOCWDINFO: case DIOCSDINFO: if((flag & FWRITE) == 0) { error = EBADF; break; } error = setdisklabel(&sc->sc_label, (struct disklabel *)data, 0, &clp); if((error == 0) && (cmd == DIOCWDINFO)) { error = writedisklabel(FLLABELDEV(dev), flstrategy, &sc->sc_label, &clp); } break; case DIOCWLABEL: if((flag & FWRITE) == 0) error = EBADF; break; default: error = EINVAL; break; } return(error); } void flstrategy(bp) struct buf *bp; { int unit = FLUNIT(bp->b_dev); struct flsoftc *sc = (struct flsoftc *) fl_cd.cd_devs[unit]; int error = 0; size_t offs, xfer, cnt; caddr_t buf; offs = bp->b_blkno << DEV_BSHIFT; /* Start address */ buf = bp->b_data; bp->b_resid = bp->b_bcount; if(offs < sc->sc_size) { xfer = bp->b_resid; if(offs + xfer > sc->sc_size) { xfer = sc->sc_size - offs; } if(bp->b_flags & B_READ) { bp->b_resid -= xfer; while(xfer > 0) { cnt = (xfer > DEV_BSIZE) ? DEV_BSIZE : xfer; flreadblk(sc, buf, offs, cnt); xfer -= cnt; offs += cnt; buf += cnt; } } else { while(xfer > 0) { cnt = (xfer > DEV_BSIZE) ? DEV_BSIZE : xfer; if(flwriteblk(sc, buf, offs, cnt)) error = EIO; xfer -= cnt; buf += cnt; offs += cnt; bp->b_resid -= cnt; } } } else if(!(bp->b_flags & B_READ)) { /* No space for write */ error = EIO; } if(error) { bp->b_error = error; bp->b_flags |= B_ERROR; } biodone(bp); } /*ARGSUSED*/ int flread(dev, uio, ioflag) dev_t dev; struct uio *uio; int ioflag; { return (physio(flstrategy, NULL, dev, B_READ, minphys, uio)); } /*ARGSUSED*/ int flwrite(dev, uio, ioflag) dev_t dev; struct uio *uio; int ioflag; { return (physio(flstrategy, NULL, dev, B_WRITE, minphys, uio)); } int fldump(dev, blkno, va, size) dev_t dev; daddr_t blkno; caddr_t va; size_t size; { return(ENODEV); } int flsize(dev) dev_t dev; { return(0); } /* * Create a disklabel from the flash ram info. */ static void flgetdisklabel(dev, sc) dev_t dev; struct flsoftc *sc; { struct disklabel *lp = &sc->sc_label; struct cpu_disklabel clp; char *errstring; bzero(lp, sizeof(struct disklabel)); bzero(&clp, sizeof(struct cpu_disklabel)); lp->d_secsize = 1 << DEV_BSHIFT; lp->d_ntracks = 1; lp->d_nsectors = sc->sc_size >> DEV_BSHIFT; lp->d_ncylinders = 1; lp->d_secpercyl = lp->d_nsectors; strncpy(lp->d_typename, "FLASH disk", 16); lp->d_type = DTYPE_SCSI; /* XXX This is a fake! */ strncpy(lp->d_packname, "fictitious", 16); lp->d_secperunit = lp->d_nsectors; lp->d_rpm = 3600; lp->d_interleave = 1; lp->d_flags = 0; lp->d_partitions[RAW_PART].p_offset = 0; lp->d_partitions[RAW_PART].p_size = lp->d_secperunit * (lp->d_secsize / DEV_BSIZE); lp->d_partitions[RAW_PART].p_fstype = FS_UNUSED; lp->d_npartitions = RAW_PART + 1; lp->d_magic = DISKMAGIC; lp->d_magic2 = DISKMAGIC; lp->d_checksum = dkcksum(lp); /* * Call the generic disklabel extraction routine */ errstring = readdisklabel(FLLABELDEV(dev), flstrategy, lp, &clp); if (errstring) { printf("%s: %s\n", sc->sc_dev.dv_xname, errstring); } }