Minix1.5/kernel/bios_wini.c
/* This file contains 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 generic boot
* diskette 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.
* This version automatically determines the drive parameters and uses them.
*
* The driver supports the following operations (using message format m2):
*
* m_type DEVICE PROC_NR COUNT POSITION ADRRESS
* ----------------------------------------------------------------
* | DISK_READ | device | proc nr | bytes | offset | buf ptr |
* |------------+---------+---------+---------+---------+---------|
* | DISK_WRITE | device | proc nr | bytes | offset | buf ptr |
* ----------------------------------------------------------------
* |SCATTERED_IO| device | proc nr | requests| | iov ptr |
* ----------------------------------------------------------------
*
* The file contains one entry point:
*
* winchester_task: main entry when system is brought up
*
*/
#include "kernel.h"
#include <minix/callnr.h>
#include <minix/com.h>
#include <minix/partition.h>
/* Error codes */
#define ERR -1 /* general error */
/* Parameters for the disk drive. */
#define MAX_DRIVES 2 /* this driver supports 2 drives (hd0 - hd9)*/
#define DEV_PER_DRIVE (1 + NR_PARTITIONS) /* whole drive & each partn */
#define NR_DEVICES (MAX_DRIVES * DEV_PER_DRIVE)
#define SECTOR_SIZE 512 /* physical sector size in bytes */
/* 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 DRIVE 0x80 /* BIOS code for drive 0 */
PUBLIC int using_bios = TRUE; /* this disk driver uses the BIOS */
PRIVATE unsigned char buf[BLOCK_SIZE] = { 1 };
/* Buffer used by the startup routine */
/* Initialized to try to avoid DMA wrap. */
PRIVATE message w_mess; /* message buffer for in and out */
PRIVATE struct wini { /* main drive struct, one entry per drive */
int wn_opcode; /* DISK_READ or DISK_WRITE */
int wn_procnr; /* which proc wanted this operation? */
int wn_cylinder; /* cylinder number addressed */
int wn_sector; /* sector addressed */
int wn_head; /* head number addressed */
int wn_heads; /* maximum number of heads */
int wn_maxsec; /* maximum number of sectors per track */
long wn_low; /* lowest cylinder of partition */
long wn_size; /* size of partition in sectors */
int wn_count; /* byte count */
int wn_drive; /* 0x80 or 0x81 */
vir_bytes wn_address; /* user virtual address */
} wini[NR_DEVICES];
PRIVATE struct param {
int nr_cyl; /* Number of cylinders */
int nr_heads; /* Number of heads */
int nr_drives; /* Number of drives on this controler */
int nr_sectors; /* Number of sectors per track */
} param0, param1;
PRIVATE int nr_drives;
FORWARD void copy_prt();
FORWARD void get_params();
FORWARD void init_params();
FORWARD void replace();
FORWARD void sort();
FORWARD int w_do_rdwt();
/*=========================================================================*
* winchester_task *
*=========================================================================*/
PUBLIC void winchester_task()
{
/* Main program of the winchester disk driver task. */
int r, caller, proc_nr;
/* First initialize the controller */
init_params();
/* Here is the main loop of the disk task. It waits for a message, carries
* it out, and sends a reply.
*/
while (TRUE) {
/* First wait for a request to read or write a disk block. */
receive(ANY, &w_mess); /* get a request to do some work */
if (w_mess.m_source < 0) {
printf("winchester task got message from %d\n",w_mess.m_source);
continue;
}
caller = w_mess.m_source;
proc_nr = w_mess.PROC_NR;
/* Now carry out the work. */
switch(w_mess.m_type) {
case DISK_READ:
case DISK_WRITE: r = w_do_rdwt(&w_mess); break;
case SCATTERED_IO: r = do_vrdwt(&w_mess, w_do_rdwt); break;
default: r = EINVAL; break;
}
/* Finally, prepare and send the reply message. */
w_mess.m_type = TASK_REPLY;
w_mess.REP_PROC_NR = proc_nr;
w_mess.REP_STATUS = r; /* # of bytes transferred or error code */
send(caller, &w_mess); /* send reply to caller */
}
}
/*==========================================================================*
* w_do_rdwt *
*==========================================================================*/
PRIVATE int w_do_rdwt(m_ptr)
message *m_ptr; /* pointer to read or write w_message */
{
/* Carry out a read or write request from the disk. */
register struct wini *wn;
vir_bytes vir, ct;
unsigned locyl, hicyl, c1, c2, c3;
int r, device;
long sector;
phys_bytes user_phys;
/* Decode the w_message parameters. */
device = m_ptr->DEVICE; /* minor device #. 1-4 are partitions */
if (device < 0 || device >= NR_DEVICES) return(EIO);
if (m_ptr->COUNT != BLOCK_SIZE) return(EINVAL);
wn = &wini[device]; /* 'wn' points to entry for this drive */
/* Set opcode to BIOS_READ or BIOS_WRITE. Check for bad starting addr. */
wn->wn_opcode = (m_ptr->m_type == DISK_WRITE ? BIOS_WRITE : BIOS_READ);
if (m_ptr->POSITION % BLOCK_SIZE != 0) return(EINVAL);
/* Calculate the physical parameters */
sector = m_ptr->POSITION/SECTOR_SIZE; /* relative sector within partition */
if ((sector+BLOCK_SIZE/SECTOR_SIZE) > wn->wn_size) return(0);
sector += wn->wn_low; /* absolute sector number */
wn->wn_cylinder = sector / (wn->wn_heads * wn->wn_maxsec);
wn->wn_sector = (sector % wn->wn_maxsec);
wn->wn_head = (sector % (wn->wn_heads * wn->wn_maxsec) )/wn->wn_maxsec;
wn->wn_count = m_ptr->COUNT;
wn->wn_address = (vir_bytes) m_ptr->ADDRESS;
wn->wn_procnr = m_ptr->PROC_NR;
/* Do the transfer */
vir = (vir_bytes) wn->wn_address;
ct = (vir_bytes) wn->wn_count;
user_phys = numap(wn->wn_procnr, vir, ct);
Ax = (wn->wn_opcode << 8) | 2; /* read or write 2 sectors */
Bx = (unsigned) user_phys % HCLICK_SIZE; /* low order 4 bits */
Es = physb_to_hclick(user_phys); /* high order 16 bits */
hicyl = (wn->wn_cylinder >> 8) & 03; /* two high-order bits */
locyl = (wn->wn_cylinder & 0xFF); /* 8 low-order bits */
c1 = (locyl<<8);
c2 = (hicyl<<6);
c3 = ((unsigned) wn->wn_sector) + 1;
Cx = c1 | c2 | c3;
Dx = (wn->wn_head<<8) | wn->wn_drive;
bios13();
r = (Ax >> 8) & 0xFF;
return(r == 0 ? BLOCK_SIZE : EIO);
}
/*===========================================================================*
* get_params *
*===========================================================================*/
PRIVATE void get_params(dr, params)
int dr;
struct param *params;
{
Dx = dr + DRIVE;
Ax = (BIOS_ASK << 8);
bios13();
params->nr_heads = ((Dx >> 8) & 0xFF) + 1;
params->nr_sectors = (Cx & 0x3F);
params->nr_cyl = ((Cx & 0xC0) << 2) + ((Cx >> 8) & 0xFF);
params->nr_drives = (Dx & 0xFF);
}
/*===========================================================================*
* init_params *
*===========================================================================*/
PRIVATE void init_params()
{
/* This routine is called at startup to initialize the partition table,
* the number of drives and the controller
*/
unsigned int i;
/* Give control to the BIOS interrupt handler. */
if (pc_at) {
replace( (AT_WINI_IRQ & 0x07) + BIOS_IRQ8_VEC, AT_WINI_VECTOR);
cim_at_wini(); /* ready for AT wini interrupts */
} else {
replace( (XT_WINI_IRQ & 0x07) + BIOS_IRQ0_VEC, XT_WINI_VECTOR);
cim_xt_wini(); /* ready for XT wini interrupts */
}
/* Copy the parameters to the structures */
get_params(0, ¶m0);
get_params(1, ¶m1);
/* Get the number of drives */
nr_drives = param0.nr_drives;
if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES;
/* Set the parameters in the drive structure */
for (i = 0; i < DEV_PER_DRIVE; i++)
{
wini[i].wn_heads = param0.nr_heads;
wini[i].wn_maxsec = param0.nr_sectors;
wini[i].wn_drive = DRIVE;
}
wini[0].wn_low = wini[DEV_PER_DRIVE].wn_low = 0L;
wini[0].wn_size = (long)((long)param0.nr_cyl
* (long)param0.nr_heads * (long)param0.nr_sectors);
for (i = DEV_PER_DRIVE; i < (2*DEV_PER_DRIVE); i++)
{
wini[i].wn_heads = param1.nr_heads;
wini[i].wn_maxsec = param1.nr_sectors;
wini[i].wn_drive = DRIVE + 1;
}
wini[DEV_PER_DRIVE].wn_size = (long)((long)param1.nr_cyl
* (long)param1.nr_heads * (long)param1.nr_sectors);
/* Read the partition table for each drive and save them */
for (i = 0; i < nr_drives; i++) {
w_mess.DEVICE = i * 5;
w_mess.POSITION = 0L;
w_mess.COUNT = BLOCK_SIZE;
w_mess.ADDRESS = (char *) buf;
w_mess.PROC_NR = WINCHESTER;
w_mess.m_type = DISK_READ;
if (w_do_rdwt(&w_mess) != BLOCK_SIZE)
panic("Can't read partition table of winchester ", i);
copy_prt(i * DEV_PER_DRIVE);
}
}
/*===========================================================================*
* copy_prt *
*===========================================================================*/
PRIVATE void copy_prt(base_dev)
int base_dev; /* base device for drive */
{
/* This routine copies the partition table for the selected drive to
* the variables wn_low and wn_size
*/
register struct part_entry *pe;
register struct wini *wn;
for (pe = (struct part_entry *) &buf[PART_TABLE_OFF],
wn = &wini[base_dev + 1];
pe < ((struct part_entry *) &buf[PART_TABLE_OFF]) + NR_PARTITIONS;
++pe, ++wn) {
wn->wn_low = pe->lowsec;
wn->wn_size = pe->size;
/* Adjust low sector to a multiple of (BLOCK_SIZE/SECTOR_SIZE) for old
* Minix partitions only. We can assume the ratio is 2 and round to
* even, which is slightly simpler.
*/
if (pe->sysind == OLD_MINIX_PART && wn->wn_low & 1) {
++wn->wn_low;
--wn->wn_size;
}
}
sort(&wini[base_dev + 1]);
}
/*===========================================================================*
* sort *
*===========================================================================*/
PRIVATE void sort(wn)
register struct wini wn[];
{
register int i,j;
struct wini tmp;
for (i = 0; i < NR_PARTITIONS; i++)
for (j = 0; j < NR_PARTITIONS-1; j++)
if ((wn[j].wn_low == 0 && wn[j+1].wn_low != 0) ||
(wn[j].wn_low > wn[j+1].wn_low && wn[j+1].wn_low != 0)) {
tmp = wn[j];
wn[j] = wn[j+1];
wn[j+1] = tmp;
}
}
/*===========================================================================*
* replace *
*===========================================================================*/
PRIVATE void replace(from, to)
int from; /* vector to get replacement from */
int to; /* vector to replace */
{
/* Replace the vector 'to' in the interrupt table with its original BIOS
* vector 'from' in vec_table (they differ since the 8259 was reprogrammed).
* On the first call only, also restore all software interrupt vectors from
* vec_table except the trap vectors and SYS_VECTOR.
* (Doing it only on the first call is redundant, since this is only called
* once! We ought to swap our vectors back just before bios_wini replies.
* Then this routine should be made more efficient.)
*/
phys_bytes phys_b;
static int repl_called = FALSE; /* set on first call of replace */
int vec;
phys_b = umap(proc_ptr, D, (vir_bytes) vec_table, VECTOR_BYTES);
if (!repl_called) {
for (vec = 16; vec < VECTOR_BYTES / 4; ++vec)
if (vec != SYS_VECTOR &&
!(vec >= IRQ0_VECTOR && vec < IRQ0_VECTOR + 8) &&
!(vec >= IRQ8_VECTOR && vec < IRQ8_VECTOR + 8))
phys_copy(phys_b + 4L * vec, 4L * vec, 4L);
repl_called = TRUE;
}
lock();
phys_copy(phys_b + 4L * from, 4L * to, 4L);
unlock();
}