shithub: purgatorio

ref: b7af62b250e5dff30320a181ca9d53ab5a7c276d
dir: /os/port/devftl.c/

View raw version
/*
 * 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"

typedef struct Ftl Ftl;
typedef struct Merase Merase;
typedef struct Terase Terase;

enum {
	Eshift = 18,	/* 2^18=256k; log2(eraseunit) */
	Flashseg = 1<<Eshift,
	Bshift = 9,		/* 2^9=512 */
	Bsize = 1<<Bshift,
	BAMoffset = 0x100,
	Nolimit = ~0,
	USABLEPCT = 95,	/* release only this % to client */

	FTLDEBUG = 0,

	NPART = 4,
};

/* erase unit header (defined by FTL specification) */
struct Merase {
	uchar	linktuple[5];
	uchar	orgtuple[10];
	uchar	nxfer;
	uchar	nerase[4];
	uchar	id[2];
	uchar	bshift;
	uchar	eshift;
	uchar	pstart[2];
	uchar	nunits[2];
	uchar	psize[4];
	uchar	vbmbase[4];
	uchar	nvbm[2];
	uchar	flags;
	uchar	code;
	uchar	serial[4];
	uchar	altoffset[4];
	uchar	bamoffset[4];
	uchar	rsv2[12];
};
#define	ERASEHDRLEN	64

enum {
	/* special unit IDs */
	XferID = 0xffff,
	XferBusy = 0x7fff,

	/* special BAM addresses */
	Bfree = 0xffffffff,
	Bwriting = 0xfffffffe,
	Bdeleted = 0,

	/* block types */
	TypeShift = 7,
	BlockType = (1<<TypeShift)-1,
	ControlBlock = 0x30,
	DataBlock = 0x40,
	ReplacePage = 0x60,
	BadBlock = 0x70,
};

#define	BTYPE(b)	((b) & BlockType)
#define	BADDR(b)	((b) & ~BlockType)
#define	BNO(va)	(((ulong)(va))>>Bshift)
#define	MKBAM(b,t)	(((b)<<Bshift)|(t))

struct Terase {
	int	x;
	int	id;
	ulong	offset;
	ulong	bamoffset;
	ulong	nbam;
	ulong*	bam;
	ulong	bamx;
	ulong	nfree;
	ulong	nused;
	ulong	ndead;
	ulong	nbad;
	ulong	nerase;
};

struct Ftl {
	QLock;
	Ref;

	Chan*	flash;
	Chan*	flashctl;
	ulong	base;	/* base of flash region */
	ulong	size;	/* size of flash region */
	ulong	segsize;	/* size of flash segment (erase unit) */
	int	eshift;	/* log2(erase-unit-size) */
	int	bshift;	/* log2(bsize) */
	int	bsize;
	int	nunit;	/* number of segments (erase units) */
	Terase**	unit;
	int	lastx;	/* index in unit of last allocation */
	int	xfer;		/* index in unit of current transfer unit (-1 if none) */
	ulong	nfree;	/* total free space in blocks */
	ulong	nblock;	/* total space in blocks */
	ulong	rwlimit;	/* user-visible block limit (`formatted size') */
	ulong*	vbm;		/* virtual block map */
	ulong	fstart;	/* address of first block of data in a segment */
	int	trace;	/* (debugging) trace of read/write actions */
	int	detach;	/* free Ftl on last close */

	/* scavenging variables */
	QLock	wantq;
	Rendez	wantr;
	Rendez	workr;
	int	needspace;
	int	hasproc;
	int	npart;		/* over and above ftldata */
	struct {
		ulong start, size;
		ulong rwlimit;
		char *name;	/* nil if slot unused */
	} part[NPART];
};

enum {
	/* Ftl.detach */
	Detached = 1,	/* detach on close */
	Deferred	/* scavenger must free it */
};

/* little endian */
#define	GET2(p)	(((p)[1]<<8)|(p)[0])
#define	GET4(p)	(((((((p)[3]<<8)|(p)[2])<<8)|(p)[1])<<8)|(p)[0])
#define	PUT2(p,v)	(((p)[1]=(v)>>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<<k) != segsize; k++)
					;
			}
			if(ftls != nil)
				error(Einuse);
			ftls = mkftl(fields[1], base, size, k, fields[0]);
		}else if(strcmp(fields[0], "scavenge") == 0){
			if(ftls != nil)
				print("scavenge %d\n", scavenge(ftls));
		}else if(strcmp(fields[0], "trace") == 0){
			if(ftls != nil)
				ftls->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<<i) != ea; i++)
			;
		if(eshift && i != eshift)
			print("ftl: overriding erasesize %d with %d\n", 1 << eshift, 1 << i);
		eshift = i;
		if(FTLDEBUG)
			print("ftl: e=%lud eshift=%d\n", ea, i);
	}
	if(eshift == 0)
		error("no erasesize");

	limit = ftlfsize(ftl->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<<eshift) > 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; i<ftl->nunit-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; i<ftl->nunit; 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; j<ftl->nunit; 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; i<ftl->nunit; 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; i<from->nbam; 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; i<from->nbam; 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; j<e->nbam; 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; i<nov; i += 4){
		p = bam+i;
		PUT4(p, ControlBlock);	/* reserve them */
	}
	putflash(ftl, offset+BAMoffset, bam, nov);
	free(bam);
}

static int
erasedetect(Ftl *ftl, ulong base, ulong size, ushort *pstart, ushort *nunits)
{
	ulong o;
	int rv;

	union {
		Merase;
		uchar	block[ERASEHDRLEN];
	} *m;
	m = malloc(sizeof(*m));
	if(m == nil)
		error(Enomem);
	rv  = 0;
	for(o = base; o < base + size; o += ftl->segsize){
		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; i<nbam; i++){
		p = (uchar*)&e->bam[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,
};