From 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a Mon Sep 17 00:00:00 2001 From: "Charles.Forsyth" Date: Fri, 22 Dec 2006 21:39:35 +0000 Subject: 20060303 --- os/port/devftl.c | 1365 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1365 insertions(+) create mode 100644 os/port/devftl.c (limited to 'os/port/devftl.c') diff --git a/os/port/devftl.c b/os/port/devftl.c new file mode 100644 index 00000000..ab24588a --- /dev/null +++ b/os/port/devftl.c @@ -0,0 +1,1365 @@ +/* + * basic Flash Translation Layer driver + * see for instance the Intel technical paper + * ``Understanding the Flash Translation Layer (FTL) Specification'' + * Order number 297816-001 (online at www.intel.com) + * + * a public driver by David Hinds, dhinds@allegro.stanford.edu + * further helps with some details. + * + * this driver uses the common simplification of never storing + * the VBM on the medium (a waste of precious flash!) but + * rather building it on the fly as the block maps are read. + * + * Plan 9 driver (c) 1997 by C H Forsyth (forsyth@terzarima.net) + * This driver may be used or adapted by anyone for any non-commercial purpose. + * + * adapted for Inferno 1998 by C H Forsyth, Vita Nuova Limited, York, England (forsyth@vitanuova.com) + * + * TO DO: + * check error handling details for get/put flash + * bad block handling + * reserved space in formatted size + * possibly block size as parameter + */ + +#include "u.h" +#include "../port/lib.h" +#include "../port/error.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" + +#include "kernel.h" + +#ifndef offsetof +#define offsetof(s, m) (ulong)(&(((s*)0)->m)) +#endif + +typedef struct Ftl Ftl; +typedef struct Merase Merase; +typedef struct Terase Terase; + +enum { + Eshift = 18, /* 2^18=256k; log2(eraseunit) */ + Flashseg = 1<>Bshift) +#define MKBAM(b,t) (((b)<>8),((p)[0]=(v))) +#define PUT4(p,v) (((p)[3]=(v)>>24),((p)[2]=(v)>>16),((p)[1]=(v)>>8),((p)[0]=(v))) + +static Lock ftllock; +static Ftl *ftls; +static int ftlpct = USABLEPCT; + +static ulong allocblk(Ftl*); +static int erasedetect(Ftl *ftl, ulong base, ulong size, ushort *pstart, ushort *nunits); +static void eraseflash(Ftl*, ulong); +static void erasefree(Terase*); +static void eraseinit(Ftl*, ulong, int, int); +static Terase* eraseload(Ftl*, int, ulong); +static void ftlfree(Ftl*); +static void getflash(Ftl*, void*, ulong, long); +static int mapblk(Ftl*, ulong, Terase**, ulong*); +static Ftl* mkftl(char*, ulong, ulong, int, char*); +static void putbam(Ftl*, Terase*, int, ulong); +static void putflash(Ftl*, ulong, void*, long); +static int scavenge(Ftl*); + +enum { + Qdir, + Qctl, + Qdata, +}; + +#define DATAQID(q) ((q) >= Qdata && (q) <= Qdata + NPART) + +static void +ftlpartcmd(Ftl *ftl, char **fields, int nfields) +{ + ulong start, end; + char *p; + int n, newn; + + /* name start [end] */ + if(nfields < 2 || nfields > 3) + error(Ebadarg); + if(ftl->npart >= NPART) + error("table full"); + if(strcmp(fields[0], "ctl") == 0 || strcmp(fields[0], "data") == 0) + error(Ebadarg); + newn = -1; + for(n = 0; n < NPART; n++){ + if(ftl->part[n].name == nil){ + if(newn < 0) + newn = n; + continue; + } + if(strcmp(fields[0], ftl->part[n].name + 3) == 0) + error(Ebadarg); + } + start = strtoul(fields[1], 0, 0); + if(nfields > 2) + end = strtoul(fields[2], 0, 0); + else + end = ftl->rwlimit * Bsize; + if(start >= end || start % Bsize || end % Bsize) + error(Ebadarg); + ftl->part[newn].start = start; + ftl->part[newn].size = end - start; + ftl->part[newn].rwlimit = end / Bsize; + free(ftl->part[newn].name); + p = malloc(strlen(fields[0]) + 3 + 1); + strcpy(p, "ftl"); + strcat(p, fields[0]); + ftl->part[newn].name = p; + ftl->npart++; +} + +static void +ftldelpartcmd(Ftl *ftl, char **fields, int nfields) +{ + int n; + // name + if(nfields != 1) + error(Ebadarg); + for(n = 0; n < NPART; n++) + if(strcmp(fields[0], ftl->part[n].name + 3) == 0){ + free(ftl->part[n].name); + ftl->part[n].name = nil; + ftl->npart--; + return; + } + error(Ebadarg); +} + +static int +ftlgen(Chan *c, char*, Dirtab*, int, int i, Dir *dp) +{ + int n; + switch(i){ + case DEVDOTDOT: + devdir(c, (Qid){Qdir, 0, QTDIR}, "#X", 0, eve, 0555, dp); + break; + case 0: + devdir(c, (Qid){Qctl, 0, QTFILE}, "ftlctl", 0, eve, 0660, dp); + break; + case 1: + devdir(c, (Qid){Qdata, 0, QTFILE}, "ftldata", ftls ? ftls->rwlimit * Bsize : 0, eve, 0660, dp); + break; + default: + if(ftls == nil) + return -1; + i -= 2; + if(i >= ftls->npart) + return -1; + for(n = 0; n < NPART; n++) + if(ftls->part[n].name != nil){ + if(i == 0) + break; + i--; + } + if(i != 0){ + print("wierd\n"); + return -1; + } + devdir(c, (Qid){Qdata + 1 + n, 0, QTFILE}, ftls->part[n].name, ftls->part[n].size, eve, 0660, dp); + } + return 1; +} + +static Ftl * +ftlget(void) +{ + Ftl *ftl; + + lock(&ftllock); + ftl = ftls; + if(ftl != nil) + incref(ftl); + unlock(&ftllock); + return ftl; +} + +static void +ftlput(Ftl *ftl) +{ + if(ftl != nil){ + lock(&ftllock); + if(decref(ftl) == 0 && ftl->detach == Detached){ + ftls = nil; + if(ftl->hasproc){ /* no lock needed: can't change if ftl->ref==0 */ + ftl->detach = Deferred; + wakeup(&ftl->workr); + }else + ftlfree(ftl); + } + unlock(&ftllock); + } +} + +static Chan * +ftlattach(char *spec) +{ + return devattach('X', spec); +} + +static Walkqid* +ftlwalk(Chan *c, Chan *nc, char **name, int nname) +{ + Walkqid *wq; + + wq = devwalk(c, nc, name, nname, 0, 0, ftlgen); + if(wq != nil && wq->clone != nil && wq->clone != c) + if(DATAQID(wq->clone->qid.path)) + wq->clone->aux = ftlget(); + return wq; +} + +static int +ftlstat(Chan *c, uchar *dp, int n) +{ + return devstat(c, dp, n, 0, 0, ftlgen); +} + +static Chan* +ftlopen(Chan *c, int omode) +{ + Ftl *ftl; + omode = openmode(omode); + if(DATAQID(c->qid.path)){ + ftl = ftls; + if(ftl == nil) + error(Enodev); + if(strcmp(up->env->user, eve)!=0) + error(Eperm); + } + else if(c->qid.path == Qctl){ + if(strcmp(up->env->user, eve)!=0) + error(Eperm); + } + c = devopen(c, omode, 0, 0, ftlgen); + if(DATAQID(c->qid.path)){ + c->aux = ftlget(); + if(c->aux == nil) + error(Enodev); + } + return c; +} + +static void +ftlclose(Chan *c) +{ + if(DATAQID(c->qid.path) && (c->flag&COPEN) != 0) + ftlput((Ftl*)c->aux); +} + +static long +ftlread(Chan *c, void *buf, long n, vlong offset) +{ + Ftl *ftl; + Terase *e; + int nb; + uchar *a; + ulong pb; + if(c->qid.type & QTDIR) + return devdirread(c, buf, n, 0, 0, ftlgen); + + if(DATAQID(c->qid.path)){ + ulong rwlimit; + + if(n <= 0 || n%Bsize || offset%Bsize) + error(Eio); + ftl = c->aux; + if(c->qid.path > Qdata){ + int p = c->qid.path - Qdata - 1; + offset += ftl->part[p].start; + rwlimit = ftl->part[p].rwlimit; + } + else + rwlimit = ftl->rwlimit; + nb = n/Bsize; + offset /= Bsize; + if(offset >= rwlimit) + return 0; + if(offset+nb > rwlimit) + nb = rwlimit - offset; + a = buf; + for(n = 0; n < nb; n++){ + qlock(ftl); + if(waserror()){ + qunlock(ftl); + nexterror(); + } + if(mapblk(ftl, offset+n, &e, &pb)) + getflash(ftl, a, e->offset + pb*Bsize, Bsize); + else + memset(a, 0, Bsize); + poperror(); + qunlock(ftl); + a += Bsize; + } + return a-(uchar*)buf; + } + + if(c->qid.path != Qctl) + error(Egreg); + + return 0; +} + +static long +ftlwrite(Chan *c, void *buf, long n, vlong offset) +{ + char cmd[64], *fields[6]; + int ns, i, k, nb; + uchar *a; + Terase *e, *oe; + ulong ob, v, base, size, segsize; + Ftl *ftl; + + if(n <= 0) + return 0; + + if(DATAQID(c->qid.path)){ + ulong rwlimit; + ftl = c->aux; + if(n <= 0 || n%Bsize || offset%Bsize) + error(Eio); + if(c->qid.path > Qdata){ + int p = c->qid.path - Qdata - 1; + offset += ftl->part[p].start; + rwlimit = ftl->part[p].rwlimit; + } + else + rwlimit = ftl->rwlimit; + nb = n/Bsize; + offset /= Bsize; + if(offset >= rwlimit) + return 0; + if(offset+nb > rwlimit) + nb = rwlimit - offset; + a = buf; + for(n = 0; n < nb; n++){ + ns = 0; + while((v = allocblk(ftl)) == 0) + if(!scavenge(ftl) || ++ns > 3){ + static int stop; + + if(stop < 3){ + stop++; + print("ftl: flash memory full\n"); + } + error("flash memory full"); + } + qlock(ftl); + if(waserror()){ + qunlock(ftl); + nexterror(); + } + if(!mapblk(ftl, offset+n, &oe, &ob)) + oe = nil; + e = ftl->unit[v>>16]; + v &= 0xffff; + putflash(ftl, e->offset + v*Bsize, a, Bsize); + putbam(ftl, e, v, MKBAM(offset+n, DataBlock)); + /* both old and new block references exist in this window (can't be closed?) */ + ftl->vbm[offset+n] = (e->x<<16) | v; + if(oe != nil){ + putbam(ftl, oe, ob, Bdeleted); + oe->ndead++; + } + poperror(); + qunlock(ftl); + a += Bsize; + } + return a-(uchar*)buf; + } + else if(c->qid.path == Qctl){ + if(n > sizeof(cmd)-1) + n = sizeof(cmd)-1; + memmove(cmd, buf, n); + cmd[n] = 0; + i = getfields(cmd, fields, 6, 1, " \t\n"); + if(i <= 0) + error(Ebadarg); + if(i >= 2 && (strcmp(fields[0], "init") == 0 || strcmp(fields[0], "format") == 0)){ + if(i > 2) + base = strtoul(fields[2], nil, 0); + else + base = 0; /* TO DO: hunt for signature */ + if(i > 3) + size = strtoul(fields[3], nil, 0); + else + size = Nolimit; + if(i > 4) + segsize = strtoul(fields[4], nil, 0); + else + segsize = 0; + /* segsize must be power of two and size and base must be multiples of it + * if segsize is zero, then use whatever the device returns + */ + if(segsize != 0 && (segsize > size + || segsize&(segsize-1) + || (base != Nolimit && base&(segsize-1)) + || size == 0 + || (size != Nolimit && size&(segsize-1)))) + error(Ebadarg); + if(segsize == 0) + k = 0; + else { + for(k=0; k<32 && (1<trace = i>1? strtol(fields[1], nil, 0): 1; + }else if(strcmp(fields[0], "detach") == 0){ + if((ftl = ftlget()) != nil){ + if(ftl->ref > 1){ + ftlput(ftl); + error(Einuse); + } + ftl->detach = Detached; + ftlput(ftl); + }else + error(Enodev); + }else if(strcmp(fields[0], "part")==0){ + if((ftl = ftlget()) != nil){ + if(ftl->ref > 1){ + ftlput(ftl); + error(Einuse); + } + if(waserror()){ + ftlput(ftl); + nexterror(); + } + ftlpartcmd(ftl, fields + 1, i - 1); + poperror(); + ftlput(ftl); + }else + error(Enodev); + }else if(strcmp(fields[0], "delpart")==0){ + if((ftl = ftlget()) != nil){ + if(ftl->ref > 1){ + ftlput(ftl); + error(Einuse); + } + if(waserror()){ + ftlput(ftl); + nexterror(); + } + ftldelpartcmd(ftls, fields + 1, i - 1); + poperror(); + ftlput(ftl); + }else + error(Enodev); + }else if(i >= 2 && strcmp(fields[0], "pct")==0){ + v = strtoul(fields[1], nil, 0); + if(v >= 50) + ftlpct = v; + }else + error(Ebadarg); + return n; + } + error(Egreg); + return 0; /* not reached */ +} + +static Chan * +ftlkopen(char *name, char *suffix, int mode) +{ + Chan *c; + char *fn; + int fd; + + if(suffix != nil && *suffix){ + fn = smalloc(strlen(name)+strlen(suffix)+1); + if(fn == nil) + return nil; + strcpy(fn, name); + strcat(fn, suffix); + fd = kopen(fn, mode); + free(fn); + }else + fd = kopen(name, mode); + if(fd < 0) + return nil; + c = fdtochan(up->env->fgrp, fd, mode, 0, 1); + kclose(fd); + return c; +} + +static ulong +ftlfsize(Chan *c) +{ + uchar dbuf[STATFIXLEN+32*4]; + Dir d; + int n; + + n = devtab[c->type]->stat(c, dbuf, sizeof(dbuf)); + if(convM2D(dbuf, n, &d, nil) == 0) + return 0; + return d.length; +} + +static Ftl * +mkftl(char *fname, ulong base, ulong size, int eshift, char *op) +{ + int i, j, nov, segblocks, n, badseg, old, valid; + ulong limit; + Terase *e; + Ftl *ftl; + char buf[64], *fields[8]; + ulong ea; + Chan *statfd; + + ftl = malloc(sizeof(*ftl)); + if(ftl == nil) + error(Enomem); + e = nil; + if(waserror()){ + ftlfree(ftl); + if(e) + free(e); + nexterror(); + } + ftl->lastx = 0; + ftl->trace = 0; + ftl->flash = ftlkopen(fname, "", ORDWR); + if(ftl->flash == nil) + error(up->env->errstr); + ftl->flashctl = ftlkopen(fname, "ctl", ORDWR); + if(ftl->flashctl == nil) + error(up->env->errstr); + old = 1; + statfd = ftlkopen(fname, "stat", OREAD); /* old scheme */ + if(statfd == nil){ + statfd = ftl->flashctl; /* new just uses ctl */ + old = 0; + } + statfd->offset = 0; + if((n = kchanio(statfd, buf, sizeof(buf), OREAD)) <= 0){ + print("ftl: read stat/ctl failed: %s\n", up->env->errstr); + error(up->env->errstr); + } + if(n >= sizeof(buf)) + n = sizeof(buf)-1; + buf[n] = 0; + if(statfd != ftl->flashctl) + cclose(statfd); + + n = getfields(buf, fields, nelem(fields), 1, " \t\n"); + ea = 0; + if(old){ + if(n >= 4) + if((ea = strtoul(fields[3], nil, 16)) < 8*1024) + ea = 0; /* assume the format is wrong */ + }else{ + if(n >= 7) + if((ea = strtoul(fields[6], nil, 0)) < 2*1024) + ea = 0; /* assume the format is wrong */ + } + if(ea != 0){ + for(i=0; i < 32 && (1<flash); + if(limit == 0) + error("no space for flash translation"); + ftl->segsize = 1 << eshift; + if(base == Nolimit){ + ushort pstart, nunits; + erasedetect(ftl, 0, limit, &pstart, &nunits); + base = pstart * ftl->segsize; + size = nunits * ftl->segsize; + print("ftl: partition in %s at 0x%.8lux, length 0x%.8lux\n", fname, base, size); + } else if(size == Nolimit) + size = limit-base; + if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1< size){ + print("ftl: bad: base=%#lux limit=%#lux size=%ld eshift=%d\n", base, limit, size, eshift); + error("bad flash space parameters"); + } + if(FTLDEBUG) + print("%s flash %s #%lux:#%lux limit #%lux\n", op, fname, base, size, limit); + ftl->base = base; + ftl->size = size; + ftl->bshift = Bshift; + ftl->bsize = Bsize; + ftl->eshift = eshift; + ftl->nunit = size>>eshift; + nov = ((ftl->segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; /* number of overhead blocks per segment (header, and BAM itself) */ + ftl->fstart = nov; + segblocks = ftl->segsize/Bsize - nov; + ftl->nblock = ftl->nunit*segblocks; + if(ftl->nblock > 0x10000){ + /* oops - too many blocks */ + ftl->nunit = 0x10000 / segblocks; + ftl->nblock = ftl->nunit * segblocks; + size = ftl->nunit * ftl->segsize; + ftl->size = size; + print("ftl: too many blocks - limiting to %ld bytes %d units %lud blocks\n", + size, ftl->nunit, ftl->nblock); + } + ftl->vbm = malloc(ftl->nblock*sizeof(*ftl->vbm)); + ftl->unit = malloc(ftl->nunit*sizeof(*ftl->unit)); + if(ftl->vbm == nil || ftl->unit == nil) + error(Enomem); + if(strcmp(op, "format") == 0){ + for(i=0; inunit-1; i++) + eraseinit(ftl, i*ftl->segsize, i, 1); + eraseinit(ftl, i*ftl->segsize, XferID, 1); + } + badseg = -1; + ftl->xfer = -1; + valid = 0; + for(i=0; inunit; i++){ + e = eraseload(ftl, i, i*ftl->segsize); + if(e == nil){ + print("ftl: logical segment %d: bad format\n", i); + if(badseg == -1) + badseg = i; + else + badseg = -2; + continue; + } + if(e->id == XferBusy){ + e->nerase++; + eraseinit(ftl, e->offset, XferID, e->nerase); + e->id = XferID; + } + for(j=0; jnunit; j++) + if(ftl->unit[j] != nil && ftl->unit[j]->id == e->id){ + print("ftl: duplicate erase unit #%x\n", e->id); + erasefree(e); + e = nil; + break; + } + if(e){ + valid++; + ftl->unit[e->x] = e; + if(e->id == XferID) + ftl->xfer = e->x; + if(FTLDEBUG) + print("ftl: unit %d:#%x used %lud free %lud dead %lud bad %lud nerase %lud\n", + e->x, e->id, e->nused, e->nfree, e->ndead, e->nbad, e->nerase); + e = nil; + USED(e); + } + } + if(badseg >= 0){ + if(ftl->xfer >= 0) + error("invalid ftl format"); + i = badseg; + eraseinit(ftl, i*ftl->segsize, XferID, 1); + e = eraseload(ftl, i, i*ftl->segsize); + if(e == nil) + error("bad ftl format"); + ftl->unit[e->x] = e; + ftl->xfer = e->x; + print("ftl: recovered transfer unit %d\n", e->x); + valid++; + e = nil; + USED(e); + } + if(ftl->xfer < 0 && valid <= 0 || ftl->xfer >= 0 && valid <= 1) + error("no valid flash data units"); + if(ftl->xfer < 0) + error("ftl: no transfer unit: device is WORM\n"); + else + ftl->nblock -= segblocks; /* discount transfer segment */ + if(ftl->nblock >= 1000) + ftl->rwlimit = ftl->nblock-100; /* TO DO: variable reserve */ + else + ftl->rwlimit = ftl->nblock*ftlpct/100; + poperror(); + return ftl; +} + +static void +ftlfree(Ftl *ftl) +{ + int i, n; + + if(ftl != nil){ + if(ftl->flashctl != nil) + cclose(ftl->flashctl); + if(ftl->flash != nil) + cclose(ftl->flash); + + if(ftl->unit){ + for(i = 0; i < ftl->nunit; i++) + erasefree(ftl->unit[i]); + free(ftl->unit); + } + free(ftl->vbm); + for(n = 0; n < NPART; n++) + free(ftl->part[n].name); + free(ftl); + } +} + +/* + * this simple greedy algorithm weighted by nerase does seem to lead + * to even wear of erase units (cf. the eNVy file system) + */ +static Terase * +bestcopy(Ftl *ftl) +{ + Terase *e, *be; + int i; + + be = nil; + for(i=0; inunit; i++) + if((e = ftl->unit[i]) != nil && e->id != XferID && e->id != XferBusy && e->ndead+e->nbad && + (be == nil || e->nerase <= be->nerase && e->ndead >= be->ndead)) + be = e; + return be; +} + +static int +copyunit(Ftl *ftl, Terase *from, Terase *to) +{ + int i, nb; + uchar id[2]; + ulong *bam; + uchar *buf; + ulong v, bno; + + if(FTLDEBUG || ftl->trace) + print("ftl: copying %d (#%lux) to #%lux\n", from->id, from->offset, to->offset); + to->nbam = 0; + free(to->bam); + to->bam = nil; + bam = nil; + buf = malloc(Bsize); + if(buf == nil) + return 0; + if(waserror()){ + free(buf); + free(bam); + return 0; + } + PUT2(id, XferBusy); + putflash(ftl, to->offset+offsetof(Merase,id[0]), id, 2); + /* make new BAM */ + nb = from->nbam*sizeof(*to->bam); + bam = malloc(nb); + if(bam == nil) + error(Enomem); + memmove(bam, from->bam, nb); + to->nused = 0; + to->nbad = 0; + to->nfree = 0; + to->ndead = 0; + for(i = 0; i < from->nbam; i++) + switch(bam[i]){ + case Bwriting: + case Bdeleted: + case Bfree: + bam[i] = Bfree; + to->nfree++; + break; + default: + switch(bam[i]&BlockType){ + default: + case BadBlock: /* it isn't necessarily bad in this unit */ + to->nfree++; + bam[i] = Bfree; + break; + case DataBlock: + case ReplacePage: + v = bam[i]; + bno = BNO(v & ~BlockType); + if(i < ftl->fstart || bno >= ftl->nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%lux\n", from->x, from->id, i, v); + to->nfree++; + bam[i] = Bfree; + break; + } + getflash(ftl, buf, from->offset+i*Bsize, Bsize); + putflash(ftl, to->offset+i*Bsize, buf, Bsize); + to->nused++; + break; + case ControlBlock: + to->nused++; + break; + } + } + for(i=0; inbam; i++){ + uchar *p = (uchar*)&bam[i]; + v = bam[i]; + if(v != Bfree && ftl->trace > 1) + print("to[%d]=#%lux\n", i, v); + PUT4(p, v); + } + putflash(ftl, to->bamoffset, bam, nb); /* BUG: PUT4 */ + for(i=0; inbam; i++){ + uchar *p = (uchar*)&bam[i]; + v = bam[i]; + PUT4(p, v); + } + to->id = from->id; + PUT2(id, to->id); + putflash(ftl, to->offset+offsetof(Merase,id[0]), id, 2); + to->nbam = from->nbam; + to->bam = bam; + ftl->nfree += to->nfree - from->nfree; + poperror(); + free(buf); + return 1; +} + +static int +mustscavenge(void *a) +{ + return ((Ftl*)a)->needspace || ((Ftl*)a)->detach == Deferred; +} + +static int +donescavenge(void *a) +{ + return ((Ftl*)a)->needspace == 0; +} + +static void +scavengeproc(void *arg) +{ + Ftl *ftl; + int i; + Terase *e, *ne; + + ftl = arg; + if(waserror()){ + print("ftl: kproc noted\n"); + pexit("ftldeath", 0); + } + for(;;){ + sleep(&ftl->workr, mustscavenge, ftl); + if(ftl->detach == Deferred){ + ftlfree(ftl); + pexit("", 0); + } + if(FTLDEBUG || ftl->trace) + print("ftl: scavenge %ld\n", ftl->nfree); + qlock(ftl); + if(waserror()){ + qunlock(ftl); + nexterror(); + } + e = bestcopy(ftl); + if(e == nil || ftl->xfer < 0 || (ne = ftl->unit[ftl->xfer]) == nil || ne->id != XferID || e == ne) + goto Fail; + if(copyunit(ftl, e, ne)){ + i = ne->x; ne->x = e->x; e->x = i; + ftl->unit[ne->x] = ne; + ftl->unit[e->x] = e; + ftl->xfer = e->x; + e->id = XferID; + e->nbam = 0; + free(e->bam); + e->bam = nil; + e->bamx = 0; + e->nerase++; + eraseinit(ftl, e->offset, XferID, e->nerase); + } + Fail: + if(FTLDEBUG || ftl->trace) + print("ftl: end scavenge %ld\n", ftl->nfree); + ftl->needspace = 0; + wakeup(&ftl->wantr); + poperror(); + qunlock(ftl); + } +} + +static int +scavenge(Ftl *ftl) +{ + if(ftl->xfer < 0 || bestcopy(ftl) == nil) + return 0; /* you worm! */ + + qlock(ftl); + if(waserror()){ + qunlock(ftl); + return 0; + } + if(!ftl->hasproc){ + ftl->hasproc = 1; + kproc("ftl.scavenge", scavengeproc, ftl, 0); + } + ftl->needspace = 1; + wakeup(&ftl->workr); + poperror(); + qunlock(ftl); + + qlock(&ftl->wantq); + if(waserror()){ + qunlock(&ftl->wantq); + nexterror(); + } + while(ftl->needspace) + sleep(&ftl->wantr, donescavenge, ftl); + poperror(); + qunlock(&ftl->wantq); + + return ftl->nfree; +} + +static void +putbam(Ftl *ftl, Terase *e, int n, ulong entry) +{ + uchar b[4]; + + e->bam[n] = entry; + PUT4(b, entry); + putflash(ftl, e->bamoffset + n*4, b, 4); +} + +static ulong +allocblk(Ftl *ftl) +{ + Terase *e; + int i, j; + + qlock(ftl); + i = ftl->lastx; + do{ + e = ftl->unit[i]; + if(e != nil && e->id != XferID && e->nfree){ + ftl->lastx = i; + for(j=e->bamx; jnbam; j++) + if(e->bam[j] == Bfree){ + putbam(ftl, e, j, Bwriting); + ftl->nfree--; + e->nfree--; + e->bamx = j+1; + qunlock(ftl); + return (e->x<<16) | j; + } + e->nfree = 0; + qunlock(ftl); + print("ftl: unit %d:#%x nfree %ld but not free in BAM\n", e->x, e->id, e->nfree); + qlock(ftl); + } + if(++i >= ftl->nunit) + i = 0; + }while(i != ftl->lastx); + qunlock(ftl); + return 0; +} + +static int +mapblk(Ftl *ftl, ulong bno, Terase **ep, ulong *bp) +{ + ulong v; + int x; + + if(bno < ftl->nblock){ + v = ftl->vbm[bno]; + if(v == 0 || v == ~0) + return 0; + x = v>>16; + if(x >= ftl->nunit || x == ftl->xfer || ftl->unit[x] == nil){ + print("ftl: corrupt format: bad block mapping %lud -> unit #%x\n", bno, x); + return 0; + } + *ep = ftl->unit[x]; + *bp = v & 0xFFFF; + return 1; + } + return 0; +} + +static void +eraseinit(Ftl *ftl, ulong offset, int id, int nerase) +{ + union { + Merase; + uchar block[ERASEHDRLEN]; + } *m; + uchar *bam, *p; + int i, nov; + + nov = ((ftl->segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize; /* number of overhead blocks (header, and BAM itself) */ + if(nov*Bsize >= ftl->segsize) + error("ftl -- too small for files"); + eraseflash(ftl, offset); + m = malloc(sizeof(*m)); + if(m == nil) + error(Enomem); + memset(m, 0xFF, sizeof(*m)); + m->linktuple[0] = 0x13; + m->linktuple[1] = 0x3; + memmove(m->linktuple+2, "CIS", 3); + m->orgtuple[0] = 0x46; + m->orgtuple[1] = 0x57; + m->orgtuple[2] = 0x00; + memmove(m->orgtuple+3, "FTL100", 7); + m->nxfer = 1; + PUT4(m->nerase, nerase); + PUT2(m->id, id); + m->bshift = ftl->bshift; + m->eshift = ftl->eshift; + PUT2(m->pstart, ftl->base >> ftl->eshift); + PUT2(m->nunits, ftl->nunit); + PUT4(m->psize, ftl->size - nov*Bsize*ftl->nunit); + PUT4(m->vbmbase, 0xffffffff); /* we always calculate the VBM */ + PUT2(m->nvbm, 0); + m->flags = 0; + m->code = 0xFF; + memmove(m->serial, "Inf1", 4); + PUT4(m->altoffset, 0); + PUT4(m->bamoffset, BAMoffset); + putflash(ftl, offset, m, ERASEHDRLEN); + free(m); + if(id == XferID) + return; + nov *= 4; /* now bytes of BAM */ + bam = malloc(nov); + if(bam == nil) + error(Enomem); + for(i=0; isegsize){ + if(waserror()) + continue; + getflash(ftl, m, o, ERASEHDRLEN); + poperror(); + if(memcmp(m->orgtuple + 3, "FTL100", 7) == 0 + && memcmp(m->serial, "Inf1", 4) == 0){ + *pstart = GET2(m->pstart); + *nunits = GET2(m->nunits); + rv = 1; + break; + } + } + free(m); + return rv; +} + +static Terase * +eraseload(Ftl *ftl, int x, ulong offset) +{ + union { + Merase; + uchar block[ERASEHDRLEN]; + } *m; + Terase *e; + uchar *p; + int i, nbam; + ulong bno, v; + + m = malloc(sizeof(*m)); + if(m == nil) + error(Enomem); + if(waserror()){ + free(m); + return nil; + } + getflash(ftl, m, offset, ERASEHDRLEN); + poperror(); + if(memcmp(m->orgtuple+3, "FTL100", 7) != 0 || + memcmp(m->serial, "Inf1", 4) != 0){ + free(m); +print("%8.8lux: bad sig\n", offset); + return nil; + } + e = malloc(sizeof(*e)); + if(e == nil){ + free(m); + error(Enomem); + } + e->x = x; + e->id = GET2(m->id); + e->offset = offset; + e->bamoffset = GET4(m->bamoffset); + e->nerase = GET4(m->nerase); + free(m); + m = nil; + USED(m); + if(e->bamoffset != BAMoffset){ + free(e); +print("%8.8lux: bad bamoffset %8.8lux\n", offset, e->bamoffset); + return nil; + } + e->bamoffset += offset; + if(e->id == XferID || e->id == XferBusy){ + e->bam = nil; + e->nbam = 0; + return e; + } + nbam = ftl->segsize/Bsize; + e->bam = malloc(nbam*sizeof(*e->bam)); + e->nbam = nbam; + if(waserror()){ + free(e); + nexterror(); + } + getflash(ftl, e->bam, e->bamoffset, nbam*4); + poperror(); + /* scan BAM to build VBM */ + e->bamx = 0; + for(i=0; ibam[i]; + e->bam[i] = v = GET4(p); + if(v == Bwriting || v == Bdeleted) + e->ndead++; + else if(v == Bfree){ + if(e->bamx == 0) + e->bamx = i; + e->nfree++; + ftl->nfree++; + }else{ + switch(v & BlockType){ + case ControlBlock: + break; + case DataBlock: + /* add to VBM */ + if(v & (1<<31)) + break; /* negative => VBM page, ignored */ + bno = BNO(v & ~BlockType); + if(i < ftl->fstart || bno >= ftl->nblock){ + print("ftl: unit %d:#%x bad bam[%d]=#%lux\n", e->x, e->id, i, v); + e->nbad++; + break; + } + ftl->vbm[bno] = (e->x<<16) | i; + e->nused++; + break; + case ReplacePage: + /* replacement VBM page; ignored */ + break; + default: + print("ftl: unit %d:#%x bad bam[%d]=%lux\n", e->x, e->id, i, v); + case BadBlock: + e->nbad++; + break; + } + } + } + return e; +} + +static void +erasefree(Terase *e) +{ + if(e){ + free(e->bam); + free(e); + } +} + +static void +eraseflash(Ftl *ftl, ulong offset) +{ + char cmd[40]; + + offset += ftl->base; + if(FTLDEBUG || ftl->trace) + print("ftl: erase seg @#%lux\n", offset); + snprint(cmd, sizeof(cmd), "erase 0x%8.8lux", offset); + if(kchanio(ftl->flashctl, cmd, strlen(cmd), OWRITE) <= 0){ + print("ftl: erase failed: %s\n", up->env->errstr); + error(up->env->errstr); + } +} + +static void +putflash(Ftl *ftl, ulong offset, void *buf, long n) +{ + offset += ftl->base; + if(ftl->trace) + print("ftl: write(#%lux, %ld)\n", offset, n); + ftl->flash->offset = offset; + if(kchanio(ftl->flash, buf, n, OWRITE) != n){ + print("ftl: flash write error: %s\n", up->env->errstr); + error(up->env->errstr); + } +} + +static void +getflash(Ftl *ftl, void *buf, ulong offset, long n) +{ + offset += ftl->base; + if(ftl->trace) + print("ftl: read(#%lux, %ld)\n", offset, n); + ftl->flash->offset = offset; + if(kchanio(ftl->flash, buf, n, OREAD) != n){ + print("ftl: flash read error %s\n", up->env->errstr); + error(up->env->errstr); + } +} + +Dev ftldevtab = { + 'X', /* TO DO */ + "ftl", + + devreset, + devinit, + devshutdown, + ftlattach, + ftlwalk, + ftlstat, + ftlopen, + devcreate, + ftlclose, + ftlread, + devbread, + ftlwrite, + devbwrite, + devremove, + devwstat, +}; -- cgit v1.2.3