1 /*
2 * read-only driver for BIOS LBA devices.
3 * devbios must be initialised first and no disks may be accessed
4 * via non-BIOS means (i.e., talking to the disk controller directly).
5 * EDD 4.0 defines the INT 0x13 functions.
6 *
7 * heavily dependent upon correct BIOS implementation.
8 * some bioses (e.g., vmware) seem to hang for two minutes then report
9 * a disk timeout on reset and extended read operations.
10 */
11 #include "u.h"
12 #include "../port/lib.h"
13 #include "mem.h"
14 #include "dat.h"
15 #include "fns.h"
16 #include "io.h"
17 #include "ureg.h"
18 #include "pool.h"
19 #include "../port/error.h"
20 #include "../port/netif.h"
21 #include "../port/sd.h"
22 #include "dosfs.h"
23
24 #define TYPE(q) ((ulong)(q).path & 0xf)
25 #define UNIT(q) (((ulong)(q).path>>4) & 0xff)
26 #define L(q) (((ulong)(q).path>>12) & 0xf)
27 #define QID(u, t) ((u)<<4 | (t))
28
29 typedef struct Biosdev Biosdev;
30 typedef struct Dap Dap;
31 typedef uvlong Devbytes, Devsects;
32 typedef uchar Devid;
33 typedef struct Edrvparam Edrvparam;
34
35 enum {
36 Debug = 0,
37 Pause = 0, /* delay to read debugging */
38
39 Minsectsz = 512, /* for disks */
40 Maxsectsz = 2048, /* for optical (CDs, etc.) */
41
42 Highshort = ((1ul<<16) - 1) << 16, /* upper short of a long */
43
44 Maxdevs = 8,
45 CF = 1, /* carry flag: indicates an error */
46 Flopid = 0, /* first floppy */
47 Baseid = 0x80, /* first disk */
48
49 Diskint = 0x13, /* "INT 13" for bios disk i/o */
50
51 /* cx capability bits in Biosckext results */
52 Fixeddisk = 1<<0, /* fixed disk access subset */
53 Drlock = 1<<1,
54 Edd = 1<<2, /* enhanced disk drive support */
55 Bit64ext = 1<<3,
56
57 /* bios calls: int 0x13 disk services w buffer at es:bx */
58 Biosinit = 0, /* initialise disk & floppy ctlrs */
59 Biosdrvsts, /* status of last int 0x13 call */
60 Biosdrvparam = 8,
61 Biosctlrinit,
62 Biosreset = 0xd, /* reset disk */
63 Biosdrvrdy = 0x10,
64 /* extended int 0x13 calls w dap at ds:si */
65 Biosckext = 0x41,
66 Biosrdsect,
67 Biosedrvparam = 0x48,
68
69 /* magic numbers for bios calls */
70 Imok = 0x55aa,
71 Youreok = 0xaa55,
72 };
73 enum {
74 Qzero, /* assumed to be 0 by devattach */
75 Qtopdir = 1,
76 Qtopbase,
77 Qtopctl = Qtopbase,
78 Qtopend,
79
80 Qunitdir,
81 Qunitbase,
82 Qctl = Qunitbase,
83 Qdata,
84
85 Qtopfiles = Qtopend-Qtopbase,
86 };
87
88 struct Biosdev {
89 Devbytes size;
90 Devbytes offset;
91 Devid id; /* drive number; e.g., 0x80 */
92 ushort sectsz;
93 Chan *rootchan;
94 Bootfs;
95 };
96
97 struct Dap { /* a device address packet */
98 uchar size;
99 uchar _unused1;
100 uchar nsects;
101 uchar _unused2;
102 union {
103 ulong addr; /* actual address (nominally seg:off) */
104 struct {
105 ushort addroff; /* :offset */
106 ushort addrseg; /* segment: */
107 };
108 };
109 uvlong stsect; /* starting sector */
110
111 uvlong addr64; /* instead of addr, if addr is ~0 */
112 ulong lnsects; /* nsects to match addr64 */
113 ulong _unused3;
114 };
115
116 struct Edrvparam {
117 ushort size; /* max. buffer (struct) size */
118 ushort flags;
119 ulong physcyls;
120 ulong physheads;
121 ulong phystracksects;
122 uvlong physsects;
123 ushort sectsz;
124
125 /* pointer is required to be unaligned, bytes 26-29. ick. */
126 // void *dpte; /* ~0ull: invalid */
127 ushort dpteoff; /* device parameter table extension */
128 ushort dpteseg;
129
130 /* remainder from edd 3.0 spec */
131 ushort key; /* 0xbedd if device path info present */
132 uchar dpilen; /* must be 44 (says edd 4.0) */
133 uchar _unused1;
134 ushort _unused2;
135 char bustype[4]; /* "PCI" or "ISA" */
136 char ifctype[8]; /* "ATA", "ATAPI", "SCSI", "USB", "1394", "FIBRE" */
137 uvlong ifcpath;
138 uvlong devpath[2];
139 uchar _unused3;
140 uchar dpicksum;
141 };
142
143 int biosinited;
144 int biosndevs;
145
146 void *biosgetfspart(int i, char *name, int chatty);
147
148 static Biosdev bdev[Maxdevs];
149 static Ureg regs;
150 static RWlock devs;
151
152 static int dreset(Devid drive);
153 static Devbytes extgetsize(Biosdev *);
154 static int drivecap(Devid drive);
155
156 /* convert ah error code to a string (just common cases) */
157 static char *
strerr(uchar err)158 strerr(uchar err)
159 {
160 switch (err) {
161 case 0:
162 return "no error";
163 case 1:
164 return "bad command";
165 case 0x80:
166 return "disk timeout";
167 default:
168 return "unknown";
169 }
170 }
171
172 static void
assertlow64k(uintptr p,char * tag)173 assertlow64k(uintptr p, char *tag)
174 {
175 if (p & Highshort)
176 panic("devbios: %s address %#p not in bottom 64k", tag, p);
177 }
178
179 static void
initrealregs(Ureg * ureg)180 initrealregs(Ureg *ureg)
181 {
182 memset(ureg, 0, sizeof *ureg);
183 }
184
185 /*
186 * caller must zero or otherwise initialise *ureg,
187 * other than ax, bx, dx, si & ds.
188 */
189 static int
biosdiskcall(Ureg * ureg,uchar op,ulong bx,ulong dx,ulong si)190 biosdiskcall(Ureg *ureg, uchar op, ulong bx, ulong dx, ulong si)
191 {
192 int s;
193 uchar err;
194
195 s = splhi(); /* don't let the bios call be interrupted */
196 initrealregs(ureg);
197 ureg->ax = op << 8;
198 ureg->bx = bx;
199 ureg->dx = dx; /* often drive id */
200 assertlow64k(si, "dap");
201 if(si && (si & Highshort) != ((si + Maxsectsz - 1) & Highshort))
202 print("biosdiskcall: dap address %#lux too near segment boundary\n",
203 si);
204
205 ureg->si = si; /* ds:si forms data address packet addr */
206 ureg->ds = 0; /* bottom 64K */
207 ureg->es = 0; /* es:bx is conventional buffer */
208 ureg->di = 0; /* buffer segment? */
209 ureg->flags = 0;
210
211 /*
212 * *ureg is copied into low memory (realmoderegs) and thence into
213 * the machine registers before the BIOS call, and the registers are
214 * copied into realmoderegs and thence into *ureg after.
215 *
216 * realmode loads these registers: di, si, ax, bx, cx, dx, ds, es.
217 */
218 ureg->trap = Diskint;
219 realmode(ureg);
220
221 if (ureg->flags & CF) {
222 if (dx == Baseid) {
223 err = ureg->ax >> 8;
224 print("\nbiosdiskcall: int %#x op %#ux drive %#lux "
225 "failed, ah error code %#ux (%s)\n",
226 Diskint, op, dx, err, strerr(err));
227 }
228 splx(s);
229 return -1;
230 }
231 splx(s);
232 return 0;
233 }
234
235 /*
236 * Find out what the bios knows about devices.
237 * our boot device could be usb; ghod only knows where it will appear.
238 */
239 int
biosinit0(void)240 biosinit0(void)
241 {
242 int cap, mask, lastbit, ndrive;
243 Devbytes size;
244 Devid devid;
245 Biosdev *bdp;
246 static int beenhere;
247
248 delay(Pause); /* pause to read the screen (DEBUG) */
249 if (biosinited || beenhere)
250 return 0;
251 beenhere = 1;
252
253 ndrive = *(uchar *)KADDR(0x475); /* from bda */
254 if (Debug)
255 print("%d bios drive(s)\n", ndrive);
256 mask = lastbit = 0;
257 for (devid = Baseid, biosndevs = 0; devid != 0 && biosndevs < Maxdevs &&
258 biosndevs < ndrive; devid++) {
259 cap = drivecap(devid);
260 /* don't reset; it seems to hang the bios */
261 if(cap < 0 || (cap & (Fixeddisk|Edd)) != (Fixeddisk|Edd)
262 /* || devid != Baseid && dreset(devid) < 0 || */)
263 continue; /* no suitable device */
264
265 /* found a live one */
266 lastbit = 1 << biosndevs;
267 mask |= lastbit;
268
269 bdp = &bdev[biosndevs];
270 bdp->id = devid;
271 size = extgetsize(bdp);
272 if (size == 0)
273 continue; /* no device */
274 bdp->size = size;
275
276 print("bios%d: drive %#ux: %,llud bytes, %d-byte sectors\n",
277 biosndevs, devid, size, bdp->sectsz);
278 biosndevs++;
279 }
280 USED(lastbit);
281
282 if (Debug && ndrive != biosndevs)
283 print("devbios: expected %d drives, found %d\n", ndrive, biosndevs);
284
285 /*
286 * some bioses seem to only be able to read from drive number 0x80 and
287 * can't read from the highest drive number, even if there is only one.
288 */
289 if (biosndevs > 0)
290 biosinited = 1;
291 else
292 panic("devbios: no bios drives seen"); /* 9loadusb needs ≥ 1 */
293 delay(Pause); /* pause to read the screen (DEBUG) */
294 return mask;
295 }
296
297 static void
biosreset(void)298 biosreset(void)
299 {
300 biosinit0();
301 }
302
303 static void
biosinit(void)304 biosinit(void)
305 {
306 }
307
308 static Chan*
biosattach(char * spec)309 biosattach(char *spec)
310 {
311 ulong drive;
312 char *p;
313 Chan *chan;
314
315 drive = 0;
316 if(spec && *spec){
317 drive = strtoul(spec, &p, 0);
318 if((drive == 0 && p == spec) || *p || (drive >= Maxdevs))
319 error(Ebadarg);
320 }
321 if(bdev[drive].rootchan)
322 return bdev[drive].rootchan;
323
324 chan = devattach(L'☹', spec);
325 if(waserror()){
326 chanfree(chan);
327 nexterror();
328 }
329 chan->dev = drive;
330 bdev[drive].rootchan = chan;
331 /* arbitrary initialisation can go here */
332 poperror();
333 return chan;
334 }
335
336 static int
unitgen(Chan * c,ulong type,Dir * dp)337 unitgen(Chan *c, ulong type, Dir *dp)
338 {
339 int perm, t;
340 ulong vers;
341 vlong size;
342 char *p;
343 Qid q;
344
345 perm = 0644;
346 size = 0;
347 // d = unit2dev(UNIT(c->qid));
348 // vers = d->vers;
349 vers = 0;
350 t = QTFILE;
351
352 switch(type){
353 default:
354 return -1;
355 case Qctl:
356 p = "ctl";
357 break;
358 case Qdata:
359 p = "data";
360 perm = 0640;
361 break;
362 }
363 mkqid(&q, QID(UNIT(c->qid), type), vers, t);
364 devdir(c, q, p, size, eve, perm, dp);
365 return 1;
366 }
367
368 static int
topgen(Chan * c,ulong type,Dir * d)369 topgen(Chan *c, ulong type, Dir *d)
370 {
371 int perm;
372 vlong size;
373 char *p;
374 Qid q;
375
376 size = 0;
377 switch(type){
378 default:
379 return -1;
380 case Qdata:
381 p = "data";
382 perm = 0644;
383 break;
384 }
385 mkqid(&q, type, 0, QTFILE);
386 devdir(c, q, p, size, eve, perm, d);
387 return 1;
388 }
389
390 static int
biosgen(Chan * c,char *,Dirtab *,int,int s,Dir * dp)391 biosgen(Chan *c, char *, Dirtab *, int, int s, Dir *dp)
392 {
393 Qid q;
394
395 if(c->qid.path == 0){
396 switch(s){
397 case DEVDOTDOT:
398 q.path = 0;
399 q.type = QTDIR;
400 devdir(c, q, "#☹", 0, eve, 0555, dp);
401 break;
402 case 0:
403 q.path = Qtopdir;
404 q.type = QTDIR;
405 devdir(c, q, "bios", 0, eve, 0555, dp);
406 break;
407 default:
408 return -1;
409 }
410 return 1;
411 }
412
413 switch(TYPE(c->qid)){
414 default:
415 return -1;
416 case Qtopdir:
417 if(s == DEVDOTDOT){
418 mkqid(&q, Qzero, 0, QTDIR);
419 devdir(c, q, "bios", 0, eve, 0555, dp);
420 return 1;
421 }
422 if(s < Qtopfiles)
423 return topgen(c, Qtopbase + s, dp);
424 s -= Qtopfiles;
425 if(s >= 1)
426 return -1;
427 mkqid(&q, QID(s, Qunitdir), 0, QTDIR);
428 devdir(c, q, "bios", 0, eve, 0555, dp);
429 return 1;
430 case Qdata:
431 return unitgen(c, TYPE(c->qid), dp);
432 }
433 }
434
435 static Walkqid*
bioswalk(Chan * c,Chan * nc,char ** name,int nname)436 bioswalk(Chan *c, Chan *nc, char **name, int nname)
437 {
438 return devwalk(c, nc, name, nname, nil, 0, biosgen);
439 }
440
441 static int
biosstat(Chan * c,uchar * db,int n)442 biosstat(Chan *c, uchar *db, int n)
443 {
444 return devstat(c, db, n, nil, 0, biosgen);
445 }
446
447 static Chan*
biosopen(Chan * c,int omode)448 biosopen(Chan *c, int omode)
449 {
450 return devopen(c, omode, 0, 0, biosgen);
451 }
452
453 static void
biosclose(Chan *)454 biosclose(Chan *)
455 {
456 }
457
458 #ifdef UNUSED
459 int
biosboot(int dev,char * file,Boot * b)460 biosboot(int dev, char *file, Boot *b)
461 {
462 Bootfs *fs;
463
464 if(strncmp(file, "dos!", 4) == 0)
465 file += 4;
466 if(strchr(file, '!') != nil || strcmp(file, "") == 0) {
467 print("syntax is bios0!file\n");
468 return -1;
469 }
470
471 fs = biosgetfspart(dev, "9fat", 1);
472 if(fs == nil)
473 return -1;
474 return fsboot(fs, file, b);
475 }
476 #endif
477
478 /* read n bytes at sector offset into a from drive id */
479 long
sectread(Biosdev * bdp,void * a,long n,Devsects offset)480 sectread(Biosdev *bdp, void *a, long n, Devsects offset)
481 {
482 uchar *xch;
483 uintptr xchaddr;
484 Dap *dap;
485
486 if(bdp->sectsz <= 0 || n < 0 || n > bdp->sectsz)
487 return -1;
488 xch = (uchar *)BIOSXCHG;
489 assertlow64k(PADDR(xch), "biosxchg");
490 if(Debug)
491 /* scribble on the buffer to provoke trouble */
492 memset(xch, 'r', bdp->sectsz);
493
494 /* read into BIOSXCHG; alloc space for a worst-case (optical) sector */
495 dap = (Dap *)(xch + Maxsectsz);
496 assertlow64k(PADDR(dap), "Dap");
497 memset(dap, 0, sizeof *dap);
498 dap->size = sizeof *dap;
499 dap->nsects = 1;
500 dap->stsect = offset;
501
502 xchaddr = PADDR(xch);
503 assertlow64k(xchaddr, "sectread buffer");
504 dap->addr = xchaddr; /* ulong version */
505 dap->addroff = xchaddr; /* pedantic seg:off */
506 dap->addrseg = 0;
507 dap->addr64 = xchaddr; /* paranoid redundancy */
508 dap->lnsects = 1;
509
510 /*
511 * ensure that entire buffer fits in low memory.
512 */
513 if((dap->addr & Highshort) !=
514 ((dap->addr + Minsectsz - 1) & Highshort))
515 print("devbios: sectread: address %#lux too near seg boundary\n",
516 dap->addr);
517 if (Debug)
518 print("reading bios drive %#ux sector %lld -> %#lux...",
519 bdp->id, offset, dap->addr);
520 delay(Pause); /* pause to read the screen (DEBUG) */
521
522 /*
523 * int 13 read sector expects buffer seg in di?,
524 * dap in si, 0x42 in ah, drive in dl.
525 */
526 if (biosdiskcall(®s, Biosrdsect, 0, bdp->id, PADDR(dap)) < 0) {
527 print("devbios: sectread: bios failed to read %ld @ sector %lld of %#ux\n",
528 n, offset, bdp->id);
529 return -1;
530 }
531 if (dap->nsects != 1)
532 panic("devbios: sector read ok but read %d sectors",
533 dap->nsects);
534 if (Debug)
535 print("OK\n");
536
537 /* copy into caller's buffer */
538 memmove(a, xch, n);
539 if(0 && Debug)
540 print("-%ux %ux %ux %ux--%16.16s-\n",
541 xch[0], xch[1], xch[2], xch[3], (char *)xch + 480);
542 delay(Pause); /* pause to read the screen (DEBUG) */
543 return n;
544 }
545
546 /* seems to hang bioses, at least vmware's */
547 static int
dreset(Devid drive)548 dreset(Devid drive)
549 {
550 print("devbios: resetting %#ux...", drive);
551 /* ignore carry flag for Biosinit */
552 biosdiskcall(®s, Biosinit, 0, drive, 0);
553 print("\n");
554 return regs.ax? -1: 0; /* ax != 0 on error */
555 }
556
557 /* returns capabilities bitmap */
558 static int
drivecap(Devid drive)559 drivecap(Devid drive)
560 {
561 int cap;
562
563 if (biosdiskcall(®s, Biosckext, Imok, drive, 0) < 0)
564 /*
565 * we have an old bios without extensions, in theory.
566 * in practice, there may just be no drive for this number.
567 */
568 return -1;
569 if(regs.bx != Youreok){
570 print("devbios: buggy bios: drive %#ux extension check "
571 "returned %lux in bx\n", drive, regs.bx);
572 return -1;
573 }
574 cap = regs.cx;
575 if (Debug) {
576 print("bios drive %#ux extensions version %#x.%d cx %#ux\n",
577 drive, (uchar)(regs.ax >> 8), (uchar)regs.ax, cap);
578 if ((uchar)(regs.ax >> 8) < 0x30) {
579 print("drivecap: extensions prior to 0x30\n");
580 return -1;
581 }
582 print("\tsubsets supported:");
583 if (cap & Fixeddisk)
584 print(" fixed disk access;");
585 if (cap & Drlock)
586 print(" drive locking;");
587 if (cap & Edd)
588 print(" enhanced disk support;");
589 if (cap & Bit64ext)
590 print(" 64-bit extensions;");
591 print("\n");
592 }
593 delay(Pause); /* pause to read the screen (DEBUG) */
594 return cap;
595 }
596
597 /* extended get size; reads bdp->id, fills in bdp->sectsz, returns # sectors */
598 static Devbytes
extgetsize(Biosdev * bdp)599 extgetsize(Biosdev *bdp)
600 {
601 ulong sectsz;
602 Edrvparam *edp;
603
604 edp = (Edrvparam *)BIOSXCHG;
605 memset(edp, 0, sizeof *edp);
606 edp->size = sizeof *edp;
607 edp->dpteseg = edp->dpteoff = ~0; /* no pointer */
608 edp->dpilen = 44;
609
610 if (biosdiskcall(®s, Biosedrvparam, 0, bdp->id, PADDR(edp)) < 0)
611 return 0; /* old bios without extensions */
612 if(Debug) {
613 print("bios drive %#ux info flags %#ux", bdp->id, edp->flags);
614 if (edp->key == 0xbedd)
615 print("; edd 3.0 %.4s %.8s",
616 edp->bustype, edp->ifctype);
617 else
618 print("; NOT edd 3.0 compliant (key %#ux)", edp->key);
619 print("\n");
620 }
621 if (edp->sectsz <= 0) {
622 print("devbios: drive %#ux: sector size <= 0\n", bdp->id);
623 edp->sectsz = 1; /* don't divide by 0 */
624 return 0;
625 }
626 sectsz = edp->sectsz;
627 if (sectsz > Maxsectsz) {
628 print("devbios: sector size %lud > %d\n", sectsz, Maxsectsz);
629 return 0;
630 }
631 bdp->sectsz = sectsz;
632 return edp->physsects * sectsz;
633 }
634
635 vlong
biossize(uint dev)636 biossize(uint dev)
637 {
638 Biosdev *bdp;
639
640 if (dev >= biosndevs)
641 return -1;
642 bdp = &bdev[dev];
643 if (bdp->sectsz <= 0)
644 return -1;
645 return bdp->size / bdp->sectsz;
646 }
647
648 long
biossectsz(uint dev)649 biossectsz(uint dev)
650 {
651 Biosdev *bdp;
652
653 if (dev >= biosndevs)
654 return -1;
655 bdp = &bdev[dev];
656 if (bdp->sectsz <= 0)
657 return -1;
658 return bdp->sectsz;
659 }
660
661 long
biosread0(Bootfs * fs,void * a,long n)662 biosread0(Bootfs *fs, void *a, long n)
663 {
664 int want, got, part, dev;
665 long totnr, stuck;
666 Devbytes offset;
667 Biosdev *bdp;
668
669 dev = fs->dev; /* only use of fs */
670 if(dev > biosndevs)
671 return -1;
672 if (n <= 0)
673 return n;
674 bdp = &bdev[dev];
675 offset = bdp->offset;
676 stuck = 0;
677 for (totnr = 0; totnr < n && stuck < 4; totnr += got) {
678 if (bdp->sectsz == 0) {
679 print("devbios: zero sector size\n");
680 return -1;
681 }
682 want = bdp->sectsz;
683 if (totnr + want > n)
684 want = n - totnr;
685 if(0 && Debug && debugload)
686 print("bios%d, read: %ld @ off %lld, want: %d, id: %#ux\n",
687 dev, n, offset, want, bdp->id);
688 part = offset % bdp->sectsz;
689 if (part != 0) { /* back up to start of sector */
690 offset -= part;
691 totnr -= part;
692 if (totnr < 0) {
693 print("biosread0: negative count %ld\n", totnr);
694 return -1;
695 }
696 }
697 if ((vlong)offset < 0) {
698 print("biosread0: negative offset %lld\n", offset);
699 return -1;
700 }
701 got = sectread(bdp, (char *)a + totnr, want,
702 offset / bdp->sectsz);
703 if(got <= 0)
704 return -1;
705 offset += got;
706 bdp->offset = offset;
707 if (got < bdp->sectsz)
708 stuck++; /* we'll have to re-read this sector */
709 else
710 stuck = 0;
711 }
712 return totnr;
713 }
714
715 vlong
biosseek(Bootfs * fs,vlong off)716 biosseek(Bootfs *fs, vlong off)
717 {
718 if (off < 0) {
719 print("biosseek(fs, %lld) is illegal\n", off);
720 return -1;
721 }
722 if(fs->dev > biosndevs) {
723 print("biosseek: fs->dev %d > biosndevs %d\n", fs->dev, biosndevs);
724 return -1;
725 }
726 bdev[fs->dev].offset = off; /* do not know size... (yet) */
727 return off;
728 }
729
730 static long
biosread(Chan * c,void * db,long n,vlong off)731 biosread(Chan *c, void *db, long n, vlong off)
732 {
733 Biosdev *bp;
734
735 switch(TYPE(c->qid)){
736 default:
737 error(Eperm);
738 case Qzero:
739 case Qtopdir:
740 return devdirread(c, db, n, 0, 0, biosgen);
741 case Qdata:
742 bp = &bdev[UNIT(c->qid)];
743 if (bp->rootchan == nil)
744 panic("biosread: nil root chan for bios%ld",
745 UNIT(c->qid));
746 biosseek(&bp->Bootfs, off);
747 return biosread0(&bp->Bootfs, db, n);
748 }
749 }
750
751 /* name is typically "9fat" */
752 void *
biosgetfspart(int i,char * name,int chatty)753 biosgetfspart(int i, char *name, int chatty)
754 {
755 char part[32];
756 static Bootfs fs;
757
758 fs.dev = i;
759 fs.diskread = biosread0;
760 fs.diskseek = biosseek;
761 snprint(part, sizeof part, "#S/sdB0/%s", name);
762 if(dosinit(&fs, part) < 0){
763 if(chatty)
764 print("bios%d!%s does not contain a FAT file system\n",
765 i, name);
766 return nil;
767 }
768 return &fs;
769 }
770
771 static long
bioswrite(Chan *,void *,long,vlong)772 bioswrite(Chan *, void *, long, vlong)
773 {
774 error("bios devices are read-only in bootstrap");
775 return 0;
776 }
777
778 Dev biosdevtab = {
779 L'☹',
780 "bios",
781
782 biosreset,
783 biosinit,
784 devshutdown,
785 biosattach,
786 bioswalk,
787 biosstat,
788 biosopen,
789 devcreate,
790 biosclose,
791 biosread,
792 devbread,
793 bioswrite,
794 devbwrite,
795 devremove,
796 devwstat,
797 devpower,
798 devconfig,
799 };
800