Changeset View
Changeset View
Standalone View
Standalone View
head/sys/boot/pc98/libpc98/bioscd.c
Property | Old Value | New Value |
---|---|---|
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
/*- | |||||
* 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 "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 size, char *buf, size_t *rsize); | |||||
static int bc_realstrategy(void *devdata, int flag, daddr_t dblk, | |||||
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; | |||||
/* SCSI CD-ROM only */ | |||||
if ((biosdev & 0xf0) != 0xa0) | |||||
return (-1); | |||||
if ((((uint32_t *)PTOV(0xA1460))[biosdev & 0x0f] & 0x1f) != 5) | |||||
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; | |||||
if (nbcinfo == 0) | |||||
return (0); | |||||
printf("%s devices:", bioscd.dv_name); | |||||
if ((ret = pager_output("\n")) != 0) | |||||
return (ret); | |||||
for (i = 0; i < nbcinfo; i++) { | |||||
sprintf(line, " cd%d: Device 0x%x\n", i, | |||||
bcinfo[i].bc_sp.sp_devicespec); | |||||
if ((ret = pager_output(line)) != 0) | |||||
break; | |||||
} | |||||
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 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, size, buf, rsize)); | |||||
} | |||||
static int | |||||
bc_realstrategy(void *devdata, int rw, daddr_t dblk, 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)) { | |||||
DEBUG("read error"); | |||||
return (EIO); | |||||
} | |||||
#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)) { | |||||
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 | |||||
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; | |||||
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 = 0x1b; | |||||
v86.eax = 0x0300 | biosdev; | |||||
v86int(); | |||||
} | |||||
v86.ctl = V86_FLAGS; | |||||
v86.addr = 0x1b; | |||||
v86.eax = 0x0600 | (biosdev & 0x7f); | |||||
v86.ebx = x * BIOSCD_SECSIZE; | |||||
v86.ecx = dblk & 0xffff; | |||||
v86.edx = (dblk >> 16) & 0xffff; | |||||
v86.ebp = VTOPOFF(xp); | |||||
v86.es = VTOPSEG(xp); | |||||
v86int(); | |||||
result = V86_CY(v86.efl); | |||||
if (result == 0) | |||||
break; | |||||
} | |||||
#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); | |||||
if (bbuf != NULL) | |||||
bcopy(bbuf, p, x * BIOSCD_SECSIZE); | |||||
p += (x * BIOSCD_SECSIZE); | |||||
dblk += x; | |||||
resid -= x; | |||||
} | |||||
/* hexdump(dest, (blks * BIOSCD_SECSIZE)); */ | |||||
return(0); | |||||
} | |||||
/* | |||||
* Return a suitable dev_t value for (dev). | |||||
*/ | |||||
int | |||||
bc_getdev(struct i386_devdesc *dev) | |||||
{ | |||||
int biosdev, unit, device; | |||||
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); | |||||
device = biosdev & 0xf0; | |||||
if (device == 0x80) | |||||
major = ACDMAJOR; | |||||
else if (device == 0xa0) | |||||
major = CDMAJOR; | |||||
else | |||||
return (-1); | |||||
unit = 0; /* XXX */ | |||||
/* XXX: Assume partition 'a'. */ | |||||
rootdev = MAKEBOOTDEV(major, 0, unit, 0); | |||||
DEBUG("dev is 0x%x\n", rootdev); | |||||
return(rootdev); | |||||
} |