77d81a4dab
lsdev command does walk over devsw list, prints list element name and will use dv_print() callback to print the device list. Unfortunately this approach will add unneeded noise when there are no particular devices detected. To remove "empty" device section headers, the dv_print() callback should print the header instead. In addition, fixed dv_print callback for md module. Reviewed by: imp Approved by: imp (mentor) Differential Revision: https://reviews.freebsd.org/D8551
455 lines
10 KiB
C
455 lines
10 KiB
C
/*-
|
|
* 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;
|
|
|
|
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++) {
|
|
snprintf(line, sizeof(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 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);
|
|
}
|