bioscd.c revision 199767f8919635c4928607450d9e0abb932109ce
/*-
* Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
* Copyright (c) 2001 John H. Baldwin <jhb@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* BIOS CD device handling for CD's that have been booted off of via no
* emulation booting as defined in the El Torito standard.
*
* Ideas and algorithms from:
*
* - FreeBSD libi386/biosdisk.c
*
*/
#include <stand.h>
#include <sys/param.h>
#include <machine/bootinfo.h>
#include <stdarg.h>
#include <bootstrap.h>
#include <btxv86.h>
#include <edd.h>
#include "libi386.h"
#define BIOSCD_SECSIZE 2048
#define BUFSIZE (1 * BIOSCD_SECSIZE)
#define MAXBCDEV 1
/* Major numbers for devices we frontend for. */
#define ACDMAJOR 117
#define CDMAJOR 15
#ifdef DISK_DEBUG
# define DEBUG(fmt, args...) printf("%s: " fmt "\n" , __func__ , ## args)
#else
# define DEBUG(fmt, args...)
#endif
struct specification_packet {
u_char sp_size;
u_char sp_bootmedia;
u_char sp_drive;
u_char sp_controller;
u_int sp_lba;
u_short sp_devicespec;
u_short sp_buffersegment;
u_short sp_loadsegment;
u_short sp_sectorcount;
u_short sp_cylsec;
u_char sp_head;
};
/*
* List of BIOS devices, translation from disk unit number to
* BIOS unit number.
*/
static struct bcinfo {
int bc_unit; /* BIOS unit number */
struct specification_packet bc_sp;
int bc_open; /* reference counter */
void *bc_bcache; /* buffer cache data */
} bcinfo [MAXBCDEV];
static int nbcinfo = 0;
#define BC(dev) (bcinfo[(dev)->d_unit])
static int bc_read(int unit, daddr_t dblk, int blks, caddr_t dest);
static int bc_init(void);
static int bc_strategy(void *devdata, int flag, daddr_t dblk,
size_t offset, size_t size, char *buf, size_t *rsize);
static int bc_realstrategy(void *devdata, int flag, daddr_t dblk,
size_t offset, size_t size, char *buf, size_t *rsize);
static int bc_open(struct open_file *f, ...);
static int bc_close(struct open_file *f);
static int bc_print(int verbose);
struct devsw bioscd = {
"cd",
DEVT_CD,
bc_init,
bc_strategy,
bc_open,
bc_close,
noioctl,
bc_print,
NULL
};
/*
* Translate between BIOS device numbers and our private unit numbers.
*/
int
bc_bios2unit(int biosdev)
{
int i;
DEBUG("looking for bios device 0x%x", biosdev);
for (i = 0; i < nbcinfo; i++) {
DEBUG("bc unit %d is BIOS device 0x%x", i, bcinfo[i].bc_unit);
if (bcinfo[i].bc_unit == biosdev)
return(i);
}
return(-1);
}
int
bc_unit2bios(int unit)
{
if ((unit >= 0) && (unit < nbcinfo))
return(bcinfo[unit].bc_unit);
return(-1);
}
/*
* We can't quiz, we have to be told what device to use, so this functoin
* doesn't do anything. Instead, the loader calls bc_add() with the BIOS
* device number to add.
*/
static int
bc_init(void)
{
return (0);
}
int
bc_add(int biosdev)
{
if (nbcinfo >= MAXBCDEV)
return (-1);
bcinfo[nbcinfo].bc_unit = biosdev;
v86.ctl = V86_FLAGS;
v86.addr = 0x13;
v86.eax = 0x4b01;
v86.edx = biosdev;
v86.ds = VTOPSEG(&bcinfo[nbcinfo].bc_sp);
v86.esi = VTOPOFF(&bcinfo[nbcinfo].bc_sp);
v86int();
if ((v86.eax & 0xff00) != 0)
return (-1);
printf("BIOS CD is cd%d\n", nbcinfo);
nbcinfo++;
bcache_add_dev(nbcinfo); /* register cd device in bcache */
return(0);
}
/*
* Print information about disks
*/
static int
bc_print(int verbose)
{
char line[80];
int i, ret = 0;
for (i = 0; i < nbcinfo; i++) {
sprintf(line, " cd%d: Device 0x%x\n", i,
bcinfo[i].bc_sp.sp_devicespec);
ret = pager_output(line);
if (ret != 0)
return (ret);
}
return (ret);
}
/*
* Attempt to open the disk described by (dev) for use by (f).
*/
static int
bc_open(struct open_file *f, ...)
{
va_list ap;
struct i386_devdesc *dev;
va_start(ap, f);
dev = va_arg(ap, struct i386_devdesc *);
va_end(ap);
if (dev->d_unit >= nbcinfo) {
DEBUG("attempt to open nonexistent disk");
return(ENXIO);
}
BC(dev).bc_open++;
if (BC(dev).bc_bcache == NULL)
BC(dev).bc_bcache = bcache_allocate();
return(0);
}
static int
bc_close(struct open_file *f)
{
struct i386_devdesc *dev;
dev = (struct i386_devdesc *)f->f_devdata;
BC(dev).bc_open--;
if (BC(dev).bc_open == 0) {
bcache_free(BC(dev).bc_bcache);
BC(dev).bc_bcache = NULL;
}
return(0);
}
static int
bc_strategy(void *devdata, int rw, daddr_t dblk, size_t offset, size_t size,
char *buf, size_t *rsize)
{
struct bcache_devdata bcd;
struct i386_devdesc *dev;
dev = (struct i386_devdesc *)devdata;
bcd.dv_strategy = bc_realstrategy;
bcd.dv_devdata = devdata;
bcd.dv_cache = BC(dev).bc_bcache;
return (bcache_strategy(&bcd, rw, dblk, offset, size, buf, rsize));
}
static int
bc_realstrategy(void *devdata, int rw, daddr_t dblk, size_t offset, size_t size,
char *buf, size_t *rsize)
{
struct i386_devdesc *dev;
int unit;
int blks;
#ifdef BD_SUPPORT_FRAGS
char fragbuf[BIOSCD_SECSIZE];
size_t fragsize;
fragsize = size % BIOSCD_SECSIZE;
#else
if (size % BIOSCD_SECSIZE)
return (EINVAL);
#endif
if (rw != F_READ)
return(EROFS);
dev = (struct i386_devdesc *)devdata;
unit = dev->d_unit;
blks = size / BIOSCD_SECSIZE;
if (dblk % (BIOSCD_SECSIZE / DEV_BSIZE) != 0)
return (EINVAL);
dblk /= (BIOSCD_SECSIZE / DEV_BSIZE);
DEBUG("read %d from %lld to %p", blks, dblk, buf);
if (rsize)
*rsize = 0;
if ((blks = bc_read(unit, dblk, blks, buf)) < 0) {
DEBUG("read error");
return (EIO);
} else {
if (size / BIOSCD_SECSIZE > blks) {
if (rsize)
*rsize = blks * BIOSCD_SECSIZE;
return (0);
}
}
#ifdef BD_SUPPORT_FRAGS
DEBUG("frag read %d from %lld+%d to %p",
fragsize, dblk, blks, buf + (blks * BIOSCD_SECSIZE));
if (fragsize && bc_read(unit, dblk + blks, 1, fragbuf) != 1) {
if (blks) {
if (rsize)
*rsize = blks * BIOSCD_SECSIZE;
return (0);
}
DEBUG("frag read error");
return(EIO);
}
bcopy(fragbuf, buf + (blks * BIOSCD_SECSIZE), fragsize);
#endif
if (rsize)
*rsize = size;
return (0);
}
/* Max number of sectors to bounce-buffer at a time. */
#define CD_BOUNCEBUF 8
/* return negative value for an error, otherwise blocks read */
static int
bc_read(int unit, daddr_t dblk, int blks, caddr_t dest)
{
u_int maxfer, resid, result, retry, x;
caddr_t bbuf, p, xp;
static struct edd_packet packet;
int biosdev;
#ifdef DISK_DEBUG
int error;
#endif
/* Just in case some idiot actually tries to read -1 blocks... */
if (blks < 0)
return (-1);
/* If nothing to do, just return succcess. */
if (blks == 0)
return (0);
/* Decide whether we have to bounce */
if (VTOP(dest) >> 20 != 0) {
/*
* The destination buffer is above first 1MB of
* physical memory so we have to arrange a suitable
* bounce buffer.
*/
x = min(CD_BOUNCEBUF, (unsigned)blks);
bbuf = alloca(x * BIOSCD_SECSIZE);
maxfer = x;
} else {
bbuf = NULL;
maxfer = 0;
}
biosdev = bc_unit2bios(unit);
resid = blks;
p = dest;
while (resid > 0) {
if (bbuf)
xp = bbuf;
else
xp = p;
x = resid;
if (maxfer > 0)
x = min(x, maxfer);
/*
* Loop retrying the operation a couple of times. The BIOS
* may also retry.
*/
for (retry = 0; retry < 3; retry++) {
/* If retrying, reset the drive */
if (retry > 0) {
v86.ctl = V86_FLAGS;
v86.addr = 0x13;
v86.eax = 0;
v86.edx = biosdev;
v86int();
}
packet.len = sizeof(struct edd_packet);
packet.count = x;
packet.off = VTOPOFF(xp);
packet.seg = VTOPSEG(xp);
packet.lba = dblk;
v86.ctl = V86_FLAGS;
v86.addr = 0x13;
v86.eax = 0x4200;
v86.edx = biosdev;
v86.ds = VTOPSEG(&packet);
v86.esi = VTOPOFF(&packet);
v86int();
result = V86_CY(v86.efl);
if (result == 0)
break;
/* fall back to 1 sector read */
x = 1;
}
#ifdef DISK_DEBUG
error = (v86.eax >> 8) & 0xff;
#endif
DEBUG("%d sectors from %lld to %p (0x%x) %s", x, dblk, p,
VTOP(p), result ? "failed" : "ok");
DEBUG("unit %d status 0x%x", unit, error);
/* still an error? break off */
if (result != 0)
break;
if (bbuf != NULL)
bcopy(bbuf, p, x * BIOSCD_SECSIZE);
p += (x * BIOSCD_SECSIZE);
dblk += x;
resid -= x;
}
/* hexdump(dest, (blks * BIOSCD_SECSIZE)); */
if (blks - resid == 0)
return (-1); /* read failed */
return (blks - resid);
}
/*
* Return a suitable dev_t value for (dev).
*/
int
bc_getdev(struct i386_devdesc *dev)
{
int biosdev, unit;
int major;
int rootdev;
unit = dev->d_unit;
biosdev = bc_unit2bios(unit);
DEBUG("unit %d BIOS device %d", unit, biosdev);
if (biosdev == -1) /* not a BIOS device */
return(-1);
/*
* XXX: Need to examine device spec here to figure out if SCSI or
* ATAPI. No idea on how to figure out device number. All we can
* really pass to the kernel is what bus and device on which bus we
* were booted from, which dev_t isn't well suited to since those
* number don't match to unit numbers very well. We may just need
* to engage in a hack where we pass -C to the boot args if we are
* the boot device.
*/
major = ACDMAJOR;
unit = 0; /* XXX */
/* XXX: Assume partition 'a'. */
rootdev = MAKEBOOTDEV(major, 0, unit, 0);
DEBUG("dev is 0x%x\n", rootdev);
return(rootdev);
}