]> git.lizzy.rs Git - plan9front.git/blob - sys/src/9/bitsy/devflash.c
merge
[plan9front.git] / sys / src / 9 / bitsy / devflash.c
1 #include        "u.h"
2 #include        "../port/lib.h"
3 #include        "mem.h"
4 #include        "dat.h"
5 #include        "fns.h"
6 #include        "io.h"
7 #include        "../port/error.h"
8
9 /*
10  *  on the bitsy, all 32 bit accesses to flash are mapped to two 16 bit
11  *  accesses, one to the low half of the chip and the other to the high
12  *  half.  Therefore for all command accesses, ushort indices in the
13  *  manuals turn into ulong indices in our code.  Also, by copying all
14  *  16 bit commands to both halves of a 32 bit command, we erase 2
15  *  sectors for each request erase request.
16  */
17
18 #define mirror(x) (((x)<<16)|(x))
19
20 /* this defines a contiguous set of erase blocks of one size */
21 typedef struct FlashRegion FlashRegion;
22 struct FlashRegion
23 {
24         ulong   addr;           /* start of region */
25         ulong   end;            /* end of region + 1 */
26         ulong   n;              /* number of blocks */
27         ulong   size;           /* size of each block */
28 };
29
30 /* this defines a particular access algorithm */
31 typedef struct FlashAlg FlashAlg;
32 struct FlashAlg
33 {
34         int     id;
35         char    *name;
36         void    (*identify)(void);      /* identify device */
37         void    (*erase)(ulong);        /* erase a region */
38         void    (*write)(void*, long, ulong);   /* write a region */
39 };
40
41 static void     ise_id(void);
42 static void     ise_erase(ulong);
43 static void     ise_write(void*, long, ulong);
44
45 static void     afs_id(void);
46 static void     afs_erase(ulong);
47 static void     afs_write(void*, long, ulong);
48
49 static ulong    blockstart(ulong);
50 static ulong    blockend(ulong);
51
52 FlashAlg falg[] =
53 {
54         { 1,    "Intel/Sharp Extended", ise_id, ise_erase, ise_write    },
55         { 2,    "AMD/Fujitsu Standard", afs_id, afs_erase, afs_write    },
56 };
57
58 struct
59 {
60         RWlock;
61         ulong           *p;
62         ushort          algid;          /* access algorithm */
63         FlashAlg        *alg;
64         ushort          manid;          /* manufacturer id */
65         ushort          devid;          /* device id */
66         ulong           size;           /* size in bytes */
67         int             wbsize;         /* size of write buffer */ 
68         ulong           nr;             /* number of regions */
69         uchar           bootprotect;
70         FlashRegion     r[32];
71 } flash;
72
73 enum
74 {
75         Maxwchunk=      1024,   /* maximum chunk written by one call to falg->write */
76 };
77
78 /*
79  *  common flash interface
80  */
81 static uchar
82 cfigetc(int off)
83 {
84         uchar rv;
85
86         flash.p[0x55] = mirror(0x98);
87         rv = flash.p[off];
88         flash.p[0x55] = mirror(0xFF);
89         return rv;
90 }
91
92 static ushort
93 cfigets(int off)
94 {
95         return (cfigetc(off+1)<<8)|cfigetc(off);
96 }
97
98 static ulong
99 cfigetl(int off)
100 {
101         return (cfigetc(off+3)<<24)|(cfigetc(off+2)<<16)|
102                 (cfigetc(off+1)<<8)|cfigetc(off);
103 }
104
105 static void
106 cfiquery(void)
107 {
108         uchar q, r, y;
109         ulong x, addr;
110
111         q = cfigetc(0x10);
112         r = cfigetc(0x11);
113         y = cfigetc(0x12);
114         if(q != 'Q' || r != 'R' || y != 'Y'){
115                 print("cfi query failed: %ux %ux %ux\n", q, r, y);
116                 return;
117         }
118         flash.algid = cfigetc(0x13);
119         flash.size = 1<<(cfigetc(0x27)+1);
120         flash.wbsize = 1<<(cfigetc(0x2a)+1);
121         flash.nr = cfigetc(0x2c);
122         if(flash.nr > nelem(flash.r)){
123                 print("cfi reports > %d regions\n", nelem(flash.r));
124                 flash.nr = nelem(flash.r);
125         }
126         addr = 0;
127         for(q = 0; q < flash.nr; q++){
128                 x = cfigetl(q+0x2d);
129                 flash.r[q].size = 2*256*(x>>16);
130                 flash.r[q].n = (x&0xffff)+1;
131                 flash.r[q].addr = addr;
132                 addr += flash.r[q].size*flash.r[q].n;
133                 flash.r[q].end = addr;
134         }
135 }
136
137 /*
138  *  flash device interface
139  */
140
141 enum
142 {
143         Qtopdir,
144         Q2nddir,
145         Qfctl,
146         Qfdata,
147
148         Maxpart= 8,
149 };
150
151
152 typedef struct FPart FPart;
153 struct FPart
154 {
155         char    *name;
156         char    *ctlname;
157         ulong   start;
158         ulong   end;
159 };
160 static FPart    part[Maxpart];
161
162 #define FQID(p,q)       ((p)<<8|(q))
163 #define FTYPE(q)        ((q) & 0xff)
164 #define FPART(q)        (&part[(q) >>8])
165
166 static int
167 gen(Chan *c, char*, Dirtab*, int, int i, Dir *dp)
168 {
169         Qid q;
170         FPart *fp;
171
172         q.vers = 0;
173
174         /* top level directory contains the name of the network */
175         if(c->qid.path == Qtopdir){
176                 switch(i){
177                 case DEVDOTDOT:
178                         q.path = Qtopdir;
179                         q.type = QTDIR;
180                         devdir(c, q, "#F", 0, eve, DMDIR|0555, dp);
181                         break;
182                 case 0:
183                         q.path = Q2nddir;
184                         q.type = QTDIR;
185                         devdir(c, q, "flash", 0, eve, DMDIR|0555, dp);
186                         break;
187                 default:
188                         return -1;
189                 }
190                 return 1;
191         }
192
193         /* second level contains all partitions and their control files */
194         switch(i) {
195         case DEVDOTDOT:
196                 q.path = Qtopdir;
197                 q.type = QTDIR;
198                 devdir(c, q, "#F", 0, eve, DMDIR|0555, dp);
199                 break;
200         default:
201                 if(i >= 2*Maxpart)
202                         return -1;
203                 fp = &part[i>>1];
204                 if(fp->name == nil)
205                         return 0;
206                 if(i & 1){
207                         q.path = FQID(i>>1, Qfdata);
208                         q.type = QTFILE;
209                         devdir(c, q, fp->name, fp->end-fp->start, eve, 0660, dp);
210                 } else {
211                         q.path = FQID(i>>1, Qfctl);
212                         q.type = QTFILE;
213                         devdir(c, q, fp->ctlname, 0, eve, 0660, dp);
214                 }
215                 break;
216         }
217         return 1;
218 }
219
220 static FPart*
221 findpart(char *name)
222 {
223         int i;
224
225         for(i = 0; i < Maxpart; i++)
226                 if(part[i].name != nil && strcmp(name, part[i].name) == 0)
227                         break;
228         if(i >= Maxpart)
229                 return nil;
230         return &part[i];
231 }
232
233 static void
234 addpart(FPart *fp, char *name, ulong start, ulong end)
235 {
236         int i;
237         char ctlname[64];
238
239         if(fp == nil){
240                 if(start >= flash.size || end > flash.size)
241                         error(Ebadarg);
242         } else {
243                 start += fp->start;
244                 end += fp->start;
245                 if(start >= fp->end || end > fp->end)
246                         error(Ebadarg);
247         }
248         if(blockstart(start) != start)
249                 error("must start on erase boundary");
250         if(blockstart(end) != end && end != flash.size)
251                 error("must end on erase boundary");
252
253         fp = findpart(name);
254         if(fp != nil)
255                 error(Eexist);
256         for(i = 0; i < Maxpart; i++)
257                 if(part[i].name == nil)
258                         break;
259         if(i == Maxpart)
260                 error("no more partitions");
261         fp = &part[i];
262         kstrdup(&fp->name, name);
263         snprint(ctlname, sizeof ctlname, "%sctl", name);
264         kstrdup(&fp->ctlname, ctlname);
265         fp->start = start;
266         fp->end = end;
267 }
268
269 static void
270 rempart(FPart *fp)
271 {
272         char *p, *cp;
273
274         p = fp->name;
275         fp->name = nil;
276         cp = fp->ctlname;
277         fp->ctlname = nil;
278         free(p);
279         free(cp);
280 }
281
282 void
283 flashinit(void)
284 {
285         int i;
286
287         flash.p = (ulong*)FLASHZERO;
288         cfiquery();
289         for(i = 0; i < nelem(falg); i++)
290                 if(flash.algid == falg[i].id){
291                         flash.alg = &falg[i];
292                         (*flash.alg->identify)();
293                         break;
294                 }
295         flash.bootprotect = 1;
296
297         addpart(nil, "flash", 0, flash.size);
298 }
299
300 static Chan*
301 flashattach(char* spec)
302 {
303         return devattach('F', spec);
304 }
305
306 static Walkqid*
307 flashwalk(Chan *c, Chan *nc, char **name, int nname)
308 {
309         return devwalk(c, nc, name, nname, nil, 0, gen);
310 }
311
312 static int       
313 flashstat(Chan *c, uchar *db, int n)
314 {
315         return devstat(c, db, n, nil, 0, gen);
316 }
317
318 static Chan*
319 flashopen(Chan* c, int omode)
320 {
321         omode = openmode(omode);
322         if(strcmp(up->user, eve)!=0)
323                 error(Eperm);
324         return devopen(c, omode, nil, 0, gen);
325 }
326
327 static void      
328 flashclose(Chan*)
329 {
330 }
331
332 static long
333 flashctlread(FPart *fp, void* a, long n, vlong off)
334 {
335         char *buf, *p, *e;
336         int i;
337         ulong addr, end;
338
339         buf = smalloc(1024);
340         e = buf + 1024;
341         p = seprint(buf, e, "0x%-9lux 0x%-9x 0x%-9ux 0x%-9ux\n", fp->end-fp->start,
342                 flash.wbsize, flash.manid, flash.devid);
343         addr = fp->start;
344         for(i = 0; i < flash.nr && addr < fp->end; i++)
345                 if(flash.r[i].addr <= addr && flash.r[i].end > addr){
346                         if(fp->end <= flash.r[i].end)
347                                 end = fp->end;
348                         else
349                                 end = flash.r[i].end;
350                         p = seprint(p, e, "0x%-9lux 0x%-9lux 0x%-9lux\n", addr,
351                                 (end-addr)/flash.r[i].size, flash.r[i].size);
352                         addr = end;
353                 }
354         n = readstr(off, a, n, buf);
355         free(buf);
356         return n;
357 }
358
359 static long
360 flashdataread(FPart *fp, void* a, long n, vlong off)
361 {
362         rlock(&flash);
363         if(waserror()){
364                 runlock(&flash);
365                 nexterror();
366         }
367         if(fp->name == nil)
368                 error("partition vanished");
369         if(!iseve())
370                 error(Eperm);
371         off += fp->start;
372         if(off >= fp->end)
373                 n = 0;
374         if(off+n >= fp->end)
375                 n = fp->end - off;
376         if(n > 0)
377                 memmove(a, ((uchar*)FLASHZERO)+off, n);
378         runlock(&flash);
379         poperror();
380
381         return n;
382 }
383
384 static long      
385 flashread(Chan* c, void* a, long n, vlong off)
386 {
387         int t;
388
389         if(c->qid.type == QTDIR)
390                 return devdirread(c, a, n, nil, 0, gen);
391         t = FTYPE(c->qid.path);
392         switch(t){
393         default:
394                 error(Eperm);
395         case Qfctl:
396                 n = flashctlread(FPART(c->qid.path), a, n, off);
397                 break;
398         case Qfdata:
399                 n = flashdataread(FPART(c->qid.path), a, n, off);
400                 break;
401         }
402         return n;
403 }
404
405 static void
406 bootprotect(ulong addr)
407 {
408         FlashRegion *r;
409
410         if(flash.bootprotect == 0)
411                 return;
412         if(flash.nr == 0)
413                 error("writing over boot loader disallowed");
414         r = flash.r;
415         if(addr >= r->addr && addr < r->addr + r->size)
416                 error("writing over boot loader disallowed");
417 }
418
419 static ulong
420 blockstart(ulong addr)
421 {
422         FlashRegion *r, *e;
423         ulong x;
424
425         r = flash.r;
426         for(e = &flash.r[flash.nr]; r < e; r++)
427                 if(addr >= r->addr && addr < r->end){
428                         x = addr - r->addr;
429                         x /= r->size;
430                         return r->addr + x*r->size;
431                 }
432                         
433         return (ulong)-1;
434 }
435
436 static ulong
437 blockend(ulong addr)
438 {
439         FlashRegion *r, *e;
440         ulong x;
441
442         r = flash.r;
443         for(e = &flash.r[flash.nr]; r < e; r++)
444                 if(addr >= r->addr && addr < r->end){
445                         x = addr - r->addr;
446                         x /= r->size;
447                         return r->addr + (x+1)*r->size;
448                 }
449                         
450         return (ulong)-1;
451 }
452
453 static long
454 flashctlwrite(FPart *fp, char *p, long n)
455 {
456         Cmdbuf *cmd;
457         ulong off;
458
459         if(fp == nil)
460                 panic("flashctlwrite");
461
462         cmd = parsecmd(p, n);
463         wlock(&flash);
464         if(waserror()){
465                 wunlock(&flash);
466                 nexterror();
467         }
468         if(strcmp(cmd->f[0], "erase") == 0){
469                 switch(cmd->nf){
470                 case 2:
471                         /* erase a single block in the partition */
472                         off = atoi(cmd->f[1]);
473                         off += fp->start;
474                         if(off >= fp->end)
475                                 error("region not in partition");
476                         if(off != blockstart(off))
477                                 error("erase must be a block boundary");
478                         bootprotect(off);
479                         (*flash.alg->erase)(off);
480                         break;
481                 case 1:
482                         /* erase the whole partition */
483                         bootprotect(fp->start);
484                         for(off = fp->start; off < fp->end; off = blockend(off))
485                                 (*flash.alg->erase)(off);
486                         break;
487                 default:
488                         error(Ebadarg);
489                 }
490         } else if(strcmp(cmd->f[0], "add") == 0){
491                 if(cmd->nf != 4)
492                         error(Ebadarg);
493                 addpart(fp, cmd->f[1], strtoul(cmd->f[2], nil, 0), strtoul(cmd->f[3], nil, 0));
494         } else if(strcmp(cmd->f[0], "remove") == 0){
495                 rempart(fp);
496         } else if(strcmp(cmd->f[0], "protectboot") == 0){
497                 if(cmd->nf == 0 || strcmp(cmd->f[1], "off") != 0)
498                         flash.bootprotect = 1;
499                 else
500                         flash.bootprotect = 0;
501         } else
502                 error(Ebadarg);
503         poperror();
504         wunlock(&flash);
505         free(cmd);
506
507         return n;
508 }
509
510 static long
511 flashdatawrite(FPart *fp, uchar *p, long n, long off)
512 {
513         uchar *end;
514         int m;
515         int on;
516         long ooff;
517         uchar *buf;
518
519         if(fp == nil)
520                 panic("flashctlwrite");
521
522         buf = nil;
523         wlock(&flash);
524         if(waserror()){
525                 wunlock(&flash);
526                 if(buf != nil)
527                         free(buf);
528                 nexterror();
529         }
530
531         if(fp->name == nil)
532                 error("partition vanished");
533         if(!iseve())
534                 error(Eperm);
535
536         /* can't cross partition boundaries */
537         off += fp->start;
538         if(off >= fp->end || off+n > fp->end || n <= 0)
539                 error(Ebadarg);
540
541         /* make sure we're not writing the boot sector */
542         bootprotect(off);
543
544         on = n;
545
546         /*
547          *  get the data into kernel memory to avoid faults during writing.
548          *  if write is not on a quad boundary or not a multiple of 4 bytes,
549          *  extend with data already in flash.
550          */
551         buf = smalloc(n+8);
552         m = off & 3;
553         if(m){
554                 *(ulong*)buf = flash.p[(off)>>2];
555                 n += m;
556                 off -= m;
557         }
558         if(n & 3){
559                 n -= n & 3;
560                 *(ulong*)(&buf[n]) = flash.p[(off+n)>>2];
561                 n += 4;
562         }
563         memmove(&buf[m], p, on);
564
565         /* (*flash.alg->write) can't cross blocks */
566         ooff = off;
567         p = buf;
568         for(end = p + n; p < end; p += m){
569                 m = blockend(off) - off;
570                 if(m > end - p)
571                         m = end - p;
572                 if(m > Maxwchunk)
573                         m = Maxwchunk;
574                 (*flash.alg->write)(p, m, off);
575                 off += m;
576         }
577
578         /* make sure write succeeded */
579         if(memcmp(buf, &flash.p[ooff>>2], n) != 0)
580                 error("written bytes don't match");
581
582         wunlock(&flash);
583         free(buf);
584         poperror();
585
586         return on;
587 }
588
589 static long      
590 flashwrite(Chan* c, void* a, long n, vlong off)
591 {
592         int t;
593
594         if(c->qid.type == QTDIR)
595                 error(Eperm);
596
597         if(!iseve())
598                 error(Eperm);
599
600         t = FTYPE(c->qid.path);
601         switch(t){
602         default:
603                 panic("flashwrite");
604         case Qfctl:
605                 n = flashctlwrite(FPART(c->qid.path), a, n);
606                 break;
607         case Qfdata:
608                 n = flashdatawrite(FPART(c->qid.path), a, n, off);
609                 break;
610         }
611         return n;
612 }
613
614 Dev flashdevtab = {
615         'F',
616         "flash",
617
618         devreset,
619         flashinit,
620         devshutdown,
621         flashattach,
622         flashwalk,
623         flashstat,
624         flashopen,
625         devcreate,
626         flashclose,
627         flashread,
628         devbread,
629         flashwrite,
630         devbwrite,
631         devremove,
632         devwstat,
633 };
634
635 enum
636 {
637         /* status register */
638         ISEs_lockerr=           1<<1,
639         ISEs_powererr=          1<<3,
640         ISEs_progerr=           1<<4,
641         ISEs_eraseerr=          1<<5,
642         ISEs_ready=             1<<7,
643         ISEs_err= (ISEs_lockerr|ISEs_powererr|ISEs_progerr|ISEs_eraseerr),
644
645         /* extended status register */
646         ISExs_bufavail=         1<<7,
647 };
648
649
650
651 /* intel/sharp extended command set */
652 static void
653 ise_reset(void)
654 {
655         flash.p[0x55] = mirror(0xff);   /* reset */
656 }
657 static void
658 ise_id(void)
659 {
660         ise_reset();
661         flash.p[0x555] = mirror(0x90);  /* uncover vendor info */
662         flash.manid = flash.p[00];
663         flash.devid = flash.p[01];
664         ise_reset();
665 }
666 static void
667 ise_clearerror(void)
668 {
669         flash.p[0x100] = mirror(0x50);
670
671 }
672 static void
673 ise_error(int bank, ulong status)
674 {
675         char err[64];
676
677         if(status & (ISEs_lockerr)){
678                 sprint(err, "flash%d: block locked %lux", bank, status);
679                 error(err);
680         }
681         if(status & (ISEs_powererr)){
682                 sprint(err, "flash%d: low prog voltage %lux", bank, status);
683                 error(err);
684         }
685         if(status & (ISEs_progerr|ISEs_eraseerr)){
686                 sprint(err, "flash%d: i/o error %lux", bank, status);
687                 error(err);
688         }
689 }
690 static void
691 ise_erase(ulong addr)
692 {
693         ulong start;
694         ulong x;
695
696         addr >>= 2;     /* convert to ulong offset */
697
698         flashprogpower(1);
699         flash.p[addr] = mirror(0x20);
700         flash.p[addr] = mirror(0xd0);
701         start = m->ticks;
702         do {
703                 x = flash.p[addr];
704                 if((x & mirror(ISEs_ready)) == mirror(ISEs_ready))
705                         break;
706         } while(TK2MS(m->ticks-start) < 1500);
707         flashprogpower(0);
708
709         ise_clearerror();
710         ise_error(0, x);
711         ise_error(1, x>>16);
712
713         ise_reset();
714 }
715 /*
716  *  the flash spec claimes writing goes faster if we use
717  *  the write buffer.  We fill the write buffer and then
718  *  issue the write request.  After the write request,
719  *  subsequent reads will yield the status register.
720  *
721  *  returns the status, even on timeouts.
722  *
723  *  NOTE: I tried starting back to back buffered writes
724  *      without reading the status in between, as the
725  *      flowchart in the intel data sheet suggests.
726  *      However, it always responded with an illegal
727  *      command sequence, so I must be missing something.
728  *      If someone learns better, please email me, though
729  *      I doubt it will be much faster. -  presotto@bell-labs.com
730  */
731 static int
732 ise_wbwrite(ulong *p, int n, ulong off, ulong baddr, ulong *status)
733 {
734         ulong x, start;
735         int i;
736         int s;
737
738         /* put flash into write buffer mode */
739         start = m->ticks;
740         for(;;) {
741                 s = splhi();
742                 /* request write buffer mode */
743                 flash.p[baddr] = mirror(0xe8);
744
745                 /* look at extended status reg for status */
746                 if((flash.p[baddr] & mirror(1<<7)) == mirror(1<<7))
747                         break;
748                 splx(s);
749
750                 /* didn't work, keep trying for 2 secs */
751                 if(TK2MS(m->ticks-start) > 2000){
752                         /* set up to read status */
753                         flash.p[baddr] = mirror(0x70);
754                         *status = flash.p[baddr];
755                         pprint("write buffered cmd timed out\n");
756                         return -1;
757                 }
758         }
759
760         /* fill write buffer */
761         flash.p[baddr] = mirror(n-1);
762         for(i = 0; i < n; i++)
763                 flash.p[off+i] = *p++;
764
765         /* program from buffer */
766         flash.p[baddr] = mirror(0xd0);
767         splx(s);
768
769         /* wait till the programming is done */
770         start = m->ticks;
771         for(;;) {
772                 x = *status = flash.p[baddr];   /* read status register */
773                 if((x & mirror(ISEs_ready)) == mirror(ISEs_ready))
774                         break;
775                 if(TK2MS(m->ticks-start) > 2000){
776                         pprint("read status timed out\n");
777                         return -1;
778                 }
779         }
780         if(x & mirror(ISEs_err))
781                 return -1;
782
783         return n;
784 }
785 static void
786 ise_write(void *a, long n, ulong off)
787 {
788         ulong *p, *end;
789         int i, wbsize;
790         ulong x, baddr;
791
792         /* everything in terms of ulongs */
793         wbsize = flash.wbsize>>2;
794         baddr = blockstart(off);
795         off >>= 2;
796         n >>= 2;
797         p = a;
798         baddr >>= 2;
799
800         /* first see if write will succeed */
801         for(i = 0; i < n; i++)
802                 if((p[i] & flash.p[off+i]) != p[i])
803                         error("flash needs erase");
804
805         if(waserror()){
806                 ise_reset();
807                 flashprogpower(0);
808                 nexterror();
809         }
810         flashprogpower(1);
811
812         /*
813          *  use the first write to reach
814          *  a write buffer boundary.  the intel maunal
815          *  says writes startng at wb boundaries
816          *  maximize speed.
817          */
818         i = wbsize - (off & (wbsize-1));
819         for(end = p + n; p < end;){
820                 if(i > end - p)
821                         i = end - p;
822
823                 if(ise_wbwrite(p, i, off, baddr, &x) < 0)
824                         break;
825
826                 off += i;
827                 p += i;
828                 i = wbsize;
829         }
830
831         ise_clearerror();
832         ise_error(0, x);
833         ise_error(1, x>>16);
834
835         ise_reset();
836         flashprogpower(0);
837         poperror();
838 }
839
840 /* amd/fujitsu standard command set
841  *      I don't have an amd chipset to work with
842  *      so I'm loathe to write this yet.  If someone
843  *      else does, please send it to me and I'll
844  *      incorporate it -- presotto@bell-labs.com
845  */
846 static void
847 afs_reset(void)
848 {
849         flash.p[0x55] = mirror(0xf0);   /* reset */
850 }
851 static void
852 afs_id(void)
853 {
854         afs_reset();
855         flash.p[0x55] = mirror(0xf0);   /* reset */
856         flash.p[0x555] = mirror(0xaa);  /* query vendor block */
857         flash.p[0x2aa] = mirror(0x55);
858         flash.p[0x555] = mirror(0x90);
859         flash.manid = flash.p[00];
860         afs_reset();
861         flash.p[0x555] = mirror(0xaa);  /* query vendor block */
862         flash.p[0x2aa] = mirror(0x55);
863         flash.p[0x555] = mirror(0x90);
864         flash.devid = flash.p[01];
865         afs_reset();
866 }
867 static void
868 afs_erase(ulong)
869 {
870         error("amd/fujistsu erase not implemented");
871 }
872 static void
873 afs_write(void*, long, ulong)
874 {
875         error("amd/fujistsu write not implemented");
876 }