ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /os/boot/pc/devbios.c/
/*
* 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;
}