/* * boot driver for BIOS devices */ #include #include "lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "ureg.h" #include "fs.h" typedef uvlong Devbytes, Devsects; typedef struct Biosdrive Biosdrive; /* 1 drive -> ndevs */ typedef struct Biosdev Biosdev; enum { Debug = 0, Maxdevs = 4, CF = 1, Flopid = 0, /* first floppy */ Baseid = 0x80, /* first disk */ /* bios calls: int 13 disk services */ Biosinit = 0, /* initialise disk & floppy ctlrs */ Biosdrvsts, Bioschsrdsects, Biosdrvparam = 8, Biosctlrinit, Biosreset = 0xd, /* reset disk */ Biosdrvrdy = 0x10, Biosdrvtype = 0x15, Biosckext = 0x41, Biosrdsect, Biosedrvparam = 0x48, /* disk types */ Typenone = 0, Typedisk = 3, }; struct Biosdrive { int ndevs; }; struct Biosdev { Devbytes size; Devbytes offset; uchar id; /* drive number; e.g., 0x80 */ char type; ushort sectsz; }; typedef struct Extread { uchar size; uchar unused1; uchar nsects; uchar unused2; ulong addr; /* segment:offset */ uvlong stsect; /* starting sector */ } Extread; typedef struct Edrvparam { /* from edd 1.1 spec */ ushort size; /* max. buffer size */ ushort flags; ulong physcyls; ulong physheads; ulong phystracksects; uvlong physsects; ushort sectsz; void *dpte; /* ~0ull: invalid */ /* remainder from edd 3.0 spec */ ushort key; /* 0xbedd if present */ uchar dpilen; uchar unused1; ushort unused2; char bustype[4]; /* "PCI" or "ISA" */ char ifctype[8]; /* "ATA", "ATAPI", "SCSI", "USB", "1394", "FIBRE" */ uvlong ifcpath; uvlong devpath; uchar unused3; uchar dpicksum; } Edrvparam; void realmode(int intr, Ureg *ureg); /* from trap.c */ int onlybios0; int biosinited; static Biosdev bdev[Maxdevs]; static Biosdrive bdrive; static Ureg regs; static int dreset(uchar drive); static Devbytes extgetsize(Biosdev *); static Devsects getsize(uchar drive, char *type); static int islba(uchar drive); static int biosdiskcall(Ureg *rp, uchar op, ulong bx, ulong dx, ulong si) { memset(rp, 0, sizeof *rp); rp->ax = op << 8; rp->bx = bx; rp->dx = dx; /* often drive id */ rp->si = si; /* pass command in *rp, get results from there */ realmode(0x13, rp); if (rp->flags & CF) { // print("biosdiskcall: int 13 op 0x%ux drive 0x%lux failed, " // "ah error code 0x%ux\n", op, dx, (uchar)(rp->ax >> 8)); return -1; } return 0; } /* * Find out what the bios knows about devices. * our boot device could be usb; ghod only knows where it will appear. */ int biosinit(void) { int devid, lba, mask, lastbit; Devbytes size; char type; Biosdev *bdp; static int beenhere; mask = lastbit = 0; if (beenhere) return mask; beenhere = 1; /* 9pxeload can't use bios int 13 calls; they wedge the machine */ if (pxe || getconf("*nobiosload") != nil || onlybios0 || !biosinited) return mask; for (devid = 0; devid < (1 << 8) && bdrive.ndevs < Maxdevs; devid++) { lba = islba(devid); if(!lba /* || devid != Baseid && dreset(devid) < 0 */ ) continue; type = Typedisk; /* HACK */ if (getsize(devid, &type) == 0) { /* no device, end of range */ devid &= ~0xf; devid += 0x10; devid--; continue; } lastbit = 1 << bdrive.ndevs; mask |= lastbit; bdp = &bdev[bdrive.ndevs]; bdp->id = devid; bdp->type = type; size = extgetsize(bdp); bdp->size = size; print("bios%d: drive 0x%ux: %llud bytes, type %d\n", bdrive.ndevs, devid, size, type); bdrive.ndevs++; } /* * bioses seem to only be able to read from drive number 0x80 * and certainly can't read from the highest drive number when we * call them, even if there is only one. attempting to read from * the last drive number yields a hung machine or a two-minute pause. */ if (bdrive.ndevs > 0) { if (bdrive.ndevs == 1) { print("biosinit: sorry, only one bios drive; " "can't read last one\n"); onlybios0 = 1; } else biosinited = 1; bdrive.ndevs--; /* omit last drive number; it can't be read */ mask &= ~lastbit; } return mask; } void biosinitdev(int i, char *name) { if(i >= bdrive.ndevs) panic("biosinitdev"); sprint(name, "bios%d", i); } void biosprintdevs(int i) { if(i >= bdrive.ndevs){ print("got a print for %d, only got %d\n", i, bdrive.ndevs); panic("biosprintdevs"); } print(" bios%d", i); } int biosboot(int dev, char *file, Boot *b) { Fs *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); } /* read n bytes at sector offset into a from drive id */ long sectread(Biosdev *bdp, void *a, long n, Devsects offset) { uchar *biosparam, *cp; Extread *erp; if(n < 0 || n > bdp->sectsz) return -1; if(Debug) memset((uchar *)BIOSXCHG, 'r', bdp->sectsz); /* preclean the buffer. */ biosdiskcall(®s, Biosdrvrdy, 0, bdp->id, 0); /* space for a BIG sector, just in case... */ biosparam = (uchar *)BIOSXCHG + 2*1024; /* read into BIOSXCHG */ erp = (Extread *)biosparam; memset(erp, 0, sizeof *erp); erp->size = sizeof *erp; erp->nsects = 1; erp->addr = PADDR(BIOSXCHG); erp->stsect = offset; if (biosdiskcall(®s, Biosrdsect, 0, bdp->id, PADDR(erp)) < 0) { print("sectread: bios failed to read %ld @ sector %lld of 0x%ux\n", n, offset, bdp->id); return -1; } /* copy into caller's buffer */ memmove(a, (char *)BIOSXCHG, n); if(Debug){ cp = (uchar *)BIOSXCHG; print("-%ux %ux %ux %ux--%16.16s-\n", cp[0], cp[1], cp[2], cp[3], (char *)cp + 480); } return n; } /* not tested yet. */ static int dreset(uchar drive) { if (0) { print("devbios: resetting disk controllers..."); biosdiskcall(®s, Biosinit, 0, drive, 0); print("\n"); } return regs.ax? -1: 0; /* ax!=0 on error */ } static int islba(uchar drive) { if (biosdiskcall(®s, Biosckext, 0x55aa, drive, 0) < 0) return 0; if(regs.bx != 0xaa55){ print("islba: buggy bios\n"); return 0; } if (Debug) print("islba: drive 0x%ux extensions version %d.%d cx 0x%lux\n", drive, (uchar)(regs.ax >> 8), (uchar)regs.ax, regs.cx); /* cx: 4=edd, 1=use dap */ return regs.cx & 1; /* dap bit */ } /* * works so so... some floppies are 0x80+x when they shouldn't be, * and report lba even if they cannot... */ static Devsects getsize(uchar id, char *typep) { int dtype; if (biosdiskcall(®s, Biosdrvtype, 0x55aa, id, 0) < 0) return 0; dtype = (ushort)regs.ax >> 8; if(dtype == Typenone){ print("no such device 0x%ux of type %d\n", id, dtype); return 0; } if(dtype != Typedisk){ print("non-disk device 0x%ux of type %d\n", id, dtype); return 0; } *typep = dtype; return (ushort)regs.cx | regs.dx << 16; } /* extended get size */ static Devbytes extgetsize(Biosdev *bdp) { Edrvparam *edp; edp = (Edrvparam *)BIOSXCHG; memset(edp, 0, sizeof *edp); edp->size = sizeof *edp; edp->dpilen = 36; if (biosdiskcall(®s, Biosedrvparam, 0, bdp->id, PADDR(edp)) < 0) return 0; if(Debug) { print("extgetsize: drive 0x%ux info flags 0x%ux", bdp->id, edp->flags); if (edp->key == 0xbedd) print(" %s %s", edp->bustype, edp->ifctype); print("\n"); } if (edp->sectsz <= 0) { print("extgetsize: drive 0x%ux: non-positive sector size\n", bdp->id); edp->sectsz = 1; /* don't divide by zero */ } bdp->sectsz = edp->sectsz; return edp->physsects * edp->sectsz; } long biosread(Fs *fs, void *a, long n) { int want, got, part; long totnr, stuck; Devbytes offset; Biosdev *bdp; if(fs->dev > bdrive.ndevs) return -1; if (n <= 0) return n; bdp = &bdev[fs->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(Debug) print("bios%d, read: %ld @ off %lld, want: %d, id: 0x%ux\n", fs->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("biosread: negative count %ld\n", totnr); return -1; } } if ((vlong)offset < 0) { print("biosread: negative offset %lld\n", offset); return -1; } got = sectread(bdp, (char *)a + totnr, want, offset/bdp->sectsz); if(got <= 0){ // print("biosread: failed to read %ld @ off %lld of 0x%ux, " // "want %d got %d\n", // n, offset, bdp->id, want, got); 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(Fs *fs, vlong off) { if (off < 0) { print("biosseek(fs, %lld) is illegal\n", off); return -1; } if(fs->dev > bdrive.ndevs) { print("biosseek: fs->dev %d > bdrive.ndevs %d\n", fs->dev, bdrive.ndevs); return -1; } bdev[fs->dev].offset = off; /* do not know size... (yet) */ return off; } void * biosgetfspart(int i, char *name, int chatty) { static Fs fs; if(strcmp(name, "9fat") != 0){ if(chatty) print("unknown partition bios%d!%s (use bios%d!9fat)\n", i, name, i); return nil; } fs.dev = i; fs.diskread = biosread; fs.diskseek = biosseek; if(dosinit(&fs) < 0){ if(chatty) print("bios%d!%s does not contain a FAT file system\n", i, name); return nil; } return &fs; }