📄 bios_wini.c
字号:
/* This file contains the "device dependent" part of a hard disk driver that
* uses the ROM BIOS. It makes a call and just waits for the transfer to
* happen. It is not interrupt driven and thus will (*) have poor performance.
* The advantage is that it should work on virtually any PC, XT, 386, PS/2
* or clone. The demo disk uses this driver. It is suggested that all
* MINIX users try the other drivers, and use this one only as a last resort,
* if all else fails.
*
* (*) The performance is within 10% of the AT driver for reads on any disk
* and writes on a 2:1 interleaved disk, it will be DMA_BUF_SIZE bytes
* per revolution for a minimum of 60 kb/s for writes to 1:1 disks.
*
* The file contains one entry point:
*
* bios_winchester_task: main entry when system is brought up
*
*
* Changes:
* 30 Apr 1992 by Kees J. Bot: device dependent/independent split.
*/
#include "kernel.h"
#include "driver.h"
#include "drvlib.h"
#if ENABLE_BIOS_WINI
/* If the DMA buffer is large enough then use it always. */
#define USE_BUF (DMA_BUF_SIZE > BLOCK_SIZE)
/* Error codes */
#define ERR (-1) /* general error */
/* Parameters for the disk drive. */
#define MAX_DRIVES 4 /* this driver supports 4 drives (hd0 - hd19)*/
#define MAX_SECS 255 /* bios can transfer this many sectors */
#define NR_DEVICES (MAX_DRIVES * DEV_PER_DRIVE)
#define SUB_PER_DRIVE (NR_PARTITIONS * NR_PARTITIONS)
#define NR_SUBDEVS (MAX_DRIVES * SUB_PER_DRIVE)
/* BIOS parameters */
#define BIOS_ASK 0x08 /* opcode for asking BIOS for parameters */
#define BIOS_RESET 0x00 /* opcode for resetting disk BIOS */
#define BIOS_READ 0x02 /* opcode for BIOS read */
#define BIOS_WRITE 0x03 /* opcode for BIOS write */
#define HD_CODE 0x80 /* BIOS code for hard disk drive 0 */
/* Variables. */
PRIVATE struct wini { /* main drive struct, one entry per drive */
unsigned cylinders; /* number of cylinders */
unsigned heads; /* number of heads */
unsigned sectors; /* number of sectors per track */
unsigned open_ct; /* in-use count */
struct device part[DEV_PER_DRIVE]; /* primary partitions: hd[0-4] */
struct device subpart[SUB_PER_DRIVE]; /* subpartitions: hd[1-4][a-d] */
} wini[MAX_DRIVES], *w_wn;
PRIVATE struct trans {
struct iorequest_s *iop; /* belongs to this I/O request */
unsigned long block; /* first sector to transfer */
unsigned count; /* byte count */
phys_bytes phys; /* user physical address */
phys_bytes dma; /* DMA physical address */
} wtrans[NR_IOREQS];
PRIVATE int nr_drives = MAX_DRIVES; /* Number of drives */
PRIVATE struct trans *w_tp; /* to add transfer requests */
PRIVATE unsigned w_count; /* number of bytes to transfer */
PRIVATE unsigned long w_nextblock; /* next block on disk to transfer */
PRIVATE int w_opcode; /* DEV_READ or DEV_WRITE */
PRIVATE int w_drive; /* selected drive */
PRIVATE struct device *w_dv; /* device's base and size */
extern unsigned Ax, Bx, Cx, Dx, Es; /* to hold registers for BIOS calls */
FORWARD _PROTOTYPE( struct device *w_prepare, (int device) );
FORWARD _PROTOTYPE( char *w_name, (void) );
FORWARD _PROTOTYPE( int w_schedule, (int proc_nr, struct iorequest_s *iop) );
FORWARD _PROTOTYPE( int w_finish, (void) );
FORWARD _PROTOTYPE( int w_do_open, (struct driver *dp, message *m_ptr) );
FORWARD _PROTOTYPE( int w_do_close, (struct driver *dp, message *m_ptr) );
FORWARD _PROTOTYPE( void w_init, (void) );
FORWARD _PROTOTYPE( void enable_vectors, (void) );
FORWARD _PROTOTYPE( void w_geometry, (struct partition *entry));
/* Entry points to this driver. */
PRIVATE struct driver w_dtab = {
w_name, /* current device's name */
w_do_open, /* open or mount request, initialize device */
w_do_close, /* release device */
do_diocntl, /* get or set a partition's geometry */
w_prepare, /* prepare for I/O on a given minor device */
w_schedule, /* precompute cylinder, head, sector, etc. */
w_finish, /* do the I/O */
nop_cleanup, /* no cleanup needed */
w_geometry /* tell the geometry of the disk */
};
/*===========================================================================*
* bios_winchester_task *
*===========================================================================*/
PUBLIC void bios_winchester_task()
{
driver_task(&w_dtab);
}
/*===========================================================================*
* w_prepare *
*===========================================================================*/
PRIVATE struct device *w_prepare(device)
int device;
{
/* Prepare for I/O on a device. */
/* Nothing to transfer as yet. */
w_count = 0;
if (device < NR_DEVICES) { /* hd0, hd1, ... */
w_drive = device / DEV_PER_DRIVE; /* save drive number */
w_wn = &wini[w_drive];
w_dv = &w_wn->part[device % DEV_PER_DRIVE];
} else
if ((unsigned) (device -= MINOR_hd1a) < NR_SUBDEVS) { /* hd1a, hd1b, ... */
w_drive = device / SUB_PER_DRIVE;
w_wn = &wini[w_drive];
w_dv = &w_wn->subpart[device % SUB_PER_DRIVE];
} else {
return(NIL_DEV);
}
return(w_drive < nr_drives ? w_dv : NIL_DEV);
}
/*===========================================================================*
* w_name *
*===========================================================================*/
PRIVATE char *w_name()
{
/* Return a name for the current device. */
static char name[] = "bios-hd5";
name[7] = '0' + w_drive * DEV_PER_DRIVE;
return name;
}
/*===========================================================================*
* w_schedule *
*===========================================================================*/
PRIVATE int w_schedule(proc_nr, iop)
int proc_nr; /* process doing the request */
struct iorequest_s *iop; /* pointer to read or write request */
{
/* Gather I/O requests on consecutive blocks so they may be read/written
* in one bios command if using a buffer. Check and gather all the requests
* and try to finish them as fast as possible if unbuffered.
*/
int r, opcode;
unsigned long pos;
unsigned nbytes, count, dma_count;
unsigned long block;
phys_bytes user_phys, dma_phys;
/* This many bytes to read/write */
nbytes = iop->io_nbytes;
if ((nbytes & SECTOR_MASK) != 0) return(iop->io_nbytes = EINVAL);
/* From/to this position on the device */
pos = iop->io_position;
if ((pos & SECTOR_MASK) != 0) return(iop->io_nbytes = EINVAL);
/* To/from this user address */
user_phys = numap(proc_nr, (vir_bytes) iop->io_buf, nbytes);
if (user_phys == 0) return(iop->io_nbytes = EINVAL);
/* Read or write? */
opcode = iop->io_request & ~OPTIONAL_IO;
/* Which block on disk and how close to EOF? */
if (pos >= w_dv->dv_size) return(OK); /* At EOF */
if (pos + nbytes > w_dv->dv_size) nbytes = w_dv->dv_size - pos;
block = (w_dv->dv_base + pos) >> SECTOR_SHIFT;
if (USE_BUF && w_count > 0 && block != w_nextblock) {
/* This new request can't be chained to the job being built */
if ((r = w_finish()) != OK) return(r);
}
/* The next consecutive block */
if (USE_BUF) w_nextblock = block + (nbytes >> SECTOR_SHIFT);
/* While there are "unscheduled" bytes in the request: */
do {
count = nbytes;
if (USE_BUF) {
if (w_count == DMA_BUF_SIZE) {
/* Can't transfer more than the buffer allows. */
if ((r = w_finish()) != OK) return(r);
}
if (w_count + count > DMA_BUF_SIZE)
count = DMA_BUF_SIZE - w_count;
} else {
if (w_tp == wtrans + NR_IOREQS) {
/* All transfer slots in use. */
if ((r = w_finish()) != OK) return(r);
}
}
if (w_count == 0) {
/* The first request in a row, initialize. */
w_opcode = opcode;
w_tp = wtrans;
}
if (USE_BUF) {
dma_phys = tmp_phys + w_count;
} else {
/* Memory chunk to DMA. */
dma_phys = user_phys;
dma_count = dma_bytes_left(dma_phys);
if (dma_phys >= (1L << 20)) {
/* The BIOS can only address the first megabyte. */
count = SECTOR_SIZE;
dma_phys = tmp_phys;
} else
if (dma_count < count) {
/* Nearing a 64K boundary. */
if (dma_count >= SECTOR_SIZE) {
/* Can read a few sectors before hitting the
* boundary.
*/
count = dma_count & ~SECTOR_MASK;
} else {
/* Must use the special buffer for this. */
count = SECTOR_SIZE;
dma_phys = tmp_phys;
}
}
}
/* Store I/O parameters */
w_tp->iop = iop;
w_tp->block = block;
w_tp->count = count;
w_tp->phys = user_phys;
w_tp->dma = dma_phys;
/* Update counters */
w_tp++;
w_count += count;
block += count >> SECTOR_SHIFT;
user_phys += count;
nbytes -= count;
} while (nbytes > 0);
return(OK);
}
/*===========================================================================*
* w_finish *
*===========================================================================*/
PRIVATE int w_finish()
{
/* Carry out the I/O requests gathered in wtrans[]. */
struct trans *tp = wtrans, *tp2;
unsigned count, cylinder, sector, head, hicyl, locyl;
unsigned secspcyl = w_wn->heads * w_wn->sectors;
int many = USE_BUF;
if (w_count == 0) return(OK); /* Spurious finish. */
do {
if (w_opcode == DEV_WRITE) {
tp2 = tp;
count = 0;
do {
if (USE_BUF || tp2->dma == tmp_phys) {
phys_copy(tp2->phys, tp2->dma,
(phys_bytes) tp2->count);
}
count += tp2->count;
tp2++;
} while (many && count < w_count);
} else {
count = many ? w_count : tp->count;
}
/* Do the transfer */
cylinder = tp->block / secspcyl;
sector = (tp->block % w_wn->sectors) + 1;
head = (tp->block % secspcyl) / w_wn->sectors;
Ax = w_opcode == DEV_WRITE ? BIOS_WRITE : BIOS_READ;
Ax = (Ax << 8) | (count >> SECTOR_SHIFT); /* opcode & count */
Bx = (unsigned) tp->dma % HCLICK_SIZE; /* low order 4 bits */
Es = physb_to_hclick(tp->dma); /* high order 16 bits */
hicyl = (cylinder & 0x300) >> 8; /* two high-order bits */
locyl = (cylinder & 0xFF); /* 8 low-order bits */
Cx = sector | (hicyl << 6) | (locyl << 8);
Dx = (HD_CODE + w_drive) | (head << 8);
level0(bios13);
if ((Ax & 0xFF00) != 0) {
/* An error occurred, try again block by block unless */
if (!many) return(tp->iop->io_nbytes = EIO);
many = 0;
continue;
}
w_count -= count;
do {
if (w_opcode == DEV_READ) {
if (USE_BUF || tp->dma == tmp_phys) {
phys_copy(tp->dma, tp->phys,
(phys_bytes) tp->count);
}
}
tp->iop->io_nbytes -= tp->count;
count -= tp->count;
tp++;
} while (count > 0);
} while (w_count > 0);
return(OK);
}
/*============================================================================*
* w_do_open *
*============================================================================*/
PRIVATE int w_do_open(dp, m_ptr)
struct driver *dp;
message *m_ptr;
{
/* Device open: Initialize the controller and read the partition table. */
static int init_done = FALSE;
if (!init_done) { w_init(); init_done = TRUE; }
if (w_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);
if (w_wn->open_ct++ == 0) {
/* Partition the disk. */
partition(&w_dtab, w_drive * DEV_PER_DRIVE, P_PRIMARY);
}
return(OK);
}
/*============================================================================*
* w_do_close *
*============================================================================*/
PRIVATE int w_do_close(dp, m_ptr)
struct driver *dp;
message *m_ptr;
{
/* Device close: Release a device. */
if (w_prepare(m_ptr->DEVICE) == NIL_DEV) return(ENXIO);
w_wn->open_ct--;
return(OK);
}
/*===========================================================================*
* w_init *
*===========================================================================*/
PRIVATE void w_init()
{
/* This routine is called at startup to initialize the drive parameters. */
int drive;
struct wini *wn;
/* Enable real mode BIOS vectors. */
enable_vectors();
/* Set the geometry of the drives */
for (drive = 0; drive < nr_drives; drive++) {
(void) w_prepare(drive * DEV_PER_DRIVE);
wn = w_wn;
Dx = drive + HD_CODE;
Ax = (BIOS_ASK << 8);
level0(bios13);
nr_drives = (Ax & 0xFF00) == 0 ? (Dx & 0xFF) : drive;
if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES;
if (drive >= nr_drives) break;
wn->heads = (Dx >> 8) + 1;
wn->sectors = (Cx & 0x3F);
wn->cylinders = ((Cx << 2) & 0x0300) + ((Cx >> 8) & 0x00FF) + 1;
wn->part[0].dv_size = ((unsigned long) wn->cylinders
* wn->heads * wn->sectors) << SECTOR_SHIFT;
printf("%s: %d cylinders, %d heads, %d sectors per track\n",
w_name(), wn->cylinders, wn->heads, wn->sectors);
}
}
/*===========================================================================*
* enable_vectors *
*===========================================================================*/
PRIVATE void enable_vectors()
{
/* Protected mode Minix has reprogrammed the interrupt controllers to
* use different vectors from the BIOS. This means that the BIOS vectors
* must be copied to the Minix locations for use in real mode. The bios13()
* function enables all interrupts that Minix doesn't use, and masks all
* interrupts that Minix does use when it makes the "INT 13" call. Alas
* more than one clock tick may occur while the disk is active, so we need
* a special real mode clock interrupt handle to gather lost ticks.
*/
int vec, irq;
static u8_t clock_handler[] = {
0x50, 0xB0, 0x20, 0xE6, 0x20, 0xEB, 0x06, 0xE4,
0x61, 0x0C, 0x80, 0xE6, 0x61, 0x58, 0x53, 0x1E,
0xE8, 0x00, 0x00, 0x5B, 0x2E, 0xC5, 0x5F, 0x0A,
0xFF, 0x07, 0x1F, 0x5B, 0xCF,
0x00, 0x00, 0x00, 0x00,
};
u16_t vector[2];
if (!protected_mode) return;
for (irq = 0; irq < NR_IRQ_VECTORS; irq++) {
phys_copy(BIOS_VECTOR(irq)*4L, VECTOR(irq)*4L, 4L);
}
/* Install a clock interrupt handler to collect clock ticks when the
* BIOS disk driver is active. The handler is a simple bit of 8086 code
* that increments "lost_ticks".
*/
/* Let the clock interrupt point to the handler. */
vector[0] = vir2phys(clock_handler) % HCLICK_SIZE;
vector[1] = vir2phys(clock_handler) / HCLICK_SIZE;
phys_copy(vir2phys(vector), VECTOR(CLOCK_IRQ)*4L, 4L);
/* Store the address of lost_ticks in the handler. */
vector[0] = vir2phys(&lost_ticks) % HCLICK_SIZE;
vector[1] = vir2phys(&lost_ticks) / HCLICK_SIZE;
memcpy(clock_handler + 0x1D, vector, 4);
if (ps_mca) clock_handler[6]= 0; /* (PS/2 port B clock ack) */
}
/*============================================================================*
* w_geometry *
*============================================================================*/
PRIVATE void w_geometry(entry)
struct partition *entry;
{
entry->cylinders = w_wn->cylinders;
entry->heads = w_wn->heads;
entry->sectors = w_wn->sectors;
}
#endif /* ENABLE_BIOS_WINI */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -