shithub: purgatorio

ref: db1eb844461b07a25ca49117851fa874fd88e065
dir: /appl/cmd/disk/ftl.b/

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@caldo.demon.co.uk)
#	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 (byteles@vitanuova.com)
#
# C H Forsyth and Vita Nuova Limited expressly allow Lucent Technologies
# to use this driver freely for any Inferno-related purposes whatever,
# including commercial applications.
#
# TO DO:
#	check error handling details for get/put flash
#	bad block handling
#	reserved space in formatted size
#	possibly block size as parameter
#	fetch parameters from header on init
#
# Adapted to a ftl formatter for Inferno 2000 by J R Firth, Vita Nuova Limited
#	usage : ftl flashsize secsize inputfile outputfile
# outputfile will then be a ftl image of inputfile
# nb assumes the base address is zero
#
# Converted to limbo for Inferno 2000 by JR Firth, Vita Nuova Holdings Limited
#

implement Ftlimage;

include "sys.m";
include "draw.m";

sys : Sys;
	OREAD, OWRITE, FD, open, create, read, write, print, fprint : import sys;

Ftlimage : module
{
	init : fn(nil : ref Draw->Context, argv : list of string);
};

stderr : ref FD;

flashsize, secsize : int;
flashm : array of byte;
trace : int = 0;

Eshift : con 18;			# 2^18=256k; log2(eraseunit)
Flashseg : con 1<<Eshift;
Bshift : con 9;			# 2^9=512
Bsize : con 1<<Bshift;
BAMoffset : con 16r100;
Nolimit : con ~0;
USABLEPCT : con 95;	# release only this % to client

FTLDEBUG : con 0;

# erase unit header (defined by FTL specification)
# offsets into Merase
O_LINKTUPLE : con 0;
O_ORGTUPLE : con 5;
O_NXFER : con 15;
O_NERASE : con 16;
O_ID : con 20;
O_BSHIFT : con 22;
O_ESHIFT : con 23;
O_PSTART : con 24;
O_NUNITS : con 26;
O_PSIZE : con 28;
O_VBMBASE : con 32;
O_NVBM : con 36;
O_FLAGS : con 38;
O_CODE : con 39;
O_SERIAL : con 40;
O_ALTOFFSET : con 44;
O_BAMOFFSET : con 48;
O_RSV2 : con 52;

ERASEHDRLEN : con	64;

# special unit IDs
XferID : con 16rffff;
XferBusy : con 16r7fff;

# special BAM addresses
Bfree : con -1;	#16rffffffff
Bwriting : con -2; #16rfffffffe
Bdeleted : con 0;

# block types
TypeShift : con 7;
BlockType : con (1<<TypeShift)-1;
ControlBlock : con 16r30;
DataBlock : con 16r40;
ReplacePage : con 16r60;
BadBlock : con 16r70;

BNO(va : int) : int
{
	return va>>Bshift;
}
MKBAM(b : int,t : int) : int
{
	return (b<<Bshift)|t;
}

Terase : adt {
	x : int;
	id : int;
	offset : int;
	bamoffset : int;
	nbam : int;
	bam : array of byte;
	bamx : int;
	nfree : int;
	nused : int;
	ndead : int;
	nbad : int;
	nerase : int;
};

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

# Ftl.detach 
Detached : con 1;	# detach on close 
Deferred : con 2;	# scavenger must free it 

ftls : ref Ftl;

ftlstat(sz : int)
{
	print("16r%x:16r%x:16r%x\n", ftls.rwlimit*Bsize, sz, flashsize);
	print("%d:%d:%d in 512b blocks\n", ftls.rwlimit, sz>>Bshift, flashsize>>Bshift);
}
	 
ftlread(buf : array of byte, n : int, offset : int) : int
{
	ftl : ref Ftl;
	e : ref Terase;
	nb : int;
	a : int;
	pb : int;
	mapb : int;

	if(n <= 0 || n%Bsize || offset%Bsize) {
		fprint(stderr, "ftl: bad read\n");
		exit;
	}
	ftl = ftls;
	nb = n/Bsize;
	offset /= Bsize;
	if(offset >= ftl.rwlimit)
		return 0;
	if(offset+nb > ftl.rwlimit)
		nb = ftl.rwlimit - offset;
	a = 0;
	for(n = 0; n < nb; n++){
		(mapb, e, pb) = mapblk(ftl, offset+n);
		if(mapb)
			getflash(ftl, buf[a:], e.offset + pb*Bsize, Bsize);
		else
			memset(buf[a:], 0, Bsize);
		a += Bsize;
	}
	return a;
}

ftlwrite(buf : array of byte, n : int, offset : int) : int
{
	ns, nb : int;
	a : int;
	e, oe : ref Terase;
	ob, v : int;
	ftl : ref Ftl;
	mapb : int;

	if(n <= 0)
		return 0;
	ftl = ftls;
	if(n <= 0 || n%Bsize || offset%Bsize) {
		fprint(stderr, "ftl: bad write\n");
		exit;
	}
	nb = n/Bsize;
	offset /= Bsize;
	if(offset >= ftl.rwlimit)
		return 0;
	if(offset+nb > ftl.rwlimit)
		nb = ftl.rwlimit - offset;
	a = 0;
	for(n = 0; n < nb; n++){
		ns = 0;
		while((v = allocblk(ftl)) == 0)
			if(!scavenge(ftl) || ++ns > 3){
				fprint(stderr, "ftl: flash memory full\n");
			}
		(mapb, oe, ob) = mapblk(ftl, offset+n);
		if(!mapb)
			oe = nil;
		e = ftl.unit[v>>16];
		v &= 16rffff;
		putflash(ftl, e.offset + v*Bsize, buf[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++;
		}
		a += Bsize;
	}
	return a;
}

mkftl(fname : string, base : int, size : int, eshift : int, op : string) : ref Ftl
{
	i, j, nov, segblocks : int;
	limit : int;
	e : ref Terase;

	ftl := ref Ftl;
	ftl.lastx = 0;
	ftl.detach = 0;
	ftl.needspace = 0;
	ftl.hasproc = 0;
	ftl.trace = 0;
	limit = flashsize;
	if(size == Nolimit)
		size = limit-base;
	if(base >= limit || size > limit || base+size > limit || eshift < 8 || (1<<eshift) > size) {
		fprint(stderr, "bad flash space parameters");
		exit;
	}
	if(FTLDEBUG || ftl.trace || trace)
		print("%s flash %s #%x:#%x limit #%x\n", op, fname, base, size, limit);
	ftl.base = base;
	ftl.size = size;
	ftl.bshift = Bshift;
	ftl.bsize = Bsize;
	ftl.eshift = eshift;
	ftl.segsize = 1<<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 >= 16r10000)
		ftl.nblock = 16r10000;
	ftl.vbm = array[ftl.nblock] of int; 
	ftl.unit = array[ftl.nunit] of ref Terase;
	if(ftl.vbm == nil || ftl.unit == nil) {
		fprint(stderr, "out of mem");
		exit;
	}
	for(i=0; i<ftl.nblock; i++)
		ftl.vbm[i] = 0;
	if(op == "format"){
		for(i=0; i<ftl.nunit-1; i++)
			eraseinit(ftl, i*ftl.segsize, i, 1);
		eraseinit(ftl, i*ftl.segsize, XferID, 1);
	}
	ftl.xfer = -1;
	for(i=0; i<ftl.nunit; i++){
		e = eraseload(ftl, i, i*ftl.segsize);
		if(e == nil){
			fprint(stderr, "ftl: logical segment %d: bad format\n", i);
			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){
				fprint(stderr, "ftl: duplicate erase unit #%x\n", e.id);
				erasefree(e);
				e = nil;
				break;
			}
		if(e != nil){
			ftl.unit[e.x] = e;
			if(e.id == XferID)
				ftl.xfer = e.x;
			if (FTLDEBUG || ftl.trace || trace)
				fprint(stderr, "ftl: unit %d:#%x used %d free %d dead %d bad %d nerase %d\n",
					e.x, e.id, e.nused, e.nfree, e.ndead, e.nbad, e.nerase);
		}
	}
	if(ftl.xfer < 0 && ftl.nunit <= 0 || ftl.xfer >= 0 && ftl.nunit <= 1) {
		fprint(stderr, "ftl: no valid flash data units");
		exit;
	}
	if(ftl.xfer < 0)
		fprint(stderr, "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*USABLEPCT/100;
	return ftl;
}

ftlfree(ftl : ref Ftl)
{
	if(ftl != nil){
		ftl.unit = nil;
		ftl.vbm = nil;
		ftl = nil;
	}
}

#
# this simple greedy algorithm weighted by nerase does seem to lead
# to even wear of erase units (cf. the eNVy file system)
#
 
bestcopy(ftl : ref Ftl) : ref Terase
{
	e, be : ref Terase;
	i : int;

	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;
}

copyunit(ftl : ref Ftl, from : ref Terase, too : ref Terase) : int
{
	i, nb : int;
	id := array[2] of byte;
	bam : array of byte;
	buf : array of byte;
	v, bno : int;

	if(FTLDEBUG || ftl.trace || trace)
		print("ftl: copying %d (#%x) to #%x\n", from.id, from.offset, too.offset);
	too.nbam = 0;
	too.bam = nil;
	bam = nil;
	buf = array[Bsize] of byte;
	if(buf == nil)
		return 0;
	PUT2(id, XferBusy);
	putflash(ftl, too.offset+O_ID, id, 2);
	# make new BAM 
	nb = from.nbam*4;
	bam = array[nb] of byte;
	memmove(bam, from.bam, nb);
	too.nused = 0;
	too.nbad = 0;
	too.nfree = 0;
	too.ndead = 0;
	for(i = 0; i < from.nbam; i++)
		bv := GET4(bam[4*i:]);
		case(bv){
		Bwriting or
		Bdeleted or
		Bfree =>
			PUT4(bam[4*i:], Bfree);
			too.nfree++;
			break;
		* =>
			case(bv&BlockType){
			DataBlock or
			ReplacePage =>
				v = bv;
				bno = BNO(v & ~BlockType);
				if(i < ftl.fstart || bno >= ftl.nblock){
					print("ftl: unit %d:#%x bad bam[%d]=#%x\n", from.x, from.id, i, v);
					too.nfree++;
					PUT4(bam[4*i:], Bfree);
					break;
				}
				getflash(ftl, buf, from.offset+i*Bsize, Bsize);
				putflash(ftl, too.offset+i*Bsize, buf, Bsize);
				too.nused++;
				break;
			ControlBlock =>
				too.nused++;
				break;
			* =>
				# case BadBlock:	# it isn't necessarily bad in this unit 
				too.nfree++;
				PUT4(bam[4*i:], Bfree);
				break;
			}
		}
	# for(i=0; i<from.nbam; i++){
	#	v = GET4(bam[4*i:]);
	#	if(v != Bfree && ftl.trace > 1)
	#		print("to[%d]=#%x\n", i, v);
	#	PUT4(bam[4*i:], v);
	# }
	putflash(ftl, too.bamoffset, bam, nb);	# BUG: PUT4 ? IS IT ?
	# for(i=0; i<from.nbam; i++){
	#	v = GET4(bam[4*i:]);
	#	PUT4(bam[4*i:], v);
	# }
	too.id = from.id;
	PUT2(id, too.id);
	putflash(ftl, too.offset+O_ID, id, 2);
	too.nbam = from.nbam;
	too.bam = bam;
	ftl.nfree += too.nfree - from.nfree;
	buf = nil;
	return 1;
}

mustscavenge(a : ref Ftl) : int
{
	return a.needspace || a.detach == Deferred;
}

donescavenge(a : ref Ftl) : int
{
	return a.needspace == 0;
}

scavengeproc(arg : ref Ftl)
{
	ftl : ref Ftl;
	i : int;
	e, ne : ref Terase;

	ftl = arg;
	if(mustscavenge(ftl)){
		if(ftl.detach == Deferred){
			ftlfree(ftl);
			fprint(stderr, "scavenge out of memory\n");
			exit;
		}
		if(FTLDEBUG || ftl.trace || trace)
			print("ftl: scavenge %d\n", ftl.nfree);
		e = bestcopy(ftl);
		if(e == nil || ftl.xfer < 0 || (ne = ftl.unit[ftl.xfer]) == nil || ne.id != XferID || e == ne)
			;
		else 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;
			e.bam = nil;
			e.bamx = 0;
			e.nerase++;
			eraseinit(ftl, e.offset, XferID, e.nerase);
		}
		if(FTLDEBUG || ftl.trace || trace)
			print("ftl: end scavenge %d\n", ftl.nfree);
		ftl.needspace = 0;
	}
}

scavenge(ftl : ref Ftl) : int
{
	if(ftl.xfer < 0 || bestcopy(ftl) == nil)
		return 0;	# you worm! 

	if(!ftl.hasproc){
		ftl.hasproc = 1;
	}
	ftl.needspace = 1;

	scavengeproc(ftls);

	return ftl.nfree;
}

putbam(ftl : ref Ftl, e : ref Terase, n : int, entry : int)
{
	b := array[4] of byte;

	PUT4(e.bam[4*n:], entry);
	PUT4(b, entry);
	putflash(ftl, e.bamoffset + n*4, b, 4);
}

allocblk(ftl : ref Ftl) : int
{
	e : ref Terase;
	i, j : int;

	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(GET4(e.bam[4*j:])== Bfree){
					putbam(ftl, e, j, Bwriting);
					ftl.nfree--;
					e.nfree--;
					e.bamx = j+1;
					return (e.x<<16) | j;
				}
			e.nfree = 0;
			print("ftl: unit %d:#%x nfree %d but not free in BAM\n", e.x, e.id, e.nfree);
		}
		if(++i >= ftl.nunit)
			i = 0;
	}while(i != ftl.lastx);
	return 0;
}

mapblk(ftl : ref Ftl, bno : int) : (int, ref Terase, int)
{
	v : int;
	x : int;

	if(bno < ftl.nblock){
		v = ftl.vbm[bno];
		if(v == 0 || v == ~0)
			return (0, nil, 0);
		x = v>>16;
		if(x >= ftl.nunit || x == ftl.xfer || ftl.unit[x] == nil){
			print("ftl: corrupt format: bad block mapping %d . unit #%x\n", bno, x);
			return (0, nil, 0);
		}
		return (1, ftl.unit[x], v & 16rFFFF);
	}
	return (0, nil, 0);
}

eraseinit(ftl : ref Ftl, offset : int, id : int, nerase : int)
{
	m : array of byte;
	bam : array of byte;
	i, nov : int;

	nov = ((ftl.segsize/Bsize)*4 + BAMoffset + Bsize - 1)/Bsize;	# number of overhead blocks (header, and BAM itself) 
	if(nov*Bsize >= ftl.segsize) {
		fprint(stderr, "ftl -- too small for files");
		exit;
	}
	eraseflash(ftl, offset);
	m = array[ERASEHDRLEN] of byte;
	if(m == nil) {
		fprint(stderr, "nomem\n");
		exit;
	}
	memset(m, 16rFF, len m);
	m[O_LINKTUPLE+0] = byte 16r13;
	m[O_LINKTUPLE+1] = byte 16r3;
	memmove(m[O_LINKTUPLE+2:], array of byte "CIS", 3);
	m[O_ORGTUPLE+0] = byte 16r46;
	m[O_ORGTUPLE+1] = byte 16r57;
	m[O_ORGTUPLE+2] = byte 16r00;
	memmove(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7);
	m[O_NXFER] = byte 1;
	PUT4(m[O_NERASE:], nerase);
	PUT2(m[O_ID:], id);
	m[O_BSHIFT] = byte ftl.bshift;
	m[O_ESHIFT] = byte ftl.eshift;
	PUT2(m[O_PSTART:], 0);
	PUT2(m[O_NUNITS:], ftl.nunit);
	PUT4(m[O_PSIZE:], ftl.size - nov*Bsize);
	PUT4(m[O_VBMBASE:], -1);	# we always calculate the VBM (16rffffffff)
	PUT2(m[O_NVBM:], 0);
	m[O_FLAGS] = byte 0;
	m[O_CODE] = byte 16rFF;
	memmove(m[O_SERIAL:], array of byte "Inf1", 4);
	PUT4(m[O_ALTOFFSET:], 0);
	PUT4(m[O_BAMOFFSET:], BAMoffset);
	putflash(ftl, offset, m, ERASEHDRLEN);
	m = nil;
	if(id == XferID)
		return;
	nov *= 4;	# now bytes of BAM 
	bam = array[nov] of byte;
	if(bam == nil) {
		fprint(stderr, "nomem");
		exit;
	}
	for(i=0; i<nov; i += 4)
		PUT4(bam[i:], ControlBlock);	# reserve them 
	putflash(ftl, offset+BAMoffset, bam, nov);
	bam = nil;
}

eraseload(ftl : ref Ftl, x : int, offset : int) : ref Terase
{
	m : array of byte;
	e : ref Terase;
	i, nbam : int;
	bno, v : int;

	m = array[ERASEHDRLEN] of byte;
	if(m == nil) {
		fprint(stderr, "nomem");
		exit;
	}
	getflash(ftl, m, offset, ERASEHDRLEN);
	if(memcmp(m[O_ORGTUPLE+3:], array of byte "FTL100\0", 7) != 0 ||
	   memcmp(m[O_SERIAL:], array of byte "Inf1", 4) != 0){
		m = nil;
		return nil;
	}
	e = ref Terase;
	if(e == nil){
		m = nil;
		fprint(stderr, "nomem");
		exit;
	}
	e.x = x;
	e.id = GET2(m[O_ID:]);
	e.offset = offset;
	e.bamoffset = GET4(m[O_BAMOFFSET:]);
	e.nerase = GET4(m[O_NERASE:]);
	e.bamx = 0;
	e.nfree = 0;
	e.nused = 0;
	e.ndead = 0;
	e.nbad = 0;
	m = nil;
	if(e.bamoffset != BAMoffset){
		e = nil;
		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 = array[4*nbam] of byte;
	e.nbam = nbam;
	getflash(ftl, e.bam, e.bamoffset, nbam*4);
	# scan BAM to build VBM 
	e.bamx = 0;
	for(i=0; i<nbam; i++){
		v = GET4(e.bam[4*i:]);
		if(v == Bwriting || v == Bdeleted)
			e.ndead++;
		else if(v == Bfree){
			if(e.bamx == 0)
				e.bamx = i;
			e.nfree++;
			ftl.nfree++;
		}else{
			case(v & BlockType){
			ControlBlock =>
				break;
			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]=#%x\n", e.x, e.id, i, v);
					e.nbad++;
					break;
				}
				ftl.vbm[bno] = (e.x<<16) | i;
				e.nused++;
				break;
			ReplacePage =>
				# replacement VBM page; ignored 
				break;
			BadBlock =>
				e.nbad++;
				break;
			* =>
				print("ftl: unit %d:#%x bad bam[%d]=%x\n", e.x, e.id, i, v);
			}
		}
	}
	return e;
}

erasefree(e : ref Terase)
{
	e.bam = nil;
	e = nil;
}

eraseflash(ftl : ref Ftl, offset : int)
{
	offset += ftl.base;
	if(FTLDEBUG || ftl.trace || trace)
		print("ftl: erase seg @#%x\n", offset);
	memset(flashm[offset:], 16rff, secsize);
}

putflash(ftl : ref Ftl, offset : int, buf : array of byte, n : int)
{
	offset += ftl.base;
	if(ftl.trace || trace)
		print("ftl: write(#%x, %d)\n", offset, n);
	memmove(flashm[offset:], buf, n);
}

getflash(ftl : ref Ftl, buf : array of byte, offset : int, n : int)
{
	offset += ftl.base;
	if(ftl.trace || trace)
		print("ftl: read(#%x, %d)\n", offset, n);
	memmove(buf, flashm[offset:], n);
}

BUFSIZE : con 8192;

main(argv : list of string)
{
	k, r, sz, offset : int = 0;
	buf, buf1 : array of byte;
	fd1, fd2 : ref FD;

	if (len argv != 5) {
		fprint(stderr, "usage: %s flashsize secsize kfsfile flashfile\n", hd argv);
		exit;
	}
	flashsize = atoi(hd tl argv);
	secsize = atoi(hd tl tl argv);
	fd1 = open(hd tl tl tl argv, OREAD);
	fd2 = create(hd tl tl tl tl argv, OWRITE, 8r644);
	if (fd1 == nil || fd2 == nil) {
		fprint(stderr, "bad io files\n");
		exit;
	}
	if(secsize == 0 || secsize > flashsize || secsize&(secsize-1) || 0&(secsize-1) || flashsize == 0 || flashsize != Nolimit && flashsize&(secsize-1)) {
		fprint(stderr, "ftl: bad sizes\n");
		exit;
	}
	for(k=0; k<32 && (1<<k) != secsize; k++)
			;
	flashm = array[flashsize] of byte;
	buf = array[BUFSIZE] of byte;
	if (flashm == nil) {
		fprint(stderr, "ftl: no mem for flash\n");
		exit;
	}
	ftls = mkftl("FLASH", 0, Nolimit, k, "format");
	for (;;) {
		r = read(fd1, buf, BUFSIZE);
		if (r <= 0)
			break;
		if (ftlwrite(buf, r, offset) != r) {
			fprint(stderr, "ftl: ftlwrite failed - input file too big\n");
			exit;
		}
		offset += r;
	}
	write(fd2, flashm, flashsize);
	fd1 = fd2 = nil;
	ftlstat(offset);
	# ftls = mkftl("FLASH", 0, Nolimit, k, "init"); 
	sz = offset;
	offset = 0;
	buf1 = array[BUFSIZE] of byte;
	fd1 = open(hd tl tl tl argv, OREAD);
	for (;;) {
		r = read(fd1, buf1, BUFSIZE);
		if (r <= 0)
			break;
		if (ftlread(buf, r, offset) != r) {
			fprint(stderr, "ftl: ftlread failed\n");
			exit;
		}
		if (memcmp(buf, buf1, r) != 0) {
			fprint(stderr, "ftl: bad read\n");
			exit;
		}
		offset += r;
	}
	fd1 = nil;
	if (offset != sz) {
		fprint(stderr, "ftl: bad final offset\n");
		exit;
	}
	exit;
}

init(nil : ref Draw->Context, argl : list of string)
{
	sys = load Sys Sys->PATH;
	stderr = sys->fildes(2);
	main(argl);
}

memset(d : array of byte, v : int, n : int)
{
	for (i := 0; i < n; i++)
		d[i] = byte v;
}

memmove(d : array of byte, s : array of byte, n : int)
{
	d[0:] = s[0:n];
}

memcmp(s1 : array of byte, s2 : array of byte, n : int) : int
{
	for (i := 0; i < n; i++) {
		if (s1[i] < s2[i])
			return -1;
		if (s1[i] > s2[i])
			return 1;
	}
	return 0;
}

atoi(s : string) : int
{
	v : int;
	base := 10;
	n := len s;
	neg := 0;

	for (i := 0; i < n && (s[i] == ' ' || s[i] == '\t'); i++)
		;
	if (s[i] == '+' || s[i] == '-') {
		if (s[i] == '-')
			neg = 1;
		i++;
	}
	if (n-i >= 2 && s[i] == '0' && s[i+1] == 'x') {
		base = 16;
		i += 2;
	}
	else if (n-i >= 1 && s[i] == '0') {
		base = 8;
		i++;
	}
	m := 0;
	for(; i < n; i++) {
		c := s[i];
		case c {
		'a' to 'z' =>
			v = c - 'a' + 10;
		'A' to 'Z' =>
			v = c - 'A' + 10;
		'0' to '9' =>
			v = c - '0';
		* =>
			fprint(stderr, "ftl: bad character in number %s\n", s);
			exit;
		}
		if(v >= base) {
			fprint(stderr, "ftl: character too big for base in %s\n", s);
			exit;
		}
		m = m * base + v;
	}
	if(neg)
		m = -m;
	return m;
}

# little endian 

GET2(b : array of byte) : int
{
	return ((int b[1]) << 8) | (int b[0]);
}

GET4(b : array of byte) : int
{
	return ((int b[3]) << 24) | ((int b[2]) << 16) | ((int b[1]) << 8) | (int b[0]);
}

PUT2(b : array of byte, v : int)
{
	b[1] = byte (v>>8);
	b[0] = byte v;
}

PUT4(b : array of byte, v : int)
{
	b[3] = byte (v>>24);
	b[2] = byte (v>>16);
	b[1] = byte (v>>8);
	b[0] = byte v;
}