/* * read-only driver for BIOS LBA devices. * devbios must be initialised first and no disks may be accessed * via non-BIOS means (i.e., talking to the disk controller directly). * EDD 4.0 defines the INT 0x13 functions. * * heavily dependent upon correct BIOS implementation. * some bioses (e.g., vmware) seem to hang for two minutes then report * a disk timeout on reset and extended read operations. */ #include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "ureg.h" #include "pool.h" #include "../port/error.h" #include "../port/netif.h" #include "../port/sd.h" #include "dosfs.h" #define TYPE(q) ((ulong)(q).path & 0xf) #define UNIT(q) (((ulong)(q).path>>4) & 0xff) #define L(q) (((ulong)(q).path>>12) & 0xf) #define QID(u, t) ((u)<<4 | (t)) typedef struct Biosdev Biosdev; typedef struct Dap Dap; typedef uvlong Devbytes, Devsects; typedef uchar Devid; typedef struct Edrvparam Edrvparam; enum { Debug = 0, Pause = 0, /* delay to read debugging */ Minsectsz = 512, /* for disks */ Maxsectsz = 2048, /* for optical (CDs, etc.) */ Highshort = ((1ul<<16) - 1) << 16, /* upper short of a long */ Maxdevs = 8, CF = 1, /* carry flag: indicates an error */ Flopid = 0, /* first floppy */ Baseid = 0x80, /* first disk */ Diskint = 0x13, /* "INT 13" for bios disk i/o */ /* cx capability bits in Biosckext results */ Fixeddisk = 1<<0, /* fixed disk access subset */ Drlock = 1<<1, Edd = 1<<2, /* enhanced disk drive support */ Bit64ext = 1<<3, /* bios calls: int 0x13 disk services w buffer at es:bx */ Biosinit = 0, /* initialise disk & floppy ctlrs */ Biosdrvsts, /* status of last int 0x13 call */ Biosdrvparam = 8, Biosctlrinit, Biosreset = 0xd, /* reset disk */ Biosdrvrdy = 0x10, /* extended int 0x13 calls w dap at ds:si */ Biosckext = 0x41, Biosrdsect, Biosedrvparam = 0x48, /* magic numbers for bios calls */ Imok = 0x55aa, Youreok = 0xaa55, }; enum { Qzero, /* assumed to be 0 by devattach */ Qtopdir = 1, Qtopbase, Qtopctl = Qtopbase, Qtopend, Qunitdir, Qunitbase, Qctl = Qunitbase, Qdata, Qtopfiles = Qtopend-Qtopbase, }; struct Biosdev { Devbytes size; Devbytes offset; Devid id; /* drive number; e.g., 0x80 */ ushort sectsz; Chan *rootchan; Bootfs; }; struct Dap { /* a device address packet */ uchar size; uchar _unused1; uchar nsects; uchar _unused2; union { ulong addr; /* actual address (nominally seg:off) */ struct { ushort addroff; /* :offset */ ushort addrseg; /* segment: */ }; }; uvlong stsect; /* starting sector */ uvlong addr64; /* instead of addr, if addr is ~0 */ ulong lnsects; /* nsects to match addr64 */ ulong _unused3; }; struct Edrvparam { ushort size; /* max. buffer (struct) size */ ushort flags; ulong physcyls; ulong physheads; ulong phystracksects; uvlong physsects; ushort sectsz; /* pointer is required to be unaligned, bytes 26-29. ick. */ // void *dpte; /* ~0ull: invalid */ ushort dpteoff; /* device parameter table extension */ ushort dpteseg; /* remainder from edd 3.0 spec */ ushort key; /* 0xbedd if device path info present */ uchar dpilen; /* must be 44 (says edd 4.0) */ uchar _unused1; ushort _unused2; char bustype[4]; /* "PCI" or "ISA" */ char ifctype[8]; /* "ATA", "ATAPI", "SCSI", "USB", "1394", "FIBRE" */ uvlong ifcpath; uvlong devpath[2]; uchar _unused3; uchar dpicksum; }; int biosinited; int biosndevs; void *biosgetfspart(int i, char *name, int chatty); static Biosdev bdev[Maxdevs]; static Ureg regs; static RWlock devs; static int dreset(Devid drive); static Devbytes extgetsize(Biosdev *); static int drivecap(Devid drive); /* convert ah error code to a string (just common cases) */ static char * strerr(uchar err) { switch (err) { case 0: return "no error"; case 1: return "bad command"; case 0x80: return "disk timeout"; default: return "unknown"; } } static void assertlow64k(uintptr p, char *tag) { if (p & Highshort) panic("devbios: %s address %#p not in bottom 64k", tag, p); } static void initrealregs(Ureg *ureg) { memset(ureg, 0, sizeof *ureg); } /* * caller must zero or otherwise initialise *ureg, * other than ax, bx, dx, si & ds. */ static int biosdiskcall(Ureg *ureg, uchar op, ulong bx, ulong dx, ulong si) { int s; uchar err; s = splhi(); /* don't let the bios call be interrupted */ initrealregs(ureg); ureg->ax = op << 8; ureg->bx = bx; ureg->dx = dx; /* often drive id */ assertlow64k(si, "dap"); if(si && (si & Highshort) != ((si + Maxsectsz - 1) & Highshort)) print("biosdiskcall: dap address %#lux too near segment boundary\n", si); ureg->si = si; /* ds:si forms data address packet addr */ ureg->ds = 0; /* bottom 64K */ ureg->es = 0; /* es:bx is conventional buffer */ ureg->di = 0; /* buffer segment? */ ureg->flags = 0; /* * *ureg is copied into low memory (realmoderegs) and thence into * the machine registers before the BIOS call, and the registers are * copied into realmoderegs and thence into *ureg after. * * realmode loads these registers: di, si, ax, bx, cx, dx, ds, es. */ ureg->trap = Diskint; realmode(ureg); if (ureg->flags & CF) { if (dx == Baseid) { err = ureg->ax >> 8; print("\nbiosdiskcall: int %#x op %#ux drive %#lux " "failed, ah error code %#ux (%s)\n", Diskint, op, dx, err, strerr(err)); } splx(s); return -1; } splx(s); return 0; } /* * Find out what the bios knows about devices. * our boot device could be usb; ghod only knows where it will appear. */ int biosinit0(void) { int cap, mask, lastbit, ndrive; Devbytes size; Devid devid; Biosdev *bdp; static int beenhere; delay(Pause); /* pause to read the screen (DEBUG) */ if (biosinited || beenhere) return 0; beenhere = 1; ndrive = *(uchar *)KADDR(0x475); /* from bda */ if (Debug) print("%d bios drive(s)\n", ndrive); mask = lastbit = 0; for (devid = Baseid, biosndevs = 0; devid != 0 && biosndevs < Maxdevs && biosndevs < ndrive; devid++) { cap = drivecap(devid); /* don't reset; it seems to hang the bios */ if(cap < 0 || (cap & (Fixeddisk|Edd)) != (Fixeddisk|Edd) /* || devid != Baseid && dreset(devid) < 0 || */) continue; /* no suitable device */ /* found a live one */ lastbit = 1 << biosndevs; mask |= lastbit; bdp = &bdev[biosndevs]; bdp->id = devid; size = extgetsize(bdp); if (size == 0) continue; /* no device */ bdp->size = size; print("bios%d: drive %#ux: %,llud bytes, %d-byte sectors\n", biosndevs, devid, size, bdp->sectsz); biosndevs++; } USED(lastbit); if (Debug && ndrive != biosndevs) print("devbios: expected %d drives, found %d\n", ndrive, biosndevs); /* * some bioses seem to only be able to read from drive number 0x80 and * can't read from the highest drive number, even if there is only one. */ if (biosndevs > 0) biosinited = 1; else panic("devbios: no bios drives seen"); /* 9loadusb needs ≥ 1 */ delay(Pause); /* pause to read the screen (DEBUG) */ return mask; } static void biosreset(void) { biosinit0(); } static void biosinit(void) { } static Chan* biosattach(char *spec) { ulong drive; char *p; Chan *chan; drive = 0; if(spec && *spec){ drive = strtoul(spec, &p, 0); if((drive == 0 && p == spec) || *p || (drive >= Maxdevs)) error(Ebadarg); } if(bdev[drive].rootchan) return bdev[drive].rootchan; chan = devattach(L'☹', spec); if(waserror()){ chanfree(chan); nexterror(); } chan->dev = drive; bdev[drive].rootchan = chan; /* arbitrary initialisation can go here */ poperror(); return chan; } static int unitgen(Chan *c, ulong type, Dir *dp) { int perm, t; ulong vers; vlong size; char *p; Qid q; perm = 0644; size = 0; // d = unit2dev(UNIT(c->qid)); // vers = d->vers; vers = 0; t = QTFILE; switch(type){ default: return -1; case Qctl: p = "ctl"; break; case Qdata: p = "data"; perm = 0640; break; } mkqid(&q, QID(UNIT(c->qid), type), vers, t); devdir(c, q, p, size, eve, perm, dp); return 1; } static int topgen(Chan *c, ulong type, Dir *d) { int perm; vlong size; char *p; Qid q; size = 0; switch(type){ default: return -1; case Qdata: p = "data"; perm = 0644; break; } mkqid(&q, type, 0, QTFILE); devdir(c, q, p, size, eve, perm, d); return 1; } static int biosgen(Chan *c, char *, Dirtab *, int, int s, Dir *dp) { Qid q; if(c->qid.path == 0){ switch(s){ case DEVDOTDOT: q.path = 0; q.type = QTDIR; devdir(c, q, "#☹", 0, eve, 0555, dp); break; case 0: q.path = Qtopdir; q.type = QTDIR; devdir(c, q, "bios", 0, eve, 0555, dp); break; default: return -1; } return 1; } switch(TYPE(c->qid)){ default: return -1; case Qtopdir: if(s == DEVDOTDOT){ mkqid(&q, Qzero, 0, QTDIR); devdir(c, q, "bios", 0, eve, 0555, dp); return 1; } if(s < Qtopfiles) return topgen(c, Qtopbase + s, dp); s -= Qtopfiles; if(s >= 1) return -1; mkqid(&q, QID(s, Qunitdir), 0, QTDIR); devdir(c, q, "bios", 0, eve, 0555, dp); return 1; case Qdata: return unitgen(c, TYPE(c->qid), dp); } } static Walkqid* bioswalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, nil, 0, biosgen); } static int biosstat(Chan *c, uchar *db, int n) { return devstat(c, db, n, nil, 0, biosgen); } static Chan* biosopen(Chan *c, int omode) { return devopen(c, omode, 0, 0, biosgen); } static void biosclose(Chan *) { } #ifdef UNUSED int biosboot(int dev, char *file, Boot *b) { Bootfs *fs; if(strncmp(file, "dos!", 4) == 0) file += 4; if(strchr(file, '!') != nil || strcmp(file, "") == 0) { print("syntax is bios0!file\n"); return -1; } fs = biosgetfspart(dev, "9fat", 1); if(fs == nil) return -1; return fsboot(fs, file, b); } #endif /* read n bytes at sector offset into a from drive id */ long sectread(Biosdev *bdp, void *a, long n, Devsects offset) { uchar *xch; uintptr xchaddr; Dap *dap; if(bdp->sectsz <= 0 || n < 0 || n > bdp->sectsz) return -1; xch = (uchar *)BIOSXCHG; assertlow64k(PADDR(xch), "biosxchg"); if(Debug) /* scribble on the buffer to provoke trouble */ memset(xch, 'r', bdp->sectsz); /* read into BIOSXCHG; alloc space for a worst-case (optical) sector */ dap = (Dap *)(xch + Maxsectsz); assertlow64k(PADDR(dap), "Dap"); memset(dap, 0, sizeof *dap); dap->size = sizeof *dap; dap->nsects = 1; dap->stsect = offset; xchaddr = PADDR(xch); assertlow64k(xchaddr, "sectread buffer"); dap->addr = xchaddr; /* ulong version */ dap->addroff = xchaddr; /* pedantic seg:off */ dap->addrseg = 0; dap->addr64 = xchaddr; /* paranoid redundancy */ dap->lnsects = 1; /* * ensure that entire buffer fits in low memory. */ if((dap->addr & Highshort) != ((dap->addr + Minsectsz - 1) & Highshort)) print("devbios: sectread: address %#lux too near seg boundary\n", dap->addr); if (Debug) print("reading bios drive %#ux sector %lld -> %#lux...", bdp->id, offset, dap->addr); delay(Pause); /* pause to read the screen (DEBUG) */ /* * int 13 read sector expects buffer seg in di?, * dap in si, 0x42 in ah, drive in dl. */ if (biosdiskcall(®s, Biosrdsect, 0, bdp->id, PADDR(dap)) < 0) { print("devbios: sectread: bios failed to read %ld @ sector %lld of %#ux\n", n, offset, bdp->id); return -1; } if (dap->nsects != 1) panic("devbios: sector read ok but read %d sectors", dap->nsects); if (Debug) print("OK\n"); /* copy into caller's buffer */ memmove(a, xch, n); if(0 && Debug) print("-%ux %ux %ux %ux--%16.16s-\n", xch[0], xch[1], xch[2], xch[3], (char *)xch + 480); delay(Pause); /* pause to read the screen (DEBUG) */ return n; } /* seems to hang bioses, at least vmware's */ static int dreset(Devid drive) { print("devbios: resetting %#ux...", drive); /* ignore carry flag for Biosinit */ biosdiskcall(®s, Biosinit, 0, drive, 0); print("\n"); return regs.ax? -1: 0; /* ax != 0 on error */ } /* returns capabilities bitmap */ static int drivecap(Devid drive) { int cap; if (biosdiskcall(®s, Biosckext, Imok, drive, 0) < 0) /* * we have an old bios without extensions, in theory. * in practice, there may just be no drive for this number. */ return -1; if(regs.bx != Youreok){ print("devbios: buggy bios: drive %#ux extension check " "returned %lux in bx\n", drive, regs.bx); return -1; } cap = regs.cx; if (Debug) { print("bios drive %#ux extensions version %#x.%d cx %#ux\n", drive, (uchar)(regs.ax >> 8), (uchar)regs.ax, cap); if ((uchar)(regs.ax >> 8) < 0x30) { print("drivecap: extensions prior to 0x30\n"); return -1; } print("\tsubsets supported:"); if (cap & Fixeddisk) print(" fixed disk access;"); if (cap & Drlock) print(" drive locking;"); if (cap & Edd) print(" enhanced disk support;"); if (cap & Bit64ext) print(" 64-bit extensions;"); print("\n"); } delay(Pause); /* pause to read the screen (DEBUG) */ return cap; } /* extended get size; reads bdp->id, fills in bdp->sectsz, returns # sectors */ static Devbytes extgetsize(Biosdev *bdp) { ulong sectsz; Edrvparam *edp; edp = (Edrvparam *)BIOSXCHG; memset(edp, 0, sizeof *edp); edp->size = sizeof *edp; edp->dpteseg = edp->dpteoff = ~0; /* no pointer */ edp->dpilen = 44; if (biosdiskcall(®s, Biosedrvparam, 0, bdp->id, PADDR(edp)) < 0) return 0; /* old bios without extensions */ if(Debug) { print("bios drive %#ux info flags %#ux", bdp->id, edp->flags); if (edp->key == 0xbedd) print("; edd 3.0 %.4s %.8s", edp->bustype, edp->ifctype); else print("; NOT edd 3.0 compliant (key %#ux)", edp->key); print("\n"); } if (edp->sectsz <= 0) { print("devbios: drive %#ux: sector size <= 0\n", bdp->id); edp->sectsz = 1; /* don't divide by 0 */ return 0; } sectsz = edp->sectsz; if (sectsz > Maxsectsz) { print("devbios: sector size %lud > %d\n", sectsz, Maxsectsz); return 0; } bdp->sectsz = sectsz; return edp->physsects * sectsz; } vlong biossize(uint dev) { Biosdev *bdp; if (dev >= biosndevs) return -1; bdp = &bdev[dev]; if (bdp->sectsz <= 0) return -1; return bdp->size / bdp->sectsz; } long biossectsz(uint dev) { Biosdev *bdp; if (dev >= biosndevs) return -1; bdp = &bdev[dev]; if (bdp->sectsz <= 0) return -1; return bdp->sectsz; } long biosread0(Bootfs *fs, void *a, long n) { int want, got, part, dev; long totnr, stuck; Devbytes offset; Biosdev *bdp; dev = fs->dev; /* only use of fs */ if(dev > biosndevs) return -1; if (n <= 0) return n; bdp = &bdev[dev]; offset = bdp->offset; stuck = 0; for (totnr = 0; totnr < n && stuck < 4; totnr += got) { if (bdp->sectsz == 0) { print("devbios: zero sector size\n"); return -1; } want = bdp->sectsz; if (totnr + want > n) want = n - totnr; if(0 && Debug && debugload) print("bios%d, read: %ld @ off %lld, want: %d, id: %#ux\n", dev, n, offset, want, bdp->id); part = offset % bdp->sectsz; if (part != 0) { /* back up to start of sector */ offset -= part; totnr -= part; if (totnr < 0) { print("biosread0: negative count %ld\n", totnr); return -1; } } if ((vlong)offset < 0) { print("biosread0: negative offset %lld\n", offset); return -1; } got = sectread(bdp, (char *)a + totnr, want, offset / bdp->sectsz); if(got <= 0) return -1; offset += got; bdp->offset = offset; if (got < bdp->sectsz) stuck++; /* we'll have to re-read this sector */ else stuck = 0; } return totnr; } vlong biosseek(Bootfs *fs, vlong off) { if (off < 0) { print("biosseek(fs, %lld) is illegal\n", off); return -1; } if(fs->dev > biosndevs) { print("biosseek: fs->dev %d > biosndevs %d\n", fs->dev, biosndevs); return -1; } bdev[fs->dev].offset = off; /* do not know size... (yet) */ return off; } static long biosread(Chan *c, void *db, long n, vlong off) { Biosdev *bp; switch(TYPE(c->qid)){ default: error(Eperm); case Qzero: case Qtopdir: return devdirread(c, db, n, 0, 0, biosgen); case Qdata: bp = &bdev[UNIT(c->qid)]; if (bp->rootchan == nil) panic("biosread: nil root chan for bios%ld", UNIT(c->qid)); biosseek(&bp->Bootfs, off); return biosread0(&bp->Bootfs, db, n); } } /* name is typically "9fat" */ void * biosgetfspart(int i, char *name, int chatty) { char part[32]; static Bootfs fs; fs.dev = i; fs.diskread = biosread0; fs.diskseek = biosseek; snprint(part, sizeof part, "#S/sdB0/%s", name); if(dosinit(&fs, part) < 0){ if(chatty) print("bios%d!%s does not contain a FAT file system\n", i, name); return nil; } return &fs; } static long bioswrite(Chan *, void *, long, vlong) { error("bios devices are read-only in bootstrap"); return 0; } Dev biosdevtab = { L'☹', "bios", biosreset, biosinit, devshutdown, biosattach, bioswalk, biosstat, biosopen, devcreate, biosclose, biosread, devbread, bioswrite, devbwrite, devremove, devwstat, devpower, devconfig, };