diff options
| author | Charles.Forsyth <devnull@localhost> | 2008-06-11 14:21:44 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2008-06-11 14:21:44 +0000 |
| commit | 8a8c2d742b51525f66c2210e3c8a251de10022ff (patch) | |
| tree | 8282ce595e5fbe2e487dc20f54891d9e9e7cbf37 /os/boot/pc/devbios.c | |
| parent | 31a18a6996a6b5927e39cc553696c167e6c88e3d (diff) | |
20080611-1520
Diffstat (limited to 'os/boot/pc/devbios.c')
| -rw-r--r-- | os/boot/pc/devbios.c | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/os/boot/pc/devbios.c b/os/boot/pc/devbios.c new file mode 100644 index 00000000..d178fe81 --- /dev/null +++ b/os/boot/pc/devbios.c @@ -0,0 +1,428 @@ +/* + * boot driver for BIOS devices + */ +#include <u.h> +#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) { + 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; +} |
