freebsd-skq/stand/i386/libi386/biosdisk.c
Toomas Soome b0af1e20e6 loader: bc_add can not use any other probes than ah=0x4b
CD boot is broken for some systems since bioscd and biosdisk merge. The issue is that we can not use anything else than int 13 ah=0x4b to query cd information.

The patch does restore the same probe as was originally used in bioscd.c. Additionally extra buffer padding is used to avoid memory corruption caused by some systems.

PR:		234031
Reported by:	ultramage and others
MFC after:	1 day
2020-02-03 11:33:33 +00:00

1382 lines
32 KiB
C

/*-
* Copyright (c) 1998 Michael Smith <msmith@freebsd.org>
* Copyright (c) 2012 Andrey V. Elsukov <ae@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 disk device handling.
*
* Ideas and algorithms from:
*
* - NetBSD libi386/biosdisk.c
* - FreeBSD biosboot/disk.c
*
*/
#include <sys/disk.h>
#include <sys/limits.h>
#include <sys/queue.h>
#include <stand.h>
#include <machine/bootinfo.h>
#include <stdarg.h>
#include <stdbool.h>
#include <bootstrap.h>
#include <btxv86.h>
#include <edd.h>
#include "disk.h"
#include "libi386.h"
#define BIOS_NUMDRIVES 0x475
#define BIOSDISK_SECSIZE 512
#define BUFSIZE (1 * BIOSDISK_SECSIZE)
#define DT_ATAPI 0x10 /* disk type for ATAPI floppies */
#define WDMAJOR 0 /* major numbers for devices we frontend for */
#define WFDMAJOR 1
#define FDMAJOR 2
#define DAMAJOR 4
#define ACDMAJOR 117
#define CDMAJOR 15
/*
* INT13 commands
*/
#define CMD_RESET 0x0000
#define CMD_READ_CHS 0x0200
#define CMD_WRITE_CHS 0x0300
#define CMD_READ_PARAM 0x0800
#define CMD_DRIVE_TYPE 0x1500
#define CMD_CHECK_EDD 0x4100
#define CMD_READ_LBA 0x4200
#define CMD_WRITE_LBA 0x4300
#define CMD_EXT_PARAM 0x4800
#define CMD_CD_GET_STATUS 0x4b01
#define DISK_BIOS 0x13
#ifdef DISK_DEBUG
#define DPRINTF(fmt, args...) printf("%s: " fmt "\n", __func__, ## args)
#else
#define DPRINTF(fmt, args...) ((void)0)
#endif
struct specification_packet {
uint8_t sp_size;
uint8_t sp_bootmedia;
uint8_t sp_drive;
uint8_t sp_controller;
uint32_t sp_lba;
uint16_t sp_devicespec;
uint16_t sp_buffersegment;
uint16_t sp_loadsegment;
uint16_t sp_sectorcount;
uint16_t sp_cylsec;
uint8_t sp_head;
uint8_t sp_dummy[16]; /* Avoid memory corruption */
};
/*
* List of BIOS devices, translation from disk unit number to
* BIOS unit number.
*/
typedef struct bdinfo
{
STAILQ_ENTRY(bdinfo) bd_link; /* link in device list */
int bd_unit; /* BIOS unit number */
int bd_cyl; /* BIOS geometry */
int bd_hds;
int bd_sec;
int bd_flags;
#define BD_MODEINT13 0x0000
#define BD_MODEEDD1 0x0001
#define BD_MODEEDD3 0x0002
#define BD_MODEEDD (BD_MODEEDD1 | BD_MODEEDD3)
#define BD_MODEMASK 0x0003
#define BD_FLOPPY 0x0004
#define BD_CDROM 0x0008
#define BD_NO_MEDIA 0x0010
int bd_type; /* BIOS 'drive type' (floppy only) */
uint16_t bd_sectorsize; /* Sector size */
uint64_t bd_sectors; /* Disk size */
int bd_open; /* reference counter */
void *bd_bcache; /* buffer cache data */
} bdinfo_t;
#define BD_RD 0
#define BD_WR 1
typedef STAILQ_HEAD(bdinfo_list, bdinfo) bdinfo_list_t;
static bdinfo_list_t fdinfo = STAILQ_HEAD_INITIALIZER(fdinfo);
static bdinfo_list_t cdinfo = STAILQ_HEAD_INITIALIZER(cdinfo);
static bdinfo_list_t hdinfo = STAILQ_HEAD_INITIALIZER(hdinfo);
static void bd_io_workaround(bdinfo_t *);
static int bd_io(struct disk_devdesc *, bdinfo_t *, daddr_t, int, caddr_t, int);
static bool bd_int13probe(bdinfo_t *);
static int bd_init(void);
static int cd_init(void);
static int fd_init(void);
static int bd_strategy(void *devdata, int flag, daddr_t dblk, size_t size,
char *buf, size_t *rsize);
static int bd_realstrategy(void *devdata, int flag, daddr_t dblk, size_t size,
char *buf, size_t *rsize);
static int bd_open(struct open_file *f, ...);
static int bd_close(struct open_file *f);
static int bd_ioctl(struct open_file *f, u_long cmd, void *data);
static int bd_print(int verbose);
static int cd_print(int verbose);
static int fd_print(int verbose);
static void bd_reset_disk(int);
static int bd_get_diskinfo_std(struct bdinfo *);
struct devsw biosfd = {
.dv_name = "fd",
.dv_type = DEVT_FD,
.dv_init = fd_init,
.dv_strategy = bd_strategy,
.dv_open = bd_open,
.dv_close = bd_close,
.dv_ioctl = bd_ioctl,
.dv_print = fd_print,
.dv_cleanup = NULL
};
struct devsw bioscd = {
.dv_name = "cd",
.dv_type = DEVT_CD,
.dv_init = cd_init,
.dv_strategy = bd_strategy,
.dv_open = bd_open,
.dv_close = bd_close,
.dv_ioctl = bd_ioctl,
.dv_print = cd_print,
.dv_cleanup = NULL
};
struct devsw bioshd = {
.dv_name = "disk",
.dv_type = DEVT_DISK,
.dv_init = bd_init,
.dv_strategy = bd_strategy,
.dv_open = bd_open,
.dv_close = bd_close,
.dv_ioctl = bd_ioctl,
.dv_print = bd_print,
.dv_cleanup = NULL
};
static bdinfo_list_t *
bd_get_bdinfo_list(struct devsw *dev)
{
if (dev->dv_type == DEVT_DISK)
return (&hdinfo);
if (dev->dv_type == DEVT_CD)
return (&cdinfo);
if (dev->dv_type == DEVT_FD)
return (&fdinfo);
return (NULL);
}
/* XXX this gets called way way too often, investigate */
static bdinfo_t *
bd_get_bdinfo(struct devdesc *dev)
{
bdinfo_list_t *bdi;
bdinfo_t *bd = NULL;
int unit;
bdi = bd_get_bdinfo_list(dev->d_dev);
if (bdi == NULL)
return (bd);
unit = 0;
STAILQ_FOREACH(bd, bdi, bd_link) {
if (unit == dev->d_unit)
return (bd);
unit++;
}
return (bd);
}
/*
* Translate between BIOS device numbers and our private unit numbers.
*/
int
bd_bios2unit(int biosdev)
{
bdinfo_list_t *bdi[] = { &fdinfo, &cdinfo, &hdinfo, NULL };
bdinfo_t *bd;
int i, unit;
DPRINTF("looking for bios device 0x%x", biosdev);
for (i = 0; bdi[i] != NULL; i++) {
unit = 0;
STAILQ_FOREACH(bd, bdi[i], bd_link) {
if (bd->bd_unit == biosdev) {
DPRINTF("bd unit %d is BIOS device 0x%x", unit,
bd->bd_unit);
return (unit);
}
unit++;
}
}
return (-1);
}
int
bd_unit2bios(struct i386_devdesc *dev)
{
bdinfo_list_t *bdi;
bdinfo_t *bd;
int unit;
bdi = bd_get_bdinfo_list(dev->dd.d_dev);
if (bdi == NULL)
return (-1);
unit = 0;
STAILQ_FOREACH(bd, bdi, bd_link) {
if (unit == dev->dd.d_unit)
return (bd->bd_unit);
unit++;
}
return (-1);
}
/*
* Use INT13 AH=15 - Read Drive Type.
*/
static int
fd_count(void)
{
int drive;
for (drive = 0; drive < MAXBDDEV; drive++) {
bd_reset_disk(drive);
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_DRIVE_TYPE;
v86.edx = drive;
v86int();
if (V86_CY(v86.efl))
break;
if ((v86.eax & 0x300) == 0)
break;
}
return (drive);
}
/*
* Quiz the BIOS for disk devices, save a little info about them.
*/
static int
fd_init(void)
{
int unit, numfd;
bdinfo_t *bd;
numfd = fd_count();
for (unit = 0; unit < numfd; unit++) {
if ((bd = calloc(1, sizeof(*bd))) == NULL)
break;
bd->bd_sectorsize = BIOSDISK_SECSIZE;
bd->bd_flags = BD_FLOPPY;
bd->bd_unit = unit;
/* Use std diskinfo for floppy drive */
if (bd_get_diskinfo_std(bd) != 0) {
free(bd);
break;
}
if (bd->bd_sectors == 0)
bd->bd_flags |= BD_NO_MEDIA;
printf("BIOS drive %c: is %s%d\n", ('A' + unit),
biosfd.dv_name, unit);
STAILQ_INSERT_TAIL(&fdinfo, bd, bd_link);
}
bcache_add_dev(unit);
return (0);
}
static int
bd_init(void)
{
int base, unit;
bdinfo_t *bd;
base = 0x80;
for (unit = 0; unit < *(unsigned char *)PTOV(BIOS_NUMDRIVES); unit++) {
/*
* Check the BIOS equipment list for number of fixed disks.
*/
if ((bd = calloc(1, sizeof(*bd))) == NULL)
break;
bd->bd_unit = base + unit;
if (!bd_int13probe(bd)) {
free(bd);
break;
}
printf("BIOS drive %c: is %s%d\n", ('C' + unit),
bioshd.dv_name, unit);
STAILQ_INSERT_TAIL(&hdinfo, bd, bd_link);
}
bcache_add_dev(unit);
return (0);
}
/*
* We can't quiz, we have to be told what device to use, so this function
* doesn't do anything. Instead, the loader calls bc_add() with the BIOS
* device number to add.
*/
static int
cd_init(void)
{
return (0);
}
/*
* Information from bootable CD-ROM.
*/
static int
bd_get_diskinfo_cd(struct bdinfo *bd)
{
struct specification_packet bc_sp;
int ret = -1;
(void) memset(&bc_sp, 0, sizeof (bc_sp));
/* Set sp_size as per specification. */
bc_sp.sp_size = sizeof (bc_sp) - sizeof (bc_sp.sp_dummy);
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_CD_GET_STATUS;
v86.edx = bd->bd_unit;
v86.ds = VTOPSEG(&bc_sp);
v86.esi = VTOPOFF(&bc_sp);
v86int();
if ((v86.eax & 0xff00) == 0 &&
bc_sp.sp_drive == bd->bd_unit) {
bd->bd_cyl = ((bc_sp.sp_cylsec & 0xc0) << 2) +
((bc_sp.sp_cylsec & 0xff00) >> 8) + 1;
bd->bd_sec = bc_sp.sp_cylsec & 0x3f;
bd->bd_hds = bc_sp.sp_head + 1;
bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec;
if (bc_sp.sp_bootmedia & 0x0F) {
/* Floppy or hard-disk emulation */
bd->bd_sectorsize = BIOSDISK_SECSIZE;
return (-1);
} else {
bd->bd_sectorsize = 2048;
bd->bd_flags = BD_MODEEDD | BD_CDROM;
ret = 0;
}
}
/*
* If this is the boot_drive, default to non-emulation bootable CD-ROM.
*/
if (ret != 0 && bd->bd_unit >= 0x88) {
bd->bd_cyl = 0;
bd->bd_hds = 1;
bd->bd_sec = 15;
bd->bd_sectorsize = 2048;
bd->bd_flags = BD_MODEEDD | BD_CDROM;
bd->bd_sectors = 0;
ret = 0;
}
/*
* Note we can not use bd_get_diskinfo_ext() nor bd_get_diskinfo_std()
* here - some systems do get hung with those.
*/
/*
* Still no size? use 7.961GB. The size does not really matter
* as long as it is reasonably large to make our reads to pass
* the sector count check.
*/
if (bd->bd_sectors == 0)
bd->bd_sectors = 4173824;
return (ret);
}
int
bc_add(int biosdev)
{
bdinfo_t *bd;
int nbcinfo = 0;
if (!STAILQ_EMPTY(&cdinfo))
return (-1);
if ((bd = calloc(1, sizeof(*bd))) == NULL)
return (-1);
bd->bd_unit = biosdev;
if (bd_get_diskinfo_cd(bd) < 0) {
free(bd);
return (-1);
}
STAILQ_INSERT_TAIL(&cdinfo, bd, bd_link);
printf("BIOS CD is cd%d\n", nbcinfo);
nbcinfo++;
bcache_add_dev(nbcinfo); /* register cd device in bcache */
return(0);
}
/*
* Return EDD version or 0 if EDD is not supported on this drive.
*/
static int
bd_check_extensions(int unit)
{
/* do not use ext calls for floppy devices */
if (unit < 0x80)
return (0);
/* Determine if we can use EDD with this device. */
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_CHECK_EDD;
v86.edx = unit;
v86.ebx = EDD_QUERY_MAGIC;
v86int();
if (V86_CY(v86.efl) || /* carry set */
(v86.ebx & 0xffff) != EDD_INSTALLED) /* signature */
return (0);
/* extended disk access functions (AH=42h-44h,47h,48h) supported */
if ((v86.ecx & EDD_INTERFACE_FIXED_DISK) == 0)
return (0);
return ((v86.eax >> 8) & 0xff);
}
static void
bd_reset_disk(int unit)
{
/* reset disk */
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_RESET;
v86.edx = unit;
v86int();
}
/*
* Read CHS info. Return 0 on success, error otherwise.
*/
static int
bd_get_diskinfo_std(struct bdinfo *bd)
{
bzero(&v86, sizeof(v86));
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_READ_PARAM;
v86.edx = bd->bd_unit;
v86int();
if (V86_CY(v86.efl) && ((v86.eax & 0xff00) != 0))
return ((v86.eax & 0xff00) >> 8);
/* return custom error on absurd sector number */
if ((v86.ecx & 0x3f) == 0)
return (0x60);
bd->bd_cyl = ((v86.ecx & 0xc0) << 2) + ((v86.ecx & 0xff00) >> 8) + 1;
/* Convert max head # -> # of heads */
bd->bd_hds = ((v86.edx & 0xff00) >> 8) + 1;
bd->bd_sec = v86.ecx & 0x3f;
bd->bd_type = v86.ebx;
bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec;
return (0);
}
/*
* Read EDD info. Return 0 on success, error otherwise.
*
* Avoid stack corruption on some systems by adding extra bytes to
* params block.
*/
static int
bd_get_diskinfo_ext(struct bdinfo *bd)
{
struct disk_params {
struct edd_params head;
struct edd_device_path_v3 device_path;
uint8_t dummy[16];
} __packed dparams;
struct edd_params *params;
uint64_t total;
params = &dparams.head;
/* Get disk params */
bzero(&dparams, sizeof(dparams));
params->len = sizeof(struct edd_params_v3);
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_EXT_PARAM;
v86.edx = bd->bd_unit;
v86.ds = VTOPSEG(&dparams);
v86.esi = VTOPOFF(&dparams);
v86int();
if (V86_CY(v86.efl) && ((v86.eax & 0xff00) != 0))
return ((v86.eax & 0xff00) >> 8);
/*
* Sector size must be a multiple of 512 bytes.
* An alternate test would be to check power of 2,
* powerof2(params.sector_size).
* 16K is largest read buffer we can use at this time.
*/
if (params->sector_size >= 512 &&
params->sector_size <= 16384 &&
(params->sector_size % BIOSDISK_SECSIZE) == 0)
bd->bd_sectorsize = params->sector_size;
bd->bd_cyl = params->cylinders;
bd->bd_hds = params->heads;
bd->bd_sec = params->sectors_per_track;
if (params->sectors != 0) {
total = params->sectors;
} else {
total = (uint64_t)params->cylinders *
params->heads * params->sectors_per_track;
}
bd->bd_sectors = total;
return (0);
}
/*
* Try to detect a device supported by the legacy int13 BIOS
*/
static bool
bd_int13probe(bdinfo_t *bd)
{
int edd, ret;
bd->bd_flags &= ~BD_NO_MEDIA;
if ((bd->bd_flags & BD_CDROM) != 0) {
return (bd_get_diskinfo_cd(bd) == 0);
}
edd = bd_check_extensions(bd->bd_unit);
if (edd == 0)
bd->bd_flags |= BD_MODEINT13;
else if (edd < 0x30)
bd->bd_flags |= BD_MODEEDD1;
else
bd->bd_flags |= BD_MODEEDD3;
/* Default sector size */
if (bd->bd_sectorsize == 0)
bd->bd_sectorsize = BIOSDISK_SECSIZE;
/*
* Test if the floppy device is present, so we can avoid receiving
* bogus information from bd_get_diskinfo_std().
*/
if (bd->bd_unit < 0x80) {
/* reset disk */
bd_reset_disk(bd->bd_unit);
/* Get disk type */
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_DRIVE_TYPE;
v86.edx = bd->bd_unit;
v86int();
if (V86_CY(v86.efl) || (v86.eax & 0x300) == 0)
return (false);
}
ret = 1;
if (edd != 0)
ret = bd_get_diskinfo_ext(bd);
if (ret != 0 || bd->bd_sectors == 0)
ret = bd_get_diskinfo_std(bd);
if (ret != 0 && bd->bd_unit < 0x80) {
/* Set defaults for 1.44 floppy */
bd->bd_cyl = 80;
bd->bd_hds = 2;
bd->bd_sec = 18;
bd->bd_sectors = 2880;
/* Since we are there, there most likely is no media */
bd->bd_flags |= BD_NO_MEDIA;
ret = 0;
}
if (ret != 0) {
if (bd->bd_sectors != 0 && edd != 0) {
bd->bd_sec = 63;
bd->bd_hds = 255;
bd->bd_cyl =
(bd->bd_sectors + bd->bd_sec * bd->bd_hds - 1) /
bd->bd_sec * bd->bd_hds;
} else {
const char *dv_name;
if ((bd->bd_flags & BD_FLOPPY) != 0)
dv_name = biosfd.dv_name;
else
dv_name = bioshd.dv_name;
printf("Can not get information about %s unit %#x\n",
dv_name, bd->bd_unit);
return (false);
}
}
if (bd->bd_sec == 0)
bd->bd_sec = 63;
if (bd->bd_hds == 0)
bd->bd_hds = 255;
if (bd->bd_sectors == 0)
bd->bd_sectors = (uint64_t)bd->bd_cyl * bd->bd_hds * bd->bd_sec;
DPRINTF("unit 0x%x geometry %d/%d/%d\n", bd->bd_unit, bd->bd_cyl,
bd->bd_hds, bd->bd_sec);
return (true);
}
static int
bd_count(bdinfo_list_t *bdi)
{
bdinfo_t *bd;
int i;
i = 0;
STAILQ_FOREACH(bd, bdi, bd_link)
i++;
return (i);
}
/*
* Print information about disks
*/
static int
bd_print_common(struct devsw *dev, bdinfo_list_t *bdi, int verbose)
{
char line[80];
struct disk_devdesc devd;
bdinfo_t *bd;
int i, ret = 0;
char drive;
if (STAILQ_EMPTY(bdi))
return (0);
printf("%s devices:", dev->dv_name);
if ((ret = pager_output("\n")) != 0)
return (ret);
i = -1;
STAILQ_FOREACH(bd, bdi, bd_link) {
i++;
switch (dev->dv_type) {
case DEVT_FD:
drive = 'A';
break;
case DEVT_CD:
drive = 'C' + bd_count(&hdinfo);
break;
default:
drive = 'C';
break;
}
snprintf(line, sizeof(line),
" %s%d: BIOS drive %c (%s%ju X %u):\n",
dev->dv_name, i, drive + i,
(bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA ?
"no media, " : "",
(uintmax_t)bd->bd_sectors,
bd->bd_sectorsize);
if ((ret = pager_output(line)) != 0)
break;
if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA)
continue;
if (dev->dv_type != DEVT_DISK)
continue;
devd.dd.d_dev = dev;
devd.dd.d_unit = i;
devd.d_slice = D_SLICENONE;
devd.d_partition = D_PARTNONE;
if (disk_open(&devd,
bd->bd_sectorsize * bd->bd_sectors,
bd->bd_sectorsize) == 0) {
snprintf(line, sizeof(line), " %s%d",
dev->dv_name, i);
ret = disk_print(&devd, line, verbose);
disk_close(&devd);
if (ret != 0)
break;
}
}
return (ret);
}
static int
fd_print(int verbose)
{
return (bd_print_common(&biosfd, &fdinfo, verbose));
}
static int
bd_print(int verbose)
{
return (bd_print_common(&bioshd, &hdinfo, verbose));
}
static int
cd_print(int verbose)
{
return (bd_print_common(&bioscd, &cdinfo, verbose));
}
/*
* Read disk size from partition.
* This is needed to work around buggy BIOS systems returning
* wrong (truncated) disk media size.
* During bd_probe() we tested if the multiplication of bd_sectors
* would overflow so it should be safe to perform here.
*/
static uint64_t
bd_disk_get_sectors(struct disk_devdesc *dev)
{
bdinfo_t *bd;
struct disk_devdesc disk;
uint64_t size;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (0);
disk.dd.d_dev = dev->dd.d_dev;
disk.dd.d_unit = dev->dd.d_unit;
disk.d_slice = D_SLICENONE;
disk.d_partition = D_PARTNONE;
disk.d_offset = 0;
size = bd->bd_sectors * bd->bd_sectorsize;
if (disk_open(&disk, size, bd->bd_sectorsize) == 0) {
(void) disk_ioctl(&disk, DIOCGMEDIASIZE, &size);
disk_close(&disk);
}
return (size / bd->bd_sectorsize);
}
/*
* Attempt to open the disk described by (dev) for use by (f).
*
* Note that the philosophy here is "give them exactly what
* they ask for". This is necessary because being too "smart"
* about what the user might want leads to complications.
* (eg. given no slice or partition value, with a disk that is
* sliced - are they after the first BSD slice, or the DOS
* slice before it?)
*/
static int
bd_open(struct open_file *f, ...)
{
bdinfo_t *bd;
struct disk_devdesc *dev;
va_list ap;
int rc;
va_start(ap, f);
dev = va_arg(ap, struct disk_devdesc *);
va_end(ap);
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (EIO);
if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA) {
if (!bd_int13probe(bd))
return (EIO);
if ((bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA)
return (EIO);
}
if (bd->bd_bcache == NULL)
bd->bd_bcache = bcache_allocate();
if (bd->bd_open == 0)
bd->bd_sectors = bd_disk_get_sectors(dev);
bd->bd_open++;
rc = 0;
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
rc = disk_open(dev, bd->bd_sectors * bd->bd_sectorsize,
bd->bd_sectorsize);
if (rc != 0) {
bd->bd_open--;
if (bd->bd_open == 0) {
bcache_free(bd->bd_bcache);
bd->bd_bcache = NULL;
}
}
}
return (rc);
}
static int
bd_close(struct open_file *f)
{
struct disk_devdesc *dev;
bdinfo_t *bd;
int rc = 0;
dev = (struct disk_devdesc *)f->f_devdata;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (EIO);
bd->bd_open--;
if (bd->bd_open == 0) {
bcache_free(bd->bd_bcache);
bd->bd_bcache = NULL;
}
if (dev->dd.d_dev->dv_type == DEVT_DISK)
rc = disk_close(dev);
return (rc);
}
static int
bd_ioctl(struct open_file *f, u_long cmd, void *data)
{
bdinfo_t *bd;
struct disk_devdesc *dev;
int rc;
dev = (struct disk_devdesc *)f->f_devdata;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (EIO);
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
rc = disk_ioctl(dev, cmd, data);
if (rc != ENOTTY)
return (rc);
}
switch (cmd) {
case DIOCGSECTORSIZE:
*(uint32_t *)data = bd->bd_sectorsize;
break;
case DIOCGMEDIASIZE:
*(uint64_t *)data = bd->bd_sectors * bd->bd_sectorsize;
break;
default:
return (ENOTTY);
}
return (0);
}
static int
bd_strategy(void *devdata, int rw, daddr_t dblk, size_t size,
char *buf, size_t *rsize)
{
bdinfo_t *bd;
struct bcache_devdata bcd;
struct disk_devdesc *dev;
daddr_t offset;
dev = (struct disk_devdesc *)devdata;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (EINVAL);
bcd.dv_strategy = bd_realstrategy;
bcd.dv_devdata = devdata;
bcd.dv_cache = bd->bd_bcache;
offset = 0;
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
offset = dev->d_offset * bd->bd_sectorsize;
offset /= BIOSDISK_SECSIZE;
}
return (bcache_strategy(&bcd, rw, dblk + offset, size,
buf, rsize));
}
static int
bd_realstrategy(void *devdata, int rw, daddr_t dblk, size_t size,
char *buf, size_t *rsize)
{
struct disk_devdesc *dev = (struct disk_devdesc *)devdata;
bdinfo_t *bd;
uint64_t disk_blocks, offset, d_offset;
size_t blks, blkoff, bsize, bio_size, rest;
caddr_t bbuf = NULL;
int rc;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL || (bd->bd_flags & BD_NO_MEDIA) == BD_NO_MEDIA)
return (EIO);
/*
* First make sure the IO size is a multiple of 512 bytes. While we do
* process partial reads below, the strategy mechanism is built
* assuming IO is a multiple of 512B blocks. If the request is not
* a multiple of 512B blocks, it has to be some sort of bug.
*/
if (size == 0 || (size % BIOSDISK_SECSIZE) != 0) {
printf("bd_strategy: %d bytes I/O not multiple of %d\n",
size, BIOSDISK_SECSIZE);
return (EIO);
}
DPRINTF("open_disk %p", dev);
offset = dblk * BIOSDISK_SECSIZE;
dblk = offset / bd->bd_sectorsize;
blkoff = offset % bd->bd_sectorsize;
/*
* Check the value of the size argument. We do have quite small
* heap (64MB), but we do not know good upper limit, so we check against
* INT_MAX here. This will also protect us against possible overflows
* while translating block count to bytes.
*/
if (size > INT_MAX) {
DPRINTF("too large I/O: %zu bytes", size);
return (EIO);
}
blks = size / bd->bd_sectorsize;
if (blks == 0 || (size % bd->bd_sectorsize) != 0)
blks++;
if (dblk > dblk + blks)
return (EIO);
if (rsize)
*rsize = 0;
/*
* Get disk blocks, this value is either for whole disk or for
* partition.
*/
d_offset = 0;
disk_blocks = 0;
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
if (disk_ioctl(dev, DIOCGMEDIASIZE, &disk_blocks) == 0) {
/* DIOCGMEDIASIZE does return bytes. */
disk_blocks /= bd->bd_sectorsize;
}
d_offset = dev->d_offset;
}
if (disk_blocks == 0)
disk_blocks = bd->bd_sectors - d_offset;
/* Validate source block address. */
if (dblk < d_offset || dblk >= d_offset + disk_blocks)
return (EIO);
/*
* Truncate if we are crossing disk or partition end.
*/
if (dblk + blks >= d_offset + disk_blocks) {
blks = d_offset + disk_blocks - dblk;
size = blks * bd->bd_sectorsize;
DPRINTF("short I/O %d", blks);
}
bio_size = min(BIO_BUFFER_SIZE, size);
while (bio_size > bd->bd_sectorsize) {
bbuf = bio_alloc(bio_size);
if (bbuf != NULL)
break;
bio_size -= bd->bd_sectorsize;
}
if (bbuf == NULL) {
bio_size = V86_IO_BUFFER_SIZE;
if (bio_size / bd->bd_sectorsize == 0)
panic("BUG: Real mode buffer is too small");
/* Use alternate 4k buffer */
bbuf = PTOV(V86_IO_BUFFER);
}
rest = size;
rc = 0;
while (blks > 0) {
int x = min(blks, bio_size / bd->bd_sectorsize);
switch (rw & F_MASK) {
case F_READ:
DPRINTF("read %d from %lld to %p", x, dblk, buf);
bsize = bd->bd_sectorsize * x - blkoff;
if (rest < bsize)
bsize = rest;
if ((rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD)) != 0) {
rc = EIO;
goto error;
}
bcopy(bbuf + blkoff, buf, bsize);
break;
case F_WRITE :
DPRINTF("write %d from %lld to %p", x, dblk, buf);
if (blkoff != 0) {
/*
* We got offset to sector, read 1 sector to
* bbuf.
*/
x = 1;
bsize = bd->bd_sectorsize - blkoff;
bsize = min(bsize, rest);
rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD);
} else if (rest < bd->bd_sectorsize) {
/*
* The remaining block is not full
* sector. Read 1 sector to bbuf.
*/
x = 1;
bsize = rest;
rc = bd_io(dev, bd, dblk, x, bbuf, BD_RD);
} else {
/* We can write full sector(s). */
bsize = bd->bd_sectorsize * x;
}
/*
* Put your Data In, Put your Data out,
* Put your Data In, and shake it all about
*/
bcopy(buf, bbuf + blkoff, bsize);
if ((rc = bd_io(dev, bd, dblk, x, bbuf, BD_WR)) != 0) {
rc = EIO;
goto error;
}
break;
default:
/* DO NOTHING */
rc = EROFS;
goto error;
}
blkoff = 0;
buf += bsize;
rest -= bsize;
blks -= x;
dblk += x;
}
if (rsize != NULL)
*rsize = size;
error:
if (bbuf != PTOV(V86_IO_BUFFER))
bio_free(bbuf, bio_size);
return (rc);
}
static int
bd_edd_io(bdinfo_t *bd, daddr_t dblk, int blks, caddr_t dest,
int dowrite)
{
static struct edd_packet packet;
packet.len = sizeof(struct edd_packet);
packet.count = blks;
packet.off = VTOPOFF(dest);
packet.seg = VTOPSEG(dest);
packet.lba = dblk;
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
if (dowrite == BD_WR)
v86.eax = CMD_WRITE_LBA; /* maybe Write with verify 0x4302? */
else
v86.eax = CMD_READ_LBA;
v86.edx = bd->bd_unit;
v86.ds = VTOPSEG(&packet);
v86.esi = VTOPOFF(&packet);
v86int();
if (V86_CY(v86.efl))
return (v86.eax >> 8);
return (0);
}
static int
bd_chs_io(bdinfo_t *bd, daddr_t dblk, int blks, caddr_t dest,
int dowrite)
{
uint32_t x, bpc, cyl, hd, sec;
bpc = bd->bd_sec * bd->bd_hds; /* blocks per cylinder */
x = dblk;
cyl = x / bpc; /* block # / blocks per cylinder */
x %= bpc; /* block offset into cylinder */
hd = x / bd->bd_sec; /* offset / blocks per track */
sec = x % bd->bd_sec; /* offset into track */
/* correct sector number for 1-based BIOS numbering */
sec++;
if (cyl > 1023) {
/* CHS doesn't support cylinders > 1023. */
return (1);
}
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
if (dowrite == BD_WR)
v86.eax = CMD_WRITE_CHS | blks;
else
v86.eax = CMD_READ_CHS | blks;
v86.ecx = ((cyl & 0xff) << 8) | ((cyl & 0x300) >> 2) | sec;
v86.edx = (hd << 8) | bd->bd_unit;
v86.es = VTOPSEG(dest);
v86.ebx = VTOPOFF(dest);
v86int();
if (V86_CY(v86.efl))
return (v86.eax >> 8);
return (0);
}
static void
bd_io_workaround(bdinfo_t *bd)
{
uint8_t buf[8 * 1024];
bd_edd_io(bd, 0xffffffff, 1, (caddr_t)buf, BD_RD);
}
static int
bd_io(struct disk_devdesc *dev, bdinfo_t *bd, daddr_t dblk, int blks,
caddr_t dest, int dowrite)
{
int result, retry;
/* Just in case some idiot actually tries to read/write -1 blocks... */
if (blks < 0)
return (-1);
/*
* Workaround for a problem with some HP ProLiant BIOS failing to work
* out the boot disk after installation. hrs and kuriyama discovered
* this problem with an HP ProLiant DL320e Gen 8 with a 3TB HDD, and
* discovered that an int13h call seems to cause a buffer overrun in
* the bios. The problem is alleviated by doing an extra read before
* the buggy read. It is not immediately known whether other models
* are similarly affected.
* Loop retrying the operation a couple of times. The BIOS
* may also retry.
*/
if (dowrite == BD_RD && dblk >= 0x100000000)
bd_io_workaround(bd);
for (retry = 0; retry < 3; retry++) {
if (bd->bd_flags & BD_MODEEDD)
result = bd_edd_io(bd, dblk, blks, dest, dowrite);
else
result = bd_chs_io(bd, dblk, blks, dest, dowrite);
if (result == 0) {
if (bd->bd_flags & BD_NO_MEDIA)
bd->bd_flags &= ~BD_NO_MEDIA;
break;
}
bd_reset_disk(bd->bd_unit);
/*
* Error codes:
* 20h controller failure
* 31h no media in drive (IBM/MS INT 13 extensions)
* 80h no media in drive, VMWare (Fusion)
* There is no reason to repeat the IO with errors above.
*/
if (result == 0x20 || result == 0x31 || result == 0x80) {
bd->bd_flags |= BD_NO_MEDIA;
break;
}
}
if (result != 0 && (bd->bd_flags & BD_NO_MEDIA) == 0) {
if (dowrite == BD_WR) {
printf("%s%d: Write %d sector(s) from %p (0x%x) "
"to %lld: 0x%x\n", dev->dd.d_dev->dv_name,
dev->dd.d_unit, blks, dest, VTOP(dest), dblk,
result);
} else {
printf("%s%d: Read %d sector(s) from %lld to %p "
"(0x%x): 0x%x\n", dev->dd.d_dev->dv_name,
dev->dd.d_unit, blks, dblk, dest, VTOP(dest),
result);
}
}
return (result);
}
/*
* Return the BIOS geometry of a given "fixed drive" in a format
* suitable for the legacy bootinfo structure. Since the kernel is
* expecting raw int 0x13/0x8 values for N_BIOS_GEOM drives, we
* prefer to get the information directly, rather than rely on being
* able to put it together from information already maintained for
* different purposes and for a probably different number of drives.
*
* For valid drives, the geometry is expected in the format (31..0)
* "000000cc cccccccc hhhhhhhh 00ssssss"; and invalid drives are
* indicated by returning the geometry of a "1.2M" PC-format floppy
* disk. And, incidentally, what is returned is not the geometry as
* such but the highest valid cylinder, head, and sector numbers.
*/
uint32_t
bd_getbigeom(int bunit)
{
v86.ctl = V86_FLAGS;
v86.addr = DISK_BIOS;
v86.eax = CMD_READ_PARAM;
v86.edx = 0x80 + bunit;
v86int();
if (V86_CY(v86.efl))
return (0x4f010f);
return (((v86.ecx & 0xc0) << 18) | ((v86.ecx & 0xff00) << 8) |
(v86.edx & 0xff00) | (v86.ecx & 0x3f));
}
/*
* Return a suitable dev_t value for (dev).
*
* In the case where it looks like (dev) is a SCSI disk, we allow the number of
* IDE disks to be specified in $num_ide_disks. There should be a Better Way.
*/
int
bd_getdev(struct i386_devdesc *d)
{
struct disk_devdesc *dev;
bdinfo_t *bd;
int biosdev;
int major;
int rootdev;
char *nip, *cp;
int i, unit, slice, partition;
/* XXX: Assume partition 'a'. */
slice = 0;
partition = 0;
dev = (struct disk_devdesc *)d;
bd = bd_get_bdinfo(&dev->dd);
if (bd == NULL)
return (-1);
biosdev = bd_unit2bios(d);
DPRINTF("unit %d BIOS device %d", dev->dd.d_unit, biosdev);
if (biosdev == -1) /* not a BIOS device */
return (-1);
if (dev->dd.d_dev->dv_type == DEVT_DISK) {
if (disk_open(dev, bd->bd_sectors * bd->bd_sectorsize,
bd->bd_sectorsize) != 0) /* oops, not a viable device */
return (-1);
else
disk_close(dev);
slice = dev->d_slice + 1;
partition = dev->d_partition;
}
if (biosdev < 0x80) {
/* floppy (or emulated floppy) or ATAPI device */
if (bd->bd_type == DT_ATAPI) {
/* is an ATAPI disk */
major = WFDMAJOR;
} else {
/* is a floppy disk */
major = FDMAJOR;
}
} else {
/* assume an IDE disk */
major = WDMAJOR;
}
/* default root disk unit number */
unit = biosdev & 0x7f;
if (dev->dd.d_dev->dv_type == DEVT_CD) {
/*
* 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 a better kludge to set the root disk unit number */
if ((nip = getenv("root_disk_unit")) != NULL) {
i = strtol(nip, &cp, 0);
/* check for parse error */
if ((cp != nip) && (*cp == 0))
unit = i;
}
rootdev = MAKEBOOTDEV(major, slice, unit, partition);
DPRINTF("dev is 0x%x\n", rootdev);
return (rootdev);
}