Minix2.0/src/kernel/esdi_wini.c
/* device dependent part of a hard disk driver for ibm ps/2 esdi adapter
*
* written by doug burks, based on other minix wini drivers.
* some additions by art roberts
*
* references:
* ibm personal system/2 hardware technical reference (1988)
* ibm 60/120mb fixed disk drive technical reference (1988)
*
* caveats:
* * this driver has been reported to work on ibm ps/2 models 50 and
* 70 with ibm's 60/120mb hard disks.
* * for a true esdi adapter, changes will have to be made, but this
* certainly serves as a good start.
* * no timeouts are implemented, so this driver could hang under
* adverse conditions.
* * the error processing has not been tested. my disk works too well.
*
* The file contains one entry point:
*
* esdi_winchester_task: main entry when system is brought up
*
*
* Changes:
* 3 May 1992 by Kees J. Bot: device dependent/independent split.
*/
#include "kernel.h"
#include "driver.h"
#include "drvlib.h"
#if ENABLE_ESDI_WINI
/* If the DMA buffer is large enough then use it always. */
#define USE_BUF (DMA_BUF_SIZE > BLOCK_SIZE)
/***** esdi i/o adapter ports */
#define CMD_REG 0x3510 /* command interface register */
#define STAT_REG 0x3510 /* status interface register */
#define BCTL_REG 0x3512 /* basic control register */
#define BST_REG 0x3512 /* basic status register */
#define ATT_REG 0x3513 /* attention register */
#define INT_REG 0x3513 /* interrupt status register */
/***** basic status register bits */
#define DMA_ENA 0x80 /* DMA enabled? */
#define INT_PND 0x40 /* interrupt pending? */
#define CMD_PRG 0x20 /* command in progress? */
#define BUSY 0x10 /* is adapter busy? */
#define STR_FUL 0x08 /* status interface register set? */
#define CMD_FUL 0x04 /* command interface register full? */
#define XFR_REQ 0x02 /* data transfer operation ready? */
#define INT_SET 0x01 /* adapter sending interrupt? */
/***** attention register commands */
#define ATT_CMD 0x01 /* command request */
#define ATT_EOI 0x02 /* end of interrupt processing */
#define ATT_ABT 0x03 /* abort the current command */
#define ATT_RST 0xE4 /* reset the esdi adapter */
/***** dma register addresses */
#define DMA_EXTCMD 0x18 /* extended function register */
#define DMA_EXEC 0x1A /* extended function execute */
/***** miscellaneous */
#define ERR (-1) /* general error code */
#define ERR_BAD_SECTOR (-2) /* block marked bad detected */
#define MAX_ERRORS 4 /* maximum number of read/write retries */
#define MAX_DRIVES 2 /* maximum number of physical drives */
#define NR_DEVICES (MAX_DRIVES*DEV_PER_DRIVE)
/* Maximum number of logical devices */
#define SUB_PER_DRIVE (NR_PARTITIONS * NR_PARTITIONS)
#define NR_SUBDEVS (MAX_DRIVES * SUB_PER_DRIVE)
#define SYS_PORTA 0x92 /* system control port a */
#define LIGHT_ON 0xC0 /* fixed-disk activity light reg. mask */
/***** variables */
PRIVATE struct wini { /* disk/partition information */
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; /* actual number of physical disk drive */
PRIVATE int command[4]; /* controller command buffer */
PRIVATE unsigned int status_block[9]; /* status block output from a command */
PRIVATE int dma_channel; /* fixed disk dma channel number */
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 int w_istat; /* interrupt status of last command */
PRIVATE struct device *w_dv; /* device's base and size */
/***** functions */
FORWARD _PROTOTYPE( struct device *w_prepare, (int device) );
FORWARD _PROTOTYPE( char *w_name, (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( int w_command, (int device, int cmd, int num_words) );
FORWARD _PROTOTYPE( int w_schedule, (int proc_nr, struct iorequest_s *iop) );
FORWARD _PROTOTYPE( int w_finish, (void) );
FORWARD _PROTOTYPE( int w_transfer, (struct trans *tp, unsigned count) );
FORWARD _PROTOTYPE( int w_att_write, (int value) );
FORWARD _PROTOTYPE( void w_interrupt, (int dma) );
FORWARD _PROTOTYPE( int w_handler, (int irq) );
FORWARD _PROTOTYPE( void w_dma_setup, (struct trans *tp, unsigned count) );
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 */
};
/*===========================================================================*
* esdi_winchester_task *
*===========================================================================*/
PUBLIC void esdi_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[] = "esdi-hd5";
name[7] = '0' + w_drive * DEV_PER_DRIVE;
return name;
}
/*============================================================================*
* 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()
{
/* initializes everything needed to run the hard disk
*
* the following items are initialized:
* -- hard disk attributes stored in bios
* -- dma transfer channel, read from system register
* -- dma transfer and interrupts [disabled]
*
* the hard disk adapter is initialized when the ibm ps/2 is turned on,
* using the programmable option select registers. thus the only
* additional initialization is making sure the dma transfer and interrupts
* are disabled. other initialization problems could be checked for, such
* as an operation underway. the paranoid could add a check for adapter
* activity and abort the operations. the truly paranoid can reset the
* adapter. until such worries are proven, why bother?
*/
unsigned int drive; /* hard disk drive number */
unsigned long size; /* hard disk size */
/* get the number of drives from the bios */
phys_copy(0x475L, tmp_phys, 1L);
nr_drives = tmp_buf[0];
if (nr_drives > MAX_DRIVES) nr_drives = MAX_DRIVES;
put_irq_handler(AT_WINI_IRQ, w_handler);
enable_irq(AT_WINI_IRQ); /* ready for winchester interrupts */
for (drive = 0; drive < nr_drives; ++drive) {
(void) w_prepare(drive * DEV_PER_DRIVE);
if (w_command(drive, 0x0609, 6) != OK) {
printf("%s: unable to get parameters\n", w_name());
nr_drives = drive;
break;
}
/* size of the drive */
size = ((unsigned long) status_block[2] << 0) |
((unsigned long) status_block[3] << 16);
if (drive == 0) {
if (w_command(7, 0x060A, 5) != OK) {
printf("%s: unable to get dma channel\n", w_name());
nr_drives = 0;
return;
}
dma_channel = (status_block[2] & 0x3C00) >> 10;
}
printf("%s: %lu sectors\n", w_name(), size);
w_wn->part[0].dv_size = size << SECTOR_SHIFT;
}
}
/*===========================================================================*
* w_command *
*===========================================================================*/
PRIVATE int w_command(device, cmd, num_words)
int device; /* i device to operate on */
/* 1-2 physical disk drive number */
/* 7 hard disk controller */
int cmd; /* i command to execute */
int num_words; /* i expected size of status block */
{
/* executes a command for a particular device
*
* the operation is conducted as follows:
* -- create the command block
* -- initialize for command reading by the controller
* -- write the command block to the controller, making sure the
* controller has digested the previous command word, before shoving
* the next down its throat
* -- wait for an interrupt
* -- read expected number of words of command status information
* -- return the command status block
*
* reading and writing registers is accompanied by enabling and disabling
* interrupts to ensure that the status register contents still apply when
* the desired register is read/written.
*/
register int ki; /* -- scratch -- */
int status; /* disk adapter status register value */
device <<= 5; /* adjust device for our use */
command[0] = cmd | device; /* build command block */
command[1] = 0;
w_att_write(device | ATT_CMD);
for (ki = 0; ki < 2; ++ki) {
out_word(CMD_REG, command[ki]);
unlock();
while (TRUE) {
lock();
status = in_byte(BST_REG);
if (!(status & CMD_FUL)) break;
unlock();
}
}
unlock();
w_interrupt(0);
if (w_istat != (device | 0x01)) {
w_att_write(device | ATT_ABT);
w_interrupt(0);
return(ERR);
}
for (ki = 0; ki < num_words; ++ki) {
while (TRUE) {
lock();
status = in_byte(BST_REG);
if (status & STR_FUL) break;
unlock();
}
status_block[ki] = in_word(STAT_REG);
unlock();
}
w_att_write(device | ATT_EOI);
return(OK);
}
/*===========================================================================*
* 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 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;
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 {
/* Note: No 64K boundary problem, the better PS/2's have a
* working DMA chip.
*/
dma_phys = user_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()
{
/* carries out the I/O requests gathered in wtrans[]
*
* fills the disk information structure for one block at a time or many
* in a row before calling 'w_transfer' to do the dirty work. while
* unsuccessful operations are re-tried, this may be superfluous, since
* the controller does the same on its own. turns on the fixed disk
* activity light, while busy. computers need blinking lights, right?
*/
struct trans *tp = wtrans, *tp2;
unsigned count;
int r, errors = 0, 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;
}
/* Turn on the disk activity light. */
out_byte(SYS_PORTA, in_byte(SYS_PORTA) | LIGHT_ON);
/* Perform the transfer. */
r = w_transfer(tp, count);
/* Turn off the disk activity light. */
out_byte(SYS_PORTA, in_byte(SYS_PORTA) & ~LIGHT_ON);
if (r != OK) {
/* An error occurred, try again block by block unless */
if (r == ERR_BAD_SECTOR || ++errors == MAX_ERRORS)
return(tp->iop->io_nbytes = EIO);
many = 0;
continue;
}
errors = 0;
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_transfer *
*===========================================================================*/
PRIVATE int w_transfer(tp, count)
struct trans *tp; /* pointer to the transfer struct */
unsigned int count; /* bytes to transfer */
{
/* reads/writes a single block of data from/to the hard disk
*
* the read/write operation performs the following steps:
* -- create the command block
* -- initialize the command reading by the controller
* -- write the command block to the controller, making sure the
* controller has digested the previous command word, before
* shoving the next down its throat.
* -- wait for an interrupt, which must return a 'data transfer ready'
* status. abort the command if it doesn't.
* -- set up and start up the direct memory transfer
* -- wait for an interrupt, signalling the end of the transfer
*/
int device; /* device mask for the command register */
int ki; /* -- scratch -- */
int status; /* basic status register value */
device = w_drive << 5;
command[0] = (w_opcode == DEV_WRITE ? 0x4602 : 0x4601) | device;
command[1] = count >> SECTOR_SHIFT;
command[2] = (int) (tp->block & 0xFFFF);
command[3] = (int) (tp->block >> 16);
w_att_write(device | ATT_CMD);
for (ki = 0; ki < 4; ++ki) {
out_word(CMD_REG, command[ki]);
unlock();
while (TRUE) {
lock();
status = in_byte(BST_REG);
if (!(status & CMD_FUL)) break;
unlock();
}
}
unlock();
w_interrupt(0);
if (w_istat != (device | 0x0B)) {
w_att_write(device | ATT_ABT);
w_interrupt(0);
return(ERR);
}
w_dma_setup(tp, count);
w_interrupt(1);
w_att_write(device | ATT_EOI);
if ((w_istat & 0x0F) > 8) return(ERR);
return(OK);
}
/*==========================================================================*
* w_att_write *
*==========================================================================*/
PRIVATE int w_att_write(value)
register int value;
{
/* writes a command to the esdi attention register
*
* waits for the controller to finish its business before sending the
* command to the controller. note that the interrupts must be off to read
* the basic status register and, if the controller is ready, must not be
* turned back on until the attention register command is sent.
*/
int status; /* basic status register value */
while (TRUE) {
lock();
status = in_byte(BST_REG);
if (!(status & (INT_PND | BUSY))) break;
unlock();
}
out_byte(ATT_REG, value);
unlock();
return(OK);
}
/*===========================================================================*
* w_interrupt *
*===========================================================================*/
PRIVATE void w_interrupt(dma)
int dma; /* i dma transfer is underway */
{
/* waits for an interrupt from the hard disk controller
*
* enable interrupts on the hard disk and interrupt controllers (and dma if
* necessary). wait for an interrupt. when received, return the interrupt
* status register value.
*
* an interrupt can be detected either from the basic status register or
* through a system interrupt handler. the handler is used for all
* interrupts, due to the expected long times to process reads and writes
* and to avoid busy waits.
*/
message dummy; /* -- scratch -- */
out_byte(BCTL_REG, dma ? 0x03 : 0x01);
receive(HARDWARE, &dummy);
out_byte(BCTL_REG, 0);
if (dma) out_byte(DMA_EXTCMD, 0x90 + dma_channel);
}
/*==========================================================================*
* w_handler *
*==========================================================================*/
PRIVATE int w_handler(irq)
int irq;
{
/* Disk interrupt, send message to winchester task and reenable interrupts. */
w_istat = in_byte(INT_REG);
interrupt(WINCHESTER);
return 1;
}
/*==========================================================================*
* w_dma_setup *
*==========================================================================*/
PRIVATE void w_dma_setup(tp, count)
struct trans *tp;
unsigned int count;
{
/* programs the dma controller to move data to and from the hard disk.
*
* uses the extended mode operation of the ibm ps/2 interrupt controller
* chip, rather than the intel 8237 (pc/at) compatible mode.
*/
lock();
out_byte(DMA_EXTCMD, 0x90 + dma_channel);
/* Disable access to dma channel 5 */
out_byte(DMA_EXTCMD, 0x20 + dma_channel);
/* Clear the address byte pointer */
out_byte(DMA_EXEC, (int) tp->dma >> 0); /* address bits 0..7 */
out_byte(DMA_EXEC, (int) tp->dma >> 8); /* address bits 8..15 */
out_byte(DMA_EXEC, (int) (tp->dma >> 16)); /* address bits 16..19 */
out_byte(DMA_EXTCMD, 0x40 + dma_channel);
/* Clear the count byte pointer */
out_byte(DMA_EXEC, (count - 1) >> 0); /* count bits 0..7 */
out_byte(DMA_EXEC, (count - 1) >> 8); /* count bits 8..15 */
out_byte(DMA_EXTCMD, 0x70 + dma_channel);
/* Set the transfer mode */
out_byte(DMA_EXEC, w_opcode == DEV_WRITE ? 0x44 : 0x4C);
out_byte(DMA_EXTCMD, 0xA0 + dma_channel);
/* Enable access to dma channel 5 */
unlock();
}
/*============================================================================*
* w_geometry *
*============================================================================*/
PRIVATE void w_geometry(entry)
struct partition *entry;
{
entry->cylinders = (w_wn->part[0].dv_size >> SECTOR_SHIFT) / (64 * 32);
entry->heads = 64;
entry->sectors = 32;
}
#endif /* ENABLE_ESDI_WINI */