#include <sys/param.h>
#include <sys/reboot.h>
#include <sys/disklabel.h>
#include <isofs/cd9660/iso.h>
#include <lib/libsa/saerrno.h>
#include <machine/tss.h>
#include <machine/biosvar.h>
#include "biosdev.h"
#include "disk.h"
#include "libsa.h"
#ifdef SOFTRAID
#include <dev/softraidvar.h>
#include <lib/libsa/softraid.h>
#include "softraid_amd64.h"
#endif
static const char *biosdisk_err(u_int);
static int biosdisk_errno(u_int);
int CHS_rw (int, int, int, int, int, int, void *);
static int EDD_rw (int, int, u_int32_t, u_int32_t, void *);
static int biosd_io(int, bios_diskinfo_t *, u_int, int, void *);
static u_int findopenbsd(bios_diskinfo_t *, const char **);
extern int debug;
int bios_bootdev;
int bios_cddev = -1;
struct EDD_CB {
u_int8_t edd_len;
u_int8_t edd_res1;
u_int8_t edd_nblk;
u_int8_t edd_res2;
u_int16_t edd_off;
u_int16_t edd_seg;
u_int64_t edd_daddr;
};
static int
biosdreset(int dev)
{
int rv;
__asm volatile (DOINT(0x13) "; setc %b0" : "=a" (rv)
: "0" (0), "d" (dev) : "%ecx", "cc");
return ((rv & 0xff)? rv >> 8 : 0);
}
int
bios_getdiskinfo(int dev, bios_diskinfo_t *pdi)
{
u_int rv;
rv = biosdreset(dev);
#ifdef BIOS_DEBUG
if (debug)
printf("getinfo: try #8, 0x%x, %p\n", dev, pdi);
#endif
__asm volatile (DOINT(0x13) "\n\t"
"setc %b0; movzbl %h1, %1\n\t"
"movzbl %%cl, %3; andb $0x3f, %b3\n\t"
"xchgb %%cl, %%ch; rolb $2, %%ch"
: "=a" (rv), "=d" (pdi->bios_heads),
"=c" (pdi->bios_cylinders),
"=b" (pdi->bios_sectors)
: "0" (0x0800), "1" (dev) : "cc");
#ifdef BIOS_DEBUG
if (debug) {
printf("getinfo: got #8\n");
printf("disk 0x%x: %d,%d,%d\n", dev, pdi->bios_cylinders,
pdi->bios_heads, pdi->bios_sectors);
}
#endif
if (rv & 0xff)
return 1;
pdi->bios_number = dev;
pdi->bios_heads++;
pdi->bios_cylinders &= 0x3ff;
pdi->bios_cylinders++;
if (dev & 0x80 && (dev == 0x80 || dev == 0x81 || dev == bios_bootdev)) {
int bm;
#ifdef BIOS_DEBUG
if (debug)
printf("getinfo: try #41, 0x%x\n", dev);
#endif
__asm volatile(DOINT(0x13) "; setc %b0"
: "=a" (rv), "=c" (bm)
: "0" (0x4100), "b" (0x55aa), "d" (dev) : "cc");
if (!(rv & 0xff) && (BIOS_regs.biosr_bx & 0xffff) == 0xaa55)
pdi->bios_edd = (bm & 0xffff) | ((rv & 0xff) << 16);
else
pdi->bios_edd = -1;
#ifdef BIOS_DEBUG
if (debug) {
printf("getinfo: got #41\n");
printf("disk 0x%x: 0x%x\n", dev, bm);
}
#endif
if (!(pdi->bios_edd & EXT_BM_EDA))
pdi->bios_edd = -1;
} else
pdi->bios_edd = -1;
if (pdi->bios_edd != -1)
return 0;
if (!pdi->bios_cylinders || !pdi->bios_heads || !pdi->bios_sectors)
return 1;
if (pdi->bios_heads < 2)
return 1;
return 0;
}
int
CHS_rw(int rw, int dev, int cyl, int head, int sect, int nsect, void *buf)
{
int rv;
rw = rw == F_READ ? 2 : 3;
BIOS_regs.biosr_es = (u_int32_t)buf >> 4;
__asm volatile ("movb %b7, %h1\n\t"
"movb %b6, %%dh\n\t"
"andl $0xf, %4\n\t"
"xchgb %%ch, %%cl\n\t"
"rorb $2, %%cl\n\t"
"orb %b5, %%cl\n\t"
"inc %%cx\n\t"
DOINT(0x13) "\n\t"
"setc %b0"
: "=a" (rv)
: "0" (nsect), "d" (dev), "c" (cyl),
"b" (buf), "m" (sect), "m" (head),
"m" (rw)
: "cc", "memory");
return ((rv & 0xff)? rv >> 8 : 0);
}
static __inline int
EDD_rw(int rw, int dev, u_int32_t daddr, u_int32_t nblk, void *buf)
{
int rv;
volatile static struct EDD_CB cb;
cb.edd_res1 = 0;
cb.edd_res2 = 0;
cb.edd_len = sizeof(cb);
cb.edd_nblk = nblk;
cb.edd_seg = ((u_int32_t)buf >> 4) & 0xffff;
cb.edd_off = (u_int32_t)buf & 0xf;
cb.edd_daddr = daddr;
if (!cb.edd_seg && !cb.edd_off)
return 1;
BIOS_regs.biosr_ds = (u_int32_t)&cb >> 4;
__asm volatile (DOINT(0x13) "; setc %b0" : "=a" (rv)
: "0" ((rw == F_READ)? 0x4200: 0x4300),
"d" (dev), "S" ((int) (&cb) & 0xf) : "%ecx", "cc");
return ((rv & 0xff)? rv >> 8 : 0);
}
int
biosd_io(int rw, bios_diskinfo_t *bd, u_int off, int nsect, void *buf)
{
int dev = bd->bios_number;
int j, error;
void *bb, *bb1 = NULL;
int bbsize = nsect * DEV_BSIZE;
if (bd->flags & BDI_EL_TORITO) {
dev &= 0xff;
off /= (ISO_DEFAULT_BLOCK_SIZE / DEV_BSIZE);
nsect /= (ISO_DEFAULT_BLOCK_SIZE / DEV_BSIZE);
}
if (((((u_int32_t)buf) & ~0xffff) !=
(((u_int32_t)buf + bbsize) & ~0xffff)) ||
(((u_int32_t)buf) >= 0x100000)) {
bb = bb1 = alloc(bbsize * 2);
if ((((u_int32_t)bb) & ~0xffff) !=
(((u_int32_t)bb + bbsize - 1) & ~0xffff))
bb = (void *)(((u_int32_t)bb + bbsize - 1) & ~0xffff);
if (rw != F_READ)
bcopy(buf, bb, bbsize);
} else
bb = buf;
for (error = 1, j = 5; j-- && error; ) {
if (bd->bios_edd != -1) {
error = EDD_rw(rw, dev, off, nsect, bb);
} else {
int cyl, head, sect;
size_t i, n;
char *p = bb;
for (error = i = 0; error == 0 && i < nsect;
i += n, off += n, p += n * DEV_BSIZE) {
btochs(off, cyl, head, sect, bd->bios_heads,
bd->bios_sectors);
if ((sect + (nsect - i)) >= bd->bios_sectors)
n = bd->bios_sectors - sect;
else
n = nsect - i;
error = CHS_rw(rw, dev, cyl, head, sect, n, p);
if (error == 0x11)
error = 0;
}
}
switch (error) {
case 0x00:
case 0x11:
error = 0;
break;
default:
#ifdef BIOS_DEBUG
if (debug)
printf("\nBIOS error 0x%x (%s)\n",
error, biosdisk_err(error));
#endif
biosdreset(dev);
break;
}
}
if (bb != buf && rw == F_READ)
bcopy(bb, buf, bbsize);
free(bb1, bbsize * 2);
#ifdef BIOS_DEBUG
if (debug) {
if (error != 0)
printf("=0x%x(%s)", error, biosdisk_err(error));
putchar('\n');
}
#endif
return error;
}
#define MAXSECTS 32
int
biosd_diskio(int rw, struct diskinfo *dip, u_int off, int nsect, void *buf)
{
char *dest = buf;
int n, ret;
for (ret = 0; ret == 0 && nsect > 0;
off += MAXSECTS, dest += MAXSECTS * DEV_BSIZE, nsect -= MAXSECTS) {
n = nsect >= MAXSECTS ? MAXSECTS : nsect;
ret = biosd_io(rw, &dip->bios_info, off, n, dest);
}
return ret;
}
static u_int
findopenbsd(bios_diskinfo_t *bd, const char **err)
{
struct dos_mbr mbr;
struct dos_partition *dp;
u_int mbroff = DOSBBSECTOR;
u_int mbr_eoff = DOSBBSECTOR;
int error, i, maxebr = DOS_MAXEBR, nextebr;
again:
if (!maxebr--) {
*err = "too many extended partitions";
return (-1);
}
bzero(&mbr, sizeof(mbr));
error = biosd_io(F_READ, bd, mbroff, 1, &mbr);
if (error) {
*err = biosdisk_err(error);
return (-1);
}
if (mbr.dmbr_sign != DOSMBR_SIGNATURE) {
*err = "bad MBR signature\n";
return (-1);
}
nextebr = 0;
for (i = 0; i < NDOSPART; i++) {
dp = &mbr.dmbr_parts[i];
if (!dp->dp_size)
continue;
#ifdef BIOS_DEBUG
if (debug)
printf("found partition %u: "
"type %u (0x%x) offset %u (0x%x)\n",
(int)(dp - mbr.dmbr_parts),
dp->dp_typ, dp->dp_typ,
dp->dp_start, dp->dp_start);
#endif
if (dp->dp_typ == DOSPTYP_OPENBSD) {
if (dp->dp_start > (dp->dp_start + mbroff))
continue;
return (dp->dp_start + mbroff);
}
if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND ||
dp->dp_typ == DOSPTYP_EXTENDL)) {
nextebr = dp->dp_start + mbr_eoff;
if (nextebr < dp->dp_start)
nextebr = (u_int)-1;
if (mbr_eoff == DOSBBSECTOR)
mbr_eoff = dp->dp_start;
}
}
if (nextebr && nextebr != (u_int)-1) {
mbroff = nextebr;
goto again;
}
return (-1);
}
const char *
bios_getdisklabel(bios_diskinfo_t *bd, struct disklabel *label)
{
u_int start = 0;
char buf[DEV_BSIZE];
const char *err = NULL;
int error;
if (bd->bios_edd == -1 &&
(bd->bios_heads == 0 || bd->bios_sectors == 0))
return "failed to read disklabel";
if (bd->bios_number & 0x80) {
start = findopenbsd(bd, &err);
if (start == (u_int)-1) {
if (err != NULL)
return (err);
return "no OpenBSD partition\n";
}
}
#ifdef BIOS_DEBUG
if (debug)
printf("loading disklabel @ %u\n", start + DOS_LABELSECTOR);
#endif
error = biosd_io(F_READ, bd, start + DOS_LABELSECTOR, 1, buf);
if (error)
return "failed to read disklabel";
return (getdisklabel(buf, label));
}
int
biosopen(struct open_file *f, ...)
{
#ifdef SOFTRAID
struct sr_boot_volume *bv;
#endif
register char *cp, **file;
dev_t maj, unit, part;
struct diskinfo *dip;
int biosdev, devlen;
const char *st;
va_list ap;
char *dev;
va_start(ap, f);
cp = *(file = va_arg(ap, char **));
va_end(ap);
#ifdef BIOS_DEBUG
if (debug)
printf("%s\n", cp);
#endif
f->f_devdata = NULL;
dev = cp;
if (cp[4] == ':')
devlen = 2;
else if (cp[5] == ':')
devlen = 3;
else
return ENOENT;
cp += devlen;
if ('0' <= *cp && *cp <= '9')
unit = *cp++ - '0';
else {
printf("Bad unit number\n");
return EUNIT;
}
part = DL_PARTNAME2NUM(*cp++);
if (part == -1) {
printf("Bad partition\n");
return EPART;
}
cp++;
if (*cp != 0)
*file = cp;
else
f->f_flags |= F_RAW;
#ifdef SOFTRAID
if (strncmp("sr", dev, 2) == 0) {
f->f_flags |= F_NOWRITE;
SLIST_FOREACH(bv, &sr_volumes, sbv_link)
if (bv->sbv_unit == unit)
break;
if (bv == NULL) {
printf("Unknown device: sr%d\n", unit);
return EADAPT;
}
if ((bv->sbv_level == 'C' || bv->sbv_level == 0x1C) &&
bv->sbv_keys == NULL) {
if (sr_crypto_unlock_volume(bv) != 0)
return EPERM;
}
if (bv->sbv_diskinfo == NULL) {
dip = alloc(sizeof(struct diskinfo));
bzero(dip, sizeof(*dip));
dip->strategy = biosstrategy;
bv->sbv_diskinfo = dip;
dip->sr_vol = bv;
dip->bios_info.flags |= BDI_BADLABEL;
}
dip = bv->sbv_diskinfo;
if (dip->bios_info.flags & BDI_BADLABEL) {
bv->sbv_part = 'c';
if (sr_getdisklabel(bv, &dip->disklabel))
return ERDLAB;
dip->bios_info.flags &= ~BDI_BADLABEL;
check_hibernate(dip);
}
bv->sbv_part = DL_PARTNUM2NAME(part);
bootdev_dip = dip;
f->f_devdata = dip;
return 0;
}
#endif
for (maj = 0; maj < nbdevs &&
strncmp(dev, bdevs[maj], devlen); maj++);
if (maj >= nbdevs) {
printf("Unknown device: ");
for (cp = *file; *cp != ':'; cp++)
putchar(*cp);
putchar('\n');
return EADAPT;
}
biosdev = unit;
switch (maj) {
case 0:
case 4:
case 17:
biosdev |= 0x80;
break;
case 2:
break;
case 6:
biosdev = bios_bootdev & 0xff;
break;
default:
return ENXIO;
}
bootdev_dip = dip = dklookup(biosdev);
{ dev_t bsd_dev;
bsd_dev = dip->bios_info.bsd_dev;
dip->bsddev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev),
B_CONTROLLER(bsd_dev), unit, part);
dip->bootdev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev),
B_CONTROLLER(bsd_dev), B_UNIT(bsd_dev), part);
}
#if 0
dip->bios_info.bsd_dev = dip->bootdev;
bootdev = dip->bootdev;
#endif
#ifdef BIOS_DEBUG
if (debug) {
printf("BIOS geometry: heads=%u, s/t=%u; EDD=%d\n",
dip->bios_info.bios_heads, dip->bios_info.bios_sectors,
dip->bios_info.bios_edd);
}
#endif
if (dip->bios_info.flags & BDI_BADLABEL) {
st = bios_getdisklabel(&dip->bios_info, &dip->disklabel);
#ifdef BIOS_DEBUG
if (debug && st)
printf("%s\n", st);
#endif
if (!st) {
dip->bios_info.flags &= ~BDI_BADLABEL;
dip->bios_info.flags |= BDI_GOODLABEL;
} else
return ERDLAB;
}
f->f_devdata = dip;
return 0;
}
const u_char bidos_errs[] =
"\x01" "invalid function/parameter\0"
"\x02" "address mark not found\0"
"\x03" "write-protected\0"
"\x04" "sector not found\0"
"\x05" "reset failed\0"
"\x06" "disk changed\0"
"\x07" "drive parameter activity failed\0"
"\x08" "DMA overrun\0"
"\x09" "data boundary error\0"
"\x0A" "bad sector detected\0"
"\x0B" "bad track detected\0"
"\x0C" "invalid media\0"
"\x0E" "control data address mark detected\0"
"\x0F" "DMA arbitration level out of range\0"
"\x10" "uncorrectable CRC or ECC error on read\0"
"\x20" "controller failure\0"
"\x31" "no media in drive\0"
"\x32" "incorrect drive type in CMOS\0"
"\x40" "seek failed\0"
"\x80" "operation timed out\0"
"\xAA" "drive not ready\0"
"\xB0" "volume not locked in drive\0"
"\xB1" "volume locked in drive\0"
"\xB2" "volume not removable\0"
"\xB3" "volume in use\0"
"\xB4" "lock count exceeded\0"
"\xB5" "valid eject request failed\0"
"\xBB" "undefined error\0"
"\xCC" "write fault\0"
"\xE0" "status register error\0"
"\xFF" "sense operation failed\0"
"\x00" "\0";
static const char *
biosdisk_err(u_int error)
{
register const u_char *p = bidos_errs;
while (*p && *p != error)
while (*p++);
return ++p;
}
const struct biosdisk_errors {
u_char error;
u_char errno;
} tab[] = {
{ 0x01, EINVAL },
{ 0x03, EROFS },
{ 0x08, EINVAL },
{ 0x09, EINVAL },
{ 0x0A, EBSE },
{ 0x0B, EBSE },
{ 0x0C, ENXIO },
{ 0x0D, EINVAL },
{ 0x10, EECC },
{ 0x20, EHER },
{ 0x31, ENXIO },
{ 0x32, ENXIO },
{ 0x00, EIO }
};
static int
biosdisk_errno(u_int error)
{
register const struct biosdisk_errors *p;
if (error == 0)
return 0;
for (p = tab; p->error && p->error != error; p++)
;
return p->errno;
}
int
biosstrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
size_t *rsize)
{
struct diskinfo *dip = (struct diskinfo *)devdata;
u_int8_t error = 0;
size_t nsect;
#ifdef SOFTRAID
if (dip->sr_vol)
return sr_strategy(dip->sr_vol, rw, blk, size, buf, rsize);
#endif
nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE;
blk += dip->disklabel.d_partitions[B_PARTITION(dip->bsddev)].p_offset;
if (blk < 0)
error = EINVAL;
else
error = biosd_diskio(rw, dip, blk, nsect, buf);
#ifdef BIOS_DEBUG
if (debug) {
if (error != 0)
printf("=0x%x(%s)", error, biosdisk_err(error));
putchar('\n');
}
#endif
if (rsize != NULL)
*rsize = nsect * DEV_BSIZE;
return (biosdisk_errno(error));
}
int
biosclose(struct open_file *f)
{
f->f_devdata = NULL;
return 0;
}
int
biosioctl(struct open_file *f, u_long cmd, void *data)
{
return 0;
}