/* * 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 voidftlpartcmd(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 voidftldelpartcmd(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 intftlgen(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 voidftlput(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 intftlstat(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)){Lines 1–500 of 1361. Load more
/* * 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)){