ref: 332f070e5a51e5fcc1f2f17f15f67aa98a01b5f4
dir: /sys/src/boot/pc/devbios.c/
/*
* boot driver for BIOS LBA devices
* heavily dependent upon correct BIOS implementation
*/
#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,
Pause = 0, /* delay to read debugging */
Maxdevs = 8,
CF = 1, /* carry flag: indicates an error */
Flopid = 0, /* first floppy */
Baseid = 0x80, /* first disk */
/* cx bits of Biosckext results */
Dap = 1<<0,
Drlock = 1<<1,
Edd = 1<<2,
/* bios calls: int 0x13 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,
/* magic numbers for bios calls */
Imok = 0x55aa,
Youreok = 0xaa55,
};
struct Biosdrive {
int ndevs;
};
struct Biosdev {
Devbytes size;
Devbytes offset;
uchar id; /* drive number; e.g., 0x80 */
char type;
ushort sectsz;
};
typedef struct Extread { /* 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 */
/* from edd 3.0: */
uvlong buffer; /* instead of addr, if addr is ~0 */
} Extread;
typedef struct Edrvparam {
/* from edd 1.1 spec */
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 */
uchar dpte[4];
/* 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);
void
stopbiosload(void)
{
biosload = 0;
print("disabling bios loading\n");
}
/* convert ah error code to a string (just common cases) */
static char *
strerr(uchar err)
{
switch (err) {
case 0:
return "none";
case 0x80:
return "disk timeout";
default:
return "unknown";
}
}
/*
* caller must zero or otherwise initialise *rp,
* other than ax, bx, dx, si & ds.
*/
static int
biosdiskcall(Ureg *up, uchar op, ulong bx, ulong dx, ulong si)
{
int s;
uchar err;
s = splhi(); /* don't let the bios call be interrupted */
up->ax = op << 8;
up->bx = bx;
up->dx = dx; /* often drive id */
/*
* ensure that dap addr fits in a short.
*/
if((si & 0xffff0000) != 0)
print("biosdiskcall: dap address %#lux not a short\n", si);
/* assume si is in first 64K */
if((si & 0xffff0000) != ((si + 512 - 1) & 0xffff0000))
print("biosdiskcall: dap address %#lux too near segment boundary\n",
si);
up->si = si; /* ds:si forms data access packet addr */
up->ds = 0;
up->di = 0;
/*
* *up 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 *up after.
*
* realmode loads these registers: di, si, ax, bx, cx, dx, ds, es.
*/
realmode(0x13, up);
splx(s);
if (up->flags & CF) {
if (Debug && dx == Baseid) {
err = up->ax >> 8;
print("\nbiosdiskcall: int 0x13 op %#ux drive %#lux "
"failed, ah error code %#ux (%s)\n",
op, dx, err, strerr(err));
}
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, ndrive;
Devbytes size;
char type;
Biosdev *bdp;
static int beenhere;
delay(Pause); /* pause to read the screen (DEBUG) */
/*
* 9pxeload can't use bios int 13 calls; they wedge the machine.
*/
if (pxe || !biosload || onlybios0 || biosinited || beenhere)
return 0;
beenhere = 1;
ndrive = *(uchar *)KADDR(0x475);
if (Debug)
print("bios claims %d drive(s)\n", ndrive);
mask = lastbit = 0;
for (devid = Baseid; devid < (1 << 8) && bdrive.ndevs < Maxdevs;
devid++) {
lba = islba(devid);
if (lba < 0)
break;
/* don't reset; it seems to hang the bios */
if(!lba /* || devid != Baseid && dreset(devid) < 0 */ )
continue;
type = Typenone;
if (getsize(devid, &type) == 0) /* no device */
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 %#ux: %,llud bytes, type %d\n",
bdrive.ndevs, devid, size, type);
bdrive.ndevs++;
}
USED(lastbit);
// islba(Baseid); /* do a successful operation to make bios happy again */
if (Debug && ndrive != bdrive.ndevs)
print("bios: ndrive %d != bdrive.ndevs %d\n",
ndrive, bdrive.ndevs);
if (ndrive < bdrive.ndevs)
bdrive.ndevs = ndrive; /* use smaller estimate */
/*
* some 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.
*/
if (bdrive.ndevs > 0)
biosinited = 1;
delay(Pause); /* pause to read the screen (DEBUG) */
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);
}
static void
dump(void *addr, int wds)
{
unsigned i;
ulong *p = addr;
for (i = 0; i < wds; i++)
print("%lux ", p[i]);
if (i % 8 == 7)
print("\n");
print("\n");
}
/* realmode loads these registers: di, si, ax, bx, cx, dx, ds, es */
static void
initrealregs(Ureg *up)
{
memset(up, 0, sizeof *up);
}
/* 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)
/* scribble on the buffer to provoke trouble */
memset((uchar *)BIOSXCHG, 'r', bdp->sectsz);
/* space for a big, optical-size 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->stsect = offset;
erp->buffer = PADDR(BIOSXCHG);
erp->addr = PADDR(BIOSXCHG);
erp->addroff = PADDR(BIOSXCHG); /* pedantic seg:off */
erp->addrseg = 0;
/*
* ensure that buffer addr fits in a short.
*/
if((erp->addr & 0xffff0000) != 0)
print("sectread: address %#lux not a short\n", erp->addr);
if((erp->addr & 0xffff0000) != ((erp->addr + 512 - 1) & 0xffff0000))
print("sectread: address %#lux too near seg boundary\n",
erp->addr);
if (Debug)
print("reading drive %#ux sector %lld -> %#lux...",
bdp->id, offset, erp->addr);
initrealregs(®s);
// regs.es = erp->addr >> 16;
regs.es = 0;
delay(Pause); /* pause to read the screen (DEBUG) */
/*
* int 13 read sector expects, buffer seg in di?,
* dap (erp) in si, 0x42 in ah, drive in dl.
*/
if (biosdiskcall(®s, Biosrdsect, erp->addr, bdp->id, PADDR(erp)) < 0) {
print("sectread: bios failed to read %ld @ sector %lld of %#ux\n",
n, offset, bdp->id);
stopbiosload(); /* stop trying before we wedge */
return -1;
}
if (Debug)
print("OK\n");
/* copy into caller's buffer */
memmove(a, (char *)BIOSXCHG, n);
if(0 && Debug){
cp = (uchar *)BIOSXCHG;
print("-%ux %ux %ux %ux--%16.16s-\n",
cp[0], cp[1], cp[2], cp[3], (char *)cp + 480);
}
delay(Pause); /* pause to read the screen (DEBUG) */
return n;
}
/* not tested yet. */
static int
dreset(uchar drive)
{
print("devbios: resetting %#ux...", drive);
initrealregs(®s);
if (biosdiskcall(®s, Biosinit, 0, drive, 0) < 0)
print("failed");
print("\n");
return regs.ax? -1: 0; /* ax!=0 on error */
}
static int
islba(uchar drive)
{
initrealregs(®s);
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("islba: buggy bios: drive %#ux extension check returned "
"%lux in bx\n", drive, regs.bx);
stopbiosload();
return -1;
}
if (Debug) {
print("islba: drive %#ux extensions version %d.%d cx %#lux\n",
drive, (uchar)(regs.ax >> 8), (uchar)regs.ax, regs.cx);
print("\tsubsets supported:");
if (regs.cx & Dap)
print(" disk access;");
if (regs.cx & Drlock)
print(" drive locking;");
if (regs.cx & Edd)
print(" enhanced disk support;");
print("\n");
}
delay(Pause); /* pause to read the screen (DEBUG) */
return regs.cx & Dap;
}
/*
* 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;
initrealregs(®s);
if (biosdiskcall(®s, Biosdrvtype, Imok, id, 0) < 0)
return 0;
*typep = dtype = (ushort)regs.ax >> 8;
if(dtype == Typenone){
print("no such device %#ux of type %d\n", id, dtype);
return 0;
}
if(dtype != Typedisk){
print("non-disk device %#ux of type %d\n", id, dtype);
return 0;
}
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;
initrealregs(®s);
if (biosdiskcall(®s, Biosedrvparam, 0, bdp->id, PADDR(edp)) < 0)
return 0; /* old bios without extensions */
if(Debug) {
print("extgetsize: drive %#ux info flags %#ux",
bdp->id, edp->flags);
if (edp->key == 0xbedd)
print(" %.4s %.8s", edp->bustype, edp->ifctype);
print("\n");
}
if (edp->sectsz <= 0) {
print("extgetsize: drive %#ux: sector size <= 0\n", bdp->id);
edp->sectsz = 1; /* don't divide by 0 */
}
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(!biosload || 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(0 && Debug && debugload)
print("bios%d, read: %ld @ off %lld, want: %d, id: %#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)
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;
}