/*
* flash memory
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/error.h"
#include "../port/flashif.h"
typedef struct Flashtype Flashtype;
struct Flashtype {
char* name;
int (*reset)(Flash*);
Flashtype* next;
};
enum {
Nbanks = 2,
};
static struct
{
Flash* card[Nbanks]; /* actual card type, reset for access */
Flashtype* types; /* possible card types */
}flash;
enum{
Qtopdir,
Qflashdir,
Qdata,
Qctl,
};
#define TYPE(q) ((ulong)(q) & 0xFF)
#define PART(q) ((ulong)(q)>>8)
#define QID(p,t) (((p)<<8) | (t))
static Flashregion* flashregion(Flash*, ulong);
static char* flashnewpart(Flash*, char*, ulong, ulong);
static ulong flashaddr(Flash*, Flashpart*, char*);
static void protect(Flash*, ulong);
static void eraseflash(Flash*, Flashregion*, ulong);
static long readflash(Flash*, void*, long, int);
static long writeflash(Flash*, long, void*,int);
static char Eprotect[] = "flash region protected";
static int
flash2gen(Chan *c, ulong p, Dir *dp)
{
Flashpart *fp;
Flash *f;
Qid q;
int mode;
f = flash.card[c->dev];
fp = &f->part[PART(p)];
if(fp->name == nil)
return 0;
mkqid(&q, p, 0, QTFILE);
switch(TYPE(p)){
case Qdata:
mode = 0660;
if(f->write == nil)
mode = 0440;
devdir(c, q, fp->name, fp->end-fp->start, eve, mode, dp);
return 1;
case Qctl:
snprint(up->genbuf, sizeof(up->genbuf), "%sctl", fp->name);
/* no harm in letting everybody read the ctl files */
devdir(c, q, up->genbuf, 0, eve, 0664, dp);
return 1;
default:
return -1;
}
}
static int
flashgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp)
{
Qid q;
char *n;
if(s == DEVDOTDOT){
mkqid(&q, QID(0, Qtopdir), 0, QTDIR);
n = "#F";
if(c->dev != 0){
snprint(up->genbuf, sizeof up->genbuf, "#F%ld", c->dev);
n = up->genbuf;
}
devdir(c, q, n, 0, eve, 0555, dp);
return 1;
}
switch(TYPE(c->qid.path)){
case Qtopdir:
if(s != 0)
break;
mkqid(&q, QID(0, Qflashdir), 0, QTDIR);
n = "flash";
if(c->dev != 0){
snprint(up->genbuf, sizeof up->genbuf, "flash%ld",
c->dev);
n = up->genbuf;
}
devdir(c, q, n, 0, eve, 0555, dp);
return 1;
case Qflashdir:
if(s >= 2*nelem(flash.card[c->dev]->part))
return -1;
return flash2gen(c, QID(s>>1, s&1?Qctl:Qdata), dp);
case Qctl:
case Qdata:
return flash2gen(c, (ulong)c->qid.path, dp);
}
return -1;
}
static void
flashreset(void)
{
Flash *f;
Flashtype *t;
char *e;
int bank;
for(bank = 0; bank < Nbanks; bank++){
f = malloc(sizeof(*f));
if(f == nil){
print("#F%d: can't allocate Flash data\n", bank);
return;
}
f->cmask = ~(ulong)0;
if(archflashreset(bank, f) < 0 || f->type == nil ||
f->addr == nil){
free(f);
return;
}
for(t = flash.types; t != nil; t = t->next)
if(strcmp(f->type, t->name) == 0)
break;
if(t == nil){
iprint("#F%d: no flash driver for type %s (addr %p)\n",
bank, f->type, f->addr);
free(f);
return;
}
f->reset = t->reset;
f->protect = 1;
if(f->reset(f) == 0){
flash.card[bank] = f;
iprint("#F%d: %s addr %#p len %lud width %d interleave %d\n",
// bank, f->type, PADDR(f->addr), f->size,
bank, f->type, f->addr, f->size,
f->width, f->interleave);
e = flashnewpart(f, "flash", 0, f->size);
if(e != nil)
panic("#F%d: couldn't init table: %s", bank, e);
}else
iprint("#F%d: %#p: reset failed (%s)\n",
bank, f->addr, f->type);
}
}
static Chan*
flashattach(char *spec)
{
Flash *f;
int bank;
Chan *c;
bank = strtol(spec, nil, 0);
if(bank < 0 || bank >= Nbanks ||
(f = flash.card[bank]) == nil ||
f->attach != nil && f->attach(f) < 0)
error(Enodev);
c = devattach('F', spec);
c->dev = bank;
return c;
}
static Walkqid*
flashwalk(Chan *c, Chan *nc, char **name, int nname)
{
return devwalk(c, nc, name, nname, nil, 0, flashgen);
}
static int
flashstat(Chan *c, uchar *dp, int n)
{
return devstat(c, dp, n, nil, 0, flashgen);
}
static Chan*
flashopen(Chan *c, int omode)
{
omode = openmode(omode);
switch(TYPE(c->qid.path)){
case Qdata:
case Qctl:
if(flash.card[c->dev] == nil)
error(Enodev);
break;
}
return devopen(c, omode, nil, 0, flashgen);
}
static void
flashclose(Chan*)
{
}
static long
flashread(Chan *c, void *buf, long n, vlong offset)
{
Flash *f;
Flashpart *fp;
Flashregion *r;
int i;
ulong start, end;
char *s, *o;
if(c->qid.type & QTDIR)
return devdirread(c, buf, n, nil, 0, flashgen);
f = flash.card[c->dev];
fp = &f->part[PART(c->qid.path)];
if(fp->name == nil)
error(Egreg);
switch(TYPE(c->qid.path)){
case Qdata:
offset += fp->start;
if(offset >= fp->end)
return 0;
if(offset+n > fp->end)
n = fp->end - offset;
n = readflash(f, buf, offset, n);
if(n < 0)
error(Eio);
return n;
case Qctl:
s = malloc(READSTR);
if(s == nil)
error(Enomem);
if(waserror()){
free(s);
nexterror();
}
o = seprint(s, s+READSTR, "%#2.2ux %#4.4ux %d %q\n",
f->id, f->devid, f->width, f->sort!=nil? f->sort: "nor");
for(i=0; i<f->nr; i++){
r = &f->regions[i];
if(r->start < fp->end && fp->start < r->end){
start = r->start;
if(fp->start > start)
start = fp->start;
end = r->end;
if(fp->end < end)
end = fp->end;
o = seprint(o, s+READSTR, "%#8.8lux %#8.8lux %#8.8lux",
start, end, r->erasesize);
if(r->pagesize)
o = seprint(o, s+READSTR, " %#8.8lux",
r->pagesize);
o = seprint(o, s+READSTR, "\n");
}
}
n = readstr(offset, buf, n, s);
poperror();
free(s);
return n;
}
error(Egreg);
return 0; /* not reached */
}
enum {
CMerase,
CMadd,
CMremove,
CMsync,
CMprotectboot,
};
static Cmdtab flashcmds[] = {
{CMerase, "erase", 2},
{CMadd, "add", 0},
{CMremove, "remove", 2},
{CMsync, "sync", 0},
{CMprotectboot, "protectboot", 0},
};
static long
flashwrite(Chan *c, void *buf, long n, vlong offset)
{
Cmdbuf *cb;
Cmdtab *ct;
ulong addr, start, end;
char *e;
Flashpart *fp;
Flashregion *r;
Flash *f;
f = flash.card[c->dev];
fp = &f->part[PART(c->qid.path)];
if(fp->name == nil)
error(Egreg);
switch(TYPE(c->qid.path)){
case Qdata:
if(f->write == nil)
error(Eperm);
offset += fp->start;
if(offset >= fp->end)
return 0;
if(offset+n > fp->end)
n = fp->end - offset;
n = writeflash(f, offset, buf, n);
if(n < 0)
error(Eio);
return n;
case Qctl:
cb = parsecmd(buf, n);
if(waserror()){
free(cb);
nexterror();
}
ct = lookupcmd(cb, flashcmds, nelem(flashcmds));
switch(ct->index){
case CMerase:
if(strcmp(cb->f[1], "all") != 0){
addr = flashaddr(f, fp, cb->f[1]);
r = flashregion(f, addr);
if(r == nil)
error("nonexistent flash region");
if(addr%r->erasesize != 0)
error("invalid erase block address");
eraseflash(f, r, addr);
}else if(fp->start == 0 && fp->end == f->size &&
f->eraseall != nil){
eraseflash(f, nil, 0);
}else{
for(addr = fp->start; addr < fp->end;
addr += r->erasesize){
r = flashregion(f, addr);
if(r == nil)
error("nonexistent flash region");
if(addr%r->erasesize != 0)
error("invalid erase block address");
eraseflash(f, r, addr);
}
}
break;
case CMadd:
if(cb->nf < 3)
error(Ebadarg);
start = flashaddr(f, fp, cb->f[2]);
if(cb->nf > 3 && strcmp(cb->f[3], "end") != 0)
end = flashaddr(f, fp, cb->f[3]);
else
end = fp->end;
if(start > end || start >= fp->end || end > fp->end)
error(Ebadarg);
e = flashnewpart(f, cb->f[1], start, end);
if(e != nil)
error(e);
break;
case CMremove:
/* TO DO */
break;
case CMprotectboot:
if(cb->nf > 1 && strcmp(cb->f[1], "off") == 0)
f->protect = 0;
else
f->protect = 1;
break;
case CMsync:
/* TO DO? */
break;
default:
error(Ebadarg);
}
poperror();
free(cb);
return n;
}
error(Egreg);
return 0; /* not reached */
}
static char*
flashnewpart(Flash *f, char *name, ulong start, ulong end)
{
Flashpart *fp, *empty;
int i;
empty = nil;
for(i = 0; i < nelem(f->part); i++){
fp = &f->part[i];
if(fp->name == nil){
if(empty == nil)
empty = fp;
}else if(strcmp(fp->name, name) == 0)
return Eexist;
}
if((fp = empty) == nil)
return "partition table full";
// fp->name = nil;
kstrdup(&fp->name, name);
if(fp->name == nil)
return Enomem;
fp->start = start;
fp->end = end;
return nil;
}
static ulong
flashaddr(Flash *f, Flashpart *fp, char *s)
{
Flashregion *r;
ulong addr;
addr = strtoul(s, &s, 0);
if(*s)
error(Ebadarg);
if(fp->name == nil)
error("partition removed");
addr += fp->start;
r = flashregion(f, addr);
if(r != nil && addr%r->erasesize != 0)
error("invalid erase unit address");
if(addr < fp->start || addr > fp->end || addr > f->size)
error(Ebadarg);
return addr;
}
static Flashregion*
flashregion(Flash *f, ulong a)
{
int i;
Flashregion *r;
for(i=0; i<f->nr; i++){
r = &f->regions[i];
if(r->start <= a && a < r->end)
return r;
}
return nil;
}
Dev flashdevtab = {
'F',
"flash",
flashreset,
devinit,
devshutdown,
flashattach,
flashwalk,
flashstat,
flashopen,
devcreate,
flashclose,
flashread,
devbread,
flashwrite,
devbwrite,
devremove,
devwstat,
};
/*
* called by flash card types named in link section (eg, flashamd.c)
*/
void
addflashcard(char *name, int (*reset)(Flash*))
{
Flashtype *f, **l;
f = (Flashtype*)malloc(sizeof(*f));
if(f == nil)
error(Enomem);
f->name = name;
f->reset = reset;
f->next = nil;
for(l = &flash.types; *l != nil; l = &(*l)->next)
;
*l = f;
}
static long
readflash(Flash *f, void *buf, long offset, int n)
{
int r, width, wmask;
uchar tmp[16];
uchar *p;
ulong o;
if(offset < 0 || offset+n > f->size)
error(Ebadarg);
qlock(f);
if(waserror()){
qunlock(f);
nexterror();
}
if(f->read != nil){
width = f->width;
wmask = width-1;
p = buf;
if(offset & wmask) {
o = offset & ~wmask;
if(f->read(f, o, (ulong*)tmp, width) < 0)
error(Eio);
memmove(tmp, (uchar*)f->addr + o, width);
for(; n > 0 && offset & wmask; n--)
*p++ = tmp[offset++ & wmask];
}
r = n & wmask;
n &= ~wmask;
if(n){
if(f->read(f, offset, (ulong*)p, n) < 0)
error(Eio);
offset += n;
p += n;
}
if(r){
if(f->read(f, offset, (ulong*)tmp, width))
error(Eio);
memmove(p, tmp, r);
}
}else
/* assumes hardware supports byte access */
memmove(buf, (uchar*)f->addr+offset, n);
poperror();
qunlock(f);
return n;
}
static long
writeflash(Flash *f, long offset, void *buf, int n)
{
uchar tmp[16];
uchar *p;
ulong o;
int r, width, wmask;
Flashregion *rg;
if(f->write == nil || offset < 0 || offset+n > f->size)
error(Ebadarg);
rg = flashregion(f, offset);
if(f->protect && rg != nil && rg->start == 0 && offset < rg->erasesize)
error(Eprotect);
width = f->width;
wmask = width-1;
qlock(f);
archflashwp(f, 0);
if(waserror()){
archflashwp(f, 1);
qunlock(f);
nexterror();
}
p = buf;
if(offset&wmask){
o = offset & ~wmask;
if(f->read != nil){
if(f->read(f, o, tmp, width) < 0)
error(Eio);
}else
memmove(tmp, (uchar*)f->addr+o, width);
for(; n > 0 && offset&wmask; n--)
tmp[offset++&wmask] = *p++;
if(f->write(f, o, tmp, width) < 0)
error(Eio);
}
r = n&wmask;
n &= ~wmask;
if(n){
if(f->write(f, offset, p, n) < 0)
error(Eio);
offset += n;
p += n;
}
if(r){
if(f->read != nil){
if(f->read(f, offset, tmp, width) < 0)
error(Eio);
}else
memmove(tmp, (uchar*)f->addr+offset, width);
memmove(tmp, p, r);
if(f->write(f, offset, tmp, width) < 0)
error(Eio);
}
poperror();
archflashwp(f, 1);
qunlock(f);
return n;
}
static void
eraseflash(Flash *f, Flashregion *r, ulong addr)
{
int rv;
if(f->protect && r != nil && r->start == 0 && addr < r->erasesize)
error(Eprotect);
qlock(f);
archflashwp(f, 0);
if(waserror()){
archflashwp(f, 1);
qunlock(f);
nexterror();
}
if(r == nil){
if(f->eraseall != nil)
rv = f->eraseall(f);
else
rv = -1;
}else
rv = f->erasezone(f, r, addr);
if(rv < 0)
error(Eio);
poperror();
archflashwp(f, 1);
qunlock(f);
}
/*
* flash access taking width and interleave into account
*/
int
flashget(Flash *f, ulong a)
{
switch(f->width){
default:
return ((uchar*)f->addr)[a<<f->bshift];
case 2:
return ((ushort*)f->addr)[a];
case 4:
return ((ulong*)f->addr)[a];
}
}
void
flashput(Flash *f, ulong a, int v)
{
switch(f->width){
default:
((uchar*)f->addr)[a<<f->bshift] = v;
break;
case 2:
((ushort*)f->addr)[a] = v;
break;
case 4:
((ulong*)f->addr)[a] = v;
break;
}
coherence();
}
|