Coherent4.2.10/conf/at/src/at.c
/*
* $Header: /v/src/rcskrnl/conf/at/src/RCS/at.c,v 420.6 1993/12/01 22:10:37 srcadm Exp srcadm $
*/
/*********************************************************************
*
* Coherent, Mark Williams Company
* RCS Header
* This file contains proprietary information and is considered
* a trade secret. Unauthorized use is prohibited.
*
* $Id: at.c,v 420.6 1993/12/01 22:10:37 srcadm Exp srcadm $
*
* $Log: at.c,v $
* Revision 420.6 1993/12/01 22:10:37 srcadm
* Fixed 'break' bug on read failure in state machine and
* added printf for version and revision tracking.
*
* Revision 420.4 1993/11/30 19:31:21 srcadm
* Initial RCS submission.
*/
/* Embedded version constant */
static char rcsid[] = "#(@) $Id"
"at.c: Last Modified: Sun Apr 24 22:46:56 1994 by [chris]";
/*
* This is a driver for the
* hard disk on the AT.
*
* Reads drive characteristics from ROM (thru interrupt vector 0x41 and 0x46).
* Reads partition information from disk.
*
* Revision 2.5 93/08/20 10:49:54 nigel
* Fix race work queue interlock problem
*
* Revision 2.4 93/08/19 10:38:34 nigel
* r83 ioctl (), corefile, new headers
*
* Revision 2.3 93/08/19 04:02:13 nigel
* Nigel's R83
*/
#include <common/_ccompat.h>
#include <sys/coherent.h>
#include <stdlib.h>
#include <kernel/typed.h>
#include <sys/buf.h>
#include <sys/cmn_err.h>
#include <sys/con.h>
#include <sys/devices.h>
#include <sys/errno.h>
#include <sys/fdisk.h>
#include <sys/hdioctl.h>
#include <sys/inline.h>
#include <sys/stat.h>
#include <sys/uproc.h>
#include <coh/defer.h>
#include <coh/fakedma.h>
#include <coh/misc.h>
#define LOCAL
/*
* Configurable parameters
*/
#define HDBASE 0x01F0 /* Port base */
#define HARDLIM 8 /* number of retries before fail */
#define BADLIM 100 /* num to stop recov if flagged bad */
#define BIT(n) (1 << (n))
#define CMOSA 0x70 /* write cmos address to this port */
#define CMOSD 0x71 /* read cmos data through this port */
#if _I386
# define VERBOSE 1
#endif
/*
* I/O Port Addresses
*/
#define DATA_REG (HDBASE + 0) /* data (r/w) */
#define AUX_REG (HDBASE + 1) /* error(r), write precomp cyl/4 (w) */
#define NSEC_REG (HDBASE + 2) /* sector count (r/w) */
#define SEC_REG (HDBASE + 3) /* sector number (r/w) */
#define LCYL_REG (HDBASE + 4) /* low cylinder (r/w) */
#define HCYL_REG (HDBASE + 5) /* high cylinder (r/w) */
#define HDRV_REG (HDBASE + 6) /* drive/head (r/w) (D <<4)+(1 << H) */
#define CHS_MAGIC 0xa0 /* Magic cookie for CHS access */
#define LBA0_REG (HDBASE + 3) /* Logical block address byte 0 */
#define LBA1_REG (HDBASE + 4) /* Logical block address byte 1 */
#define LBA2_REG (HDBASE + 5) /* Logical block address byte 2 */
#define LBA3_REG (HDBASE + 6) /* Logical block address byte 3 */
#define LBA_MAGIC 0xe0 /* Magic cookie for LBA access */
#define CSR_REG (HDBASE + 7) /* status (r), command (w) */
#define HF_REG (HDBASE + 0x206) /* Usually 0x3F6 */
#define DISABLE_ATINTR 0x02
/*
* Error from AUX_REG (r)
*/
#define DAM_ERR BIT(0) /* data address mark not found */
#define TR0_ERR BIT(1) /* track 000 not found */
#define ABT_ERR BIT(2) /* aborted command */
#define ID_ERR BIT(4) /* id not found */
#define ECC_ERR BIT(6) /* data ecc error */
#define BADBLK_ERR BIT(7) /* bad block detect */
/*
* Status from CSR_REG (r)
*/
#define ERR_ST BIT(0) /* error occurred */
#define INDEX_ST BIT(1) /* index pulse */
#define SOFT_ST BIT(2) /* soft (corrected) ECC error */
#define DRQ_ST BIT(3) /* data request */
#define SKC_ST BIT(4) /* seek complete */
#define WFLT_ST BIT(5) /* improper drive operation */
#define RDY_ST BIT(6) /* drive is ready */
#define BSY_ST BIT(7) /* controller is busy */
/*
* Commands to CSR_REG (w)
*/
#define RESTORE(rate) (0x10 +(rate)) /* X */
#define SEEK(rate) (0x70 +(rate)) /* X */
#define READ_CMD (0x20) /* X */
#define WRITE_CMD (0x30) /* X */
#define FORMAT_CMD (0x50) /* X */
#define VERIFY_CMD (0x40) /* X */
#define DIAGNOSE_CMD (0x90) /* X */
#define SETPARM_CMD (0x91) /* X */
#define IDDRIVE_CMD (0xEC)
/*
* Device States.
*/
typedef enum {
SIDLE = 0,
SRESET,
SRETRY,
SREAD,
SWRITE
} at_state_t;
/*
* Set up to report a timeout.
*/
static int report_scheduled;
static int report_drv;
/***********************************************************************
* Forward referenced local functions.
*/
LOCAL void atreset __PROTO((void));
LOCAL int atdequeue __PROTO((void));
LOCAL void atstart __PROTO((void));
LOCAL int aterror __PROTO((void));
LOCAL void atrecov __PROTO((void));
#define NOTBUSY() ((inb (ATSREG) & BSY_ST) == 0)
#define ATBSYW(u) (NOTBUSY () ? 1 : myatbsyw (u))
#define DATAREQUESTED() ((inb (ATSREG) & DRQ_ST) != 0)
#define ATDRQ() (DATAREQUESTED () ? 1 : atdrq ())
/*** To Do *************************************************************
* We should change the way that we calculate the indexs in the
* structures for access data about individual partitions. That would
* make this a lot less confusing.
*/
#define partn(dev) ((minor(dev) % (N_ATDRV * NPARTN)) + \
((minor(dev) & SDEV) ? (N_ATDRV * NPARTN) : 0))
#define partnbase(p) (pparm[p]->p_base)
#define partnsize(p) (pparm[p]->p_size)
#define drv(dev) ((minor(dev) & SDEV) ? (minor(dev) % N_ATDRV) : \
(minor(dev) / NPARTN))
extern typed_space boot_gift;
extern short at_drive_ct;
extern int at_nsecmax;
/*
* ATSECS is number of seconds to wait for an expected interrupt.
* ATSREG needs to be 3F6 for most new IDE drives; needs to be
* 1F7 for Perstor controllers and some old IDE drives.
* Either value works with most drives.
* atparm - drive parameters. If initialized zero, try to use ROM values.
*/
extern unsigned ATSECS;
extern unsigned ATSREG;
extern struct hdparm_s atparm [];
/*
* Partition Parameters - copied from disk.
*
* There are N_ATDRV * NPARTN positions for the user partitions,
* plus N_ATDRV additional partitions to span each drive.
*
* Aligning partitions on cylinder boundaries:
* Optimal partition size: 2 * 3 * 4 * 5 * 7 * 17 = 14280 blocks
* Acceptable partition size: 3 * 4 * 5 * 7 * 17 = 7140 blocks
*/
static struct fdisk_s pparm [N_ATDRV * NPARTN + N_ATDRV];
/*
* Per disk controller data.
* Only one controller; no more, no less.
*/
static struct at {
buf_t *at_actf; /* Link to first */
buf_t *at_actl; /* Link to last */
daddr_t at_bno; /* Block # on disk */
int at_bufcnt; /* Block count */
unsigned at_drv;
unsigned at_partn;
unsigned char at_dtype [N_ATDRV]; /* drive type, 0 if unused */
unsigned char at_tries;
at_state_t at_state;
int at_use_BIOS_parms[N_ATDRV]; /* Non-zero if using BIOS parms */
} at;
static char timeout_msg [] = "at%d: TO\n";
ide_info_t ide_drive_info[N_ATDRV]; /* info from drive itself */
#if __USE_PROTO__
static void
at_byte_order_copy(unsigned char *dest, unsigned char *src, int n)
#else
static void
at_byte_order_copy(dest, src, n)
unsigned char *dest;
unsigned char *src;
int n;
#endif /* __USE_PROTO__ */
{
while (n) {
*dest = *(src + 1);
*(dest + 1) = *src;
dest += 2;
src += 2;
n -= 2;
}
*dest = '\0';
}
#if __USE_PROTO__
LOCAL int notBusy (void)
#else
LOCAL int
notBusy ()
#endif
{
return NOTBUSY ();
}
#if __USE_PROTO__
LOCAL void _report_timeout (void)
#else
LOCAL void
_report_timeout ()
#endif
{
cmn_err(CE_CONT, timeout_msg, report_drv);
report_scheduled = 0;
}
#if __USE_PROTO__
LOCAL void report_timeout (int unit)
#else
LOCAL void
report_timeout (unit)
int unit;
#endif
{
short s = sphi ();
if (report_scheduled == 0) {
report_scheduled = 1;
spl (s);
report_drv = unit;
defer ((__DEFERRED_FN_PTR)_report_timeout, 0);
} else
spl (s);
}
/*
* Wait while controller is busy.
*
* Return 0 if timeout, nonzero if not busy.
*/
#if __USE_PROTO__
LOCAL int myatbsyw (int unit)
#else
LOCAL int
myatbsyw (unit)
int unit;
#endif
{
if (busyWait (notBusy, ATSECS * HZ))
return 1;
report_timeout (unit);
return 0;
}
#if __USE_PROTO__
int notReadyComp (void)
#else
int
notReadyComp ()
#endif
{
return ((inb(ATSREG) & (BSY_ST | RDY_ST | SKC_ST)) == (RDY_ST | SKC_ST));
}
#if __USE_PROTO__
int wait_for_ready_comp(int a)
#else
int
wait_for_ready_comp (a)
int a;
#endif
{
if ((inb(ATSREG) & (BSY_ST | RDY_ST | SKC_ST)) == (RDY_ST | SKC_ST))
return 0;
if (busyWait(notReadyComp, ATSECS * HZ))
return 0;
report_timeout(a);
return -1;
}
#if __USE_PROTO__
LOCAL int dataRequested (void)
#else
LOCAL int
dataRequested ()
#endif
{
return DATAREQUESTED ();
}
/*
* Wait for controller to initiate request.
*
* Return 0 if timeout, 1 if data requested.
*/
#if __USE_PROTO__
LOCAL int atdrq (void)
#else
LOCAL int
atdrq ()
#endif
{
if (busyWait (dataRequested, ATSECS * HZ))
return 1;
report_timeout (at.at_drv);
return 0;
}
#if __USE_PROTO__
int at_get_drive_id(int dr)
#else
int
at_get_drive_id(dr)
int dr;
#endif
{
int s;
unsigned char status;
ide_info_t *dr_info = &ide_drive_info[dr];
s = sphi();
/* Default to using BIOS params in case of failure */
at.at_use_BIOS_parms[dr] = 1;
/* Wait for drive to not be busy */
ATBSYW(dr);
/* Select the proper drive */
outb(HDRV_REG, dr ? 0xb0 : 0xa0);
/* Wait for drive to be selected */
if (wait_for_ready_comp(dr)) {
cmn_err(CE_WARN, "at%dx Failure selecting drive", dr);
spl(s);
atreset();
return -1;
}
inb(CSR_REG); /* Clear the interrupt this generated */
outb(CSR_REG, IDDRIVE_CMD); /* Send ID request command */
ATBSYW(dr);
status = inb(CSR_REG);
if (status & ERR_ST) {
spl(s);
atreset();
return -1;
}
if (ATDRQ() == 0) {
cmn_err(CE_WARN, "at%dx Failure reading status", dr);
spl(s);
atreset();
return -1;
}
repinsw(DATA_REG, (ushort_t *)dr_info, 256);
/* Drive info request was successful, so we use these params instead */
at.at_use_BIOS_parms[dr] = 0;
spl(s);
atreset();
return 0;
}
/*
* void
* atload () - load routine.
*
* Action: The controller is reset and the interrupt vector is grabbed.
* The drive characteristics are set up at this time.
*/
#if __USE_PROTO__
LOCAL void atload(void)
#else
LOCAL void
atload ()
#endif
{
size_t ptnsize;
unsigned int u;
struct hdparm_s * dp;
struct { unsigned short off, seg; } p;
if (at_drive_ct <= 0)
return;
/* Flag drives 0, 1 as present or not. */
at.at_dtype [0] = 1;
at.at_dtype [1] = at_drive_ct > 1 ? 1 : 0;
#if 0
/* hex dump boot gift */
{
int bgi;
unsigned char * bgp = (char *)& boot_gift;
cmn_err(CE_CONT, "& boot_gift = %lx", & boot_gift);
for (bgi = 0; bgi < 80; bgi ++) {
cmn_err(CE_CONT, " %x", (* bgp ++));
}
}
#endif
/*
* Obtain Drive Characteristics.
*/
for (u = 0, dp = atparm; u < at_drive_ct; ++ dp, ++ u) {
struct hdparm_s int_dp;
unsigned short ncyl = _CHAR2_TO_USHORT (dp->ncyl);
if (ncyl == 0) {
/*
* Not patched.
*
* If tertiary boot sent us parameters,
* Use "fifo" routines to fetch them.
* This only gives us ncyl, nhead, and nspt.
* Make educated guesses for other parameters:
* Set landc to ncyl, wpcc to -1.
* Set ctrl to 0 or 8 depending on head count.
*
* Follow INT 0x41/46 to get drive static BIOS drive
* parameters, if any.
*
* If there were no parameters from tertiary boot,
* or if INT 0x4? nhead and nspt match tboot parms,
* use "INT" parameters (will give better match on
* wpcc, landc, and ctrl fields, which tboot can't
* give us).
*/
FIFO * ffp;
typed_space * tp;
int found, parm_int;
if (F_NULL != (ffp = fifo_open (& boot_gift, 0))) {
for (found = 0; ! found && (tp = fifo_read (ffp)); ) {
BIOS_DISK * bdp = (BIOS_DISK *)tp->ts_data;
if ((T_BIOS_DISK == tp->ts_type) && (u == bdp->dp_drive) ) {
found = 1;
_NUM_TO_CHAR2(dp->ncyl, bdp->dp_cylinders);
dp->nhead = bdp->dp_heads;
dp->nspt = bdp->dp_sectors;
_NUM_TO_CHAR2(dp->wpcc, 0xffff);
_NUM_TO_CHAR2(dp->landc, bdp->dp_cylinders);
if (dp->nhead > 8)
dp->ctrl |= 8;
}
}
fifo_close (ffp);
}
if (u == 0)
parm_int = 0x41;
else /* (u == 1) */
parm_int = 0x46;
pxcopy ((paddr_t)(parm_int * 4),
(__caddr_t)(& p), sizeof p, SEL_386_KD);
pxcopy ((paddr_t)(p.seg <<4L) + p.off,
(__caddr_t)(& int_dp), sizeof (int_dp), SEL_386_KD);
if (! found || (dp->nhead == int_dp.nhead && dp->nspt == int_dp.nspt)) {
* dp = int_dp;
cmn_err(CE_CONT, "Using INT 0x%x", parm_int);
} else
cmn_err(CE_CONT, "Using INT 0x13(08)");
} else {
cmn_err(CE_CONT, "Using patched");
/*
* Avoid incomplete patching.
*/
if (at.at_dtype [u] == 0)
at.at_dtype [u] = 1;
if (dp->nspt == 0)
dp->nspt = 17;
#if FORCE_CTRL_8
if (dp->nhead > 8)
dp->ctrl |= 8;
#endif
}
#if VERBOSE > 0
cmn_err(CE_CONT, " drive %d parameters\n", u);
cmn_err(CE_CONT,
"at%d: ncyl=%d nhead=%d wpcc=%d eccl=%d ctrl=%d landc=%d "
"nspt=%d\n", u, _CHAR2_TO_USHORT (dp->ncyl), dp->nhead,
_CHAR2_TO_USHORT (dp->wpcc), dp->eccl, dp->ctrl,
_CHAR2_TO_USHORT (dp->landc), dp->nspt);
#endif
}
/*
* Initialize drive size and set access method if it hasn't
* been set already.
*/
for (u = 0, dp = atparm; u < at_drive_ct; ++ dp, ++ u) {
unsigned char tmp_model[41];
unsigned char tmp_rev[9];
unsigned char tmp_serno[21];
ide_info_t *ti;
if (at.at_dtype [u] == 0)
continue;
ptnsize = (long) _CHAR2_TO_USHORT(dp->ncyl) * dp->nhead * dp->nspt;
pparm [N_ATDRV * NPARTN + u].p_size = ptnsize;
/* Get the drive id info if possible */
if (at_get_drive_id(u) == 0) {
ti = &ide_drive_info[u];
at_byte_order_copy(tmp_model, (unsigned char *)ti->ii_modelnum, 40);
at_byte_order_copy(tmp_rev, (unsigned char *)ti->ii_firmrev, 8);
at_byte_order_copy(tmp_serno, (unsigned char *)ti->ii_serialnum, 20);
cmn_err(CE_CONT, "at%d: Model %s\n", u, tmp_model);
cmn_err(CE_CONT, "at%d: Firmware Revision: %s Ser. No.: %s\n",
u, tmp_rev, tmp_serno);
/* Override BIOS parameters if IDE drive info available, when
computing partition size. */
ptnsize = (long) ti->ii_cyl * ti->ii_heads * ti->ii_spt;
pparm [N_ATDRV * NPARTN + u].p_size = ptnsize;
} else {
cmn_err(CE_CONT, "at%d: MFM/RLL/ESDI or non-identifiable IDE", u);
}
}
/*
* Initialize Drive Controller.
*/
atreset ();
}
/*
* void
* atunload () - unload routine.
*/
#if __USE_PROTO__
LOCAL void atunload(void)
#else
LOCAL void
atunload ()
#endif
{
}
/*
* void
* atreset () -- reset hard disk controller, define drive characteristics.
*/
#if __USE_PROTO__
LOCAL void atreset(void)
#else
LOCAL void
atreset ()
#endif
{
int u;
struct hdparm_s *dp;
int s;
s = sphi();
at.at_state = SRESET;
outb (HF_REG, 4); /* Reset the Controller */
busyWait2(NULL, 12); /* Wait for minimum of 4.8 usec */
outb (HF_REG, atparm [0].ctrl & 0x0F);
ATBSYW (0);
if (inb (AUX_REG) != 0x01) {
/*
* Some IDE drives always timeout on initial reset.
* So don't report first timeout.
*/
static one_bad;
if (one_bad)
cmn_err(CE_NOTE, "at: hd controller reset timeout\n");
else
one_bad = 1;
}
/*
* Initialize drive parameters.
*/
for (u = 0, dp = atparm; u < at_drive_ct; ++ dp, ++ u) {
ide_info_t *tmp_ide_info;
if (at.at_dtype [u] == 0)
continue;
ATBSYW (u);
/*
* Set drive characteristics. The problem
* is that with an EDPT drive we are supposed
* to be using the IDE level CHS info and not
* the bios info. Whenever possible, we use
* what the drive reports back to use over the BIOS info.
*/
if (at.at_use_BIOS_parms[u] == 0) { /* Use IDE params from drive */
tmp_ide_info = &ide_drive_info[u];
outb (HF_REG, dp->ctrl);
outb (HDRV_REG, 0xA0 + (u << 4) + tmp_ide_info->ii_heads - 1);
outb (AUX_REG, _CHAR2_TO_USHORT (dp->wpcc) / 4);
outb (NSEC_REG, tmp_ide_info->ii_spt);
outb (SEC_REG, 0x01);
outb (LCYL_REG, tmp_ide_info->ii_cyl & 0xFF);
outb (HCYL_REG, tmp_ide_info->ii_cyl >> 8);
outb (CSR_REG, SETPARM_CMD);
ATBSYW (u); /* Wait blindly */
inb(CSR_REG); /* Clear the interrupt this generated */
} else { /* Use the BIOS params */
outb (HF_REG, dp->ctrl);
outb (HDRV_REG, 0xA0 + (u << 4) + dp->nhead - 1);
outb (AUX_REG, _CHAR2_TO_USHORT (dp->wpcc) / 4);
outb (NSEC_REG, dp->nspt);
outb (SEC_REG, 0x01);
outb (LCYL_REG, dp->ncyl [0]);
outb (HCYL_REG, dp->ncyl [1]);
outb (CSR_REG, SETPARM_CMD);
ATBSYW (u); /* Wait blindly */
inb(CSR_REG); /* Clear the interrupt this generated */
}
/*
* Restore heads.
*/
outb (CSR_REG, RESTORE (0));
ATBSYW (u); /* Wait blindly */
inb(CSR_REG); /* Clear the interrupt this generated */
}
outb(HF_REG, dp->ctrl);
at.at_state = SIDLE;
spl(s);
}
/*
* void
* atopen (dev, mode)
* dev_t dev;
* int mode;
*
* Input: dev = disk device to be opened.
* mode = access mode [IPR, IPW, IPR + IPW].
*
* Action: Validate the minor device.
* Update the paritition table if necessary.
*/
#if __USE_PROTO__
LOCAL void atopen(dev_t dev /* , int mode */)
#else
LOCAL void
atopen (dev /* , mode */)
dev_t dev;
#endif
{
int d; /* drive */
int p; /* partition */
p = minor (dev) % (N_ATDRV * NPARTN);
if (minor (dev) & SDEV) {
d = minor (dev) % N_ATDRV;
p += N_ATDRV * NPARTN;
} else
d = minor (dev) / NPARTN;
if (d >= N_ATDRV || at.at_dtype [d] == 0) {
cmn_err(CE_WARN, "atopen: drive %d not present ", d);
set_user_error (ENXIO);
return;
}
if (minor (dev) & SDEV)
return;
/*
* If partition not defined read partition characteristics.
*/
if (pparm [p].p_size == 0)
fdisk (makedev (major (dev), SDEV + d), & pparm [d * NPARTN]);
/*
* Ensure partition lies within drive boundaries and is non-zero size.
*/
if (pparm [p].p_base + pparm [p].p_size >
pparm [d + N_ATDRV * NPARTN].p_size) {
cmn_err(CE_WARN, "atopen: p_size too big ");
set_user_error (EINVAL);
} else if (pparm [p].p_size == 0) {
cmn_err(CE_WARN, "atopen: p_size zero ");
set_user_error (ENODEV);
}
}
/*
* void
* atread (dev, iop) - write a block to the raw disk
* dev_t dev;
* IO * iop;
*
* Input: dev = disk device to be written to.
* iop = pointer to source I/O structure.
*
* Action: Invoke the common raw I/O processing code.
*/
#if __USE_PROTO__
LOCAL void atread(dev_t dev, IO *iop)
#else
LOCAL void
atread (dev, iop)
dev_t dev;
IO * iop;
#endif
{
ioreq (NULL, iop, dev, BREAD, BFRAW | BFBLK | BFIOC);
}
/*
* void
* atwrite (dev, iop) - write a block to the raw disk
* dev_t dev;
* IO * iop;
*
* Input: dev = disk device to be written to.
* iop = pointer to source I/O structure.
*
* Action: Invoke the common raw I/O processing code.
*/
#if __USE_PROTO__
LOCAL void atwrite(dev_t dev, IO *iop)
#else
LOCAL void
atwrite (dev, iop)
dev_t dev;
IO * iop;
#endif
{
ioreq (NULL, iop, dev, BWRITE, BFRAW | BFBLK | BFIOC);
}
/*
* void
* atioctl (dev, cmd, arg)
* dev_t dev;
* int cmd;
* char * vec;
*
* Input: dev = disk device to be operated on.
* cmd = input / output request to be performed.
* vec = (pointer to) optional argument.
*
* Action: Validate the minor device.
* Update the paritition table if necessary.
*/
#if __USE_PROTO__
LOCAL void atioctl(dev_t dev, int cmd, char * vec)
#else
LOCAL void
atioctl (dev, cmd, vec)
dev_t dev;
int cmd;
char *vec;
#endif /* __USE_PROTO__ */
{
int d;
/*
* Identify drive number.
*/
if (minor (dev) & SDEV)
d = minor (dev) % N_ATDRV;
else
d = minor (dev) / NPARTN;
/*
* Identify input / output request.
*/
switch (cmd) {
case HDGETA:
/*
* Get hard disk attributes.
*/
kucopy (atparm + d, vec, sizeof (atparm [0]));
break;
case HDGETIDEINFO:
/*
* Get info from an IDE drive
*/
if (at.at_use_BIOS_parms[d]) {
set_user_error(ENXIO);
}
else
kucopy(&ide_drive_info[d], vec, sizeof(ide_info_t));
break;
case HDSETA:
/* Set hard disk attributes. */
ukcopy (vec, atparm + d, sizeof (atparm [0]));
at.at_dtype [d] = 1; /* set drive type nonzero */
pparm [N_ATDRV * NPARTN + d].p_size =
(long) _CHAR2_TO_USHORT (atparm [d].ncyl) *
atparm [d].nhead * atparm [d].nspt;
atreset ();
break;
default:
set_user_error (EINVAL);
break;
}
}
/*
* void
* atwatch () - guard against lost interrupt
*
* Action: If drvl [AT_MAJOR] is greater than zero, decrement it.
* If it decrements to zero, simulate a hardware interrupt.
*/
#if __USE_PROTO__
LOCAL void atwatch (void)
#else
LOCAL void
atwatch ()
#endif
{
int s;
s = sphi ();
if (--drvl[AT_MAJOR].d_time <= 0) {
cmn_err(CE_WARN, "at: <Watchdog timeout>");
atreset();
atrecov();
}
spl (s);
}
/*
* void
* atblock (bp) - queue a block to the disk
*
* Input: bp = pointer to block to be queued.
*
* Action: Queue a block to the disk.
* Make sure that the transfer is within the disk partition.
*/
#if __USE_PROTO__
LOCAL void atblock (buf_t *bp)
#else
LOCAL void
atblock (bp)
buf_t * bp;
#endif
{
struct fdisk_s * pp;
int partn = minor (bp->b_dev) % (N_ATDRV * NPARTN);
int s;
bp->b_resid = bp->b_count;
if (minor (bp->b_dev) & SDEV)
partn += N_ATDRV * NPARTN;
pp = pparm + partn;
/*
* Check for read at end of partition.
*/
if (bp->b_req == BREAD && bp->b_bno == pp->p_size) {
bdone (bp);
return;
}
/*
* Range check disk region.
*/
if (bp->b_bno + (bp->b_count / BSIZE) > pp->p_size ||
bp->b_count % BSIZE != 0 || bp->b_count == 0) {
bp->b_flag |= BFERR;
bdone (bp);
return;
}
s = sphi ();
bp->b_actf = NULL;
if (at.at_actf == NULL)
at.at_actf = bp;
else
at.at_actl->b_actf = bp;
at.at_actl = bp;
spl (s);
if (at.at_state == SIDLE && atdequeue())
atstart ();
}
/*
* int
* atdequeue () - obtain next disk read / write operation
*
* Action: See if there is work to do in the work queue. If so
* zero the retry counter.
*
* Return: 0 = no work.
* 1 = work to do.
*/
#if __USE_PROTO__
LOCAL int atdequeue (void)
#else
LOCAL int
atdequeue ()
#endif
{
if (!at. at_actf)
return 0;
else {
at.at_tries = 0;
return 1;
}
} /* atdequeue() */
#if __USE_PROTO__
LOCAL int atsend(buf_t *bp)
#else
LOCAL int
atsend(bp)
buf_t *bp;
#endif
{
if (!bp)
cmn_err(CE_PANIC, "at: <atsend: no buffer>");
if (ATDRQ () == 0) {
cmn_err(CE_WARN, "at: <Failure starting write>");
atreset();
return 0;
}
else {
repoutsw(DATA_REG,
(ushort_t *) (bp->b_vaddr + bp->b_count - bp->b_resid),
BSIZE / 2);
return 1;
}
}
#if __USE_PROTO__
LOCAL int atrecv (buf_t *bp)
#else
LOCAL int
atrecv (bp)
buf_t *bp;
#endif /* __USE_PROTO__ */
{
if (!bp)
cmn_err(CE_PANIC, "at.c: <atrecv: with no buffer>");
if (ATDRQ() == 0) {
cmn_err(CE_WARN, "at: <Failure starting read>");
atreset();
return 0;
}
else {
repinsw(DATA_REG,
(ushort_t *) (bp->b_vaddr + bp->b_count - bp->b_resid),
BSIZE / 2);
return 1;
}
} /* atrecv() */
/*
* void
* atstart () - start or restart next disk read / write operation.
*
* Action: Initiate disk read / write operation.
*/
#if __USE_PROTO__
LOCAL void atstart (void)
#else
LOCAL void
atstart ()
#endif /* __USE_PROTO__ */
{
buf_t *bp; /* Utility buffer. */
ldiv_t addr; /* Utility address */
struct hdparm_s *dp; /* This drive's paramters */
unsigned ncyl; /* Number of cylinders */
unsigned wpcc; /* Write precomp cylinder */
int s;
/***************************************************************
* These parameters are calculated for this i/o request from
* the drive. Note that one i/o request can encompass many
* sectors.
*/
unsigned start_blkno; /* Starting physical block no. */
unsigned start_cyl; /* Starting sector */
unsigned start_head; /* Starting head */
unsigned start_sect; /* Starting sector */
unsigned nsec; /* # sectors all requests so far */
unsigned secs; /* # sectors, this buffer */
int bufcnt; /* Number of buffer, this request */
/***************************************************************
* Sanity check - Is there any work to do?
*/
if (!(bp = at. at_actf)) {
cmn_err(CE_NOTE, "at: atstart called with empty work queue\n");
drvl[AT_MAJOR]. d_time = 0;
return;
}
/***************************************************************
* Now calculate the parameters for the controller for the
* current request.
*/
at.at_partn = partn(bp->b_dev);
at.at_drv = drv(bp->b_dev);
start_blkno = bp->b_bno + ((bp->b_count - bp->b_resid) / BSIZE) +
pparm[at.at_partn].p_base;
/*
* Added by Louis to trap divide by zero problem.
* When the disk becomes full, for some reason one
* of the ldiv's below causes a kernel panic with
* a trap on divide by zero. Unless the atparm
* struct is being corrupted, at.at_drv may be wrong
* if given a negative drive number, or one too large.
* Assuming that N_ATDRV does not change, this means there
* is a sign-extension problem of some type and/or
* there is a plain-old-hosed inode on the system displaying
* this behavior, since bp->b_dev in the system call
* that paniced got the b_dev value from the inode
* i_rdev field. Also, sign extension is possible, since
* minor(a) is just #def'ed as (a & 0xFF).....
*/
ASSERT(at.at_drv >= 0 && at.at_drv <= 1);
ASSERT(at.at_partn >= 0);
/***************************************************************
* Generate a pointer to the disk drive geometry table.
*/
dp = &(atparm[at.at_drv]);
if (at.at_use_BIOS_parms[at.at_drv]) {
addr = ldiv (start_blkno, dp->nspt);
start_sect = addr.rem + 1;
addr = ldiv (addr.quot, dp->nhead);
start_cyl = addr.quot;
start_head = addr.rem;
} else { /* Use IDE params */
addr = ldiv(start_blkno, ide_drive_info[at.at_drv].ii_spt);
start_sect = addr.rem + 1;
addr = ldiv(addr.quot, ide_drive_info[at.at_drv].ii_heads);
start_cyl = addr.quot;
start_head = addr.rem;
}
/*
* NIGEL: It is unclear why, but IDE writes appear to always blow a
* revolution no matter what, even though reads appear to work quite
* comfortably. It may be that this is a problem caused by IDE drives
* trying to maintain the synchronous semantics of the write, and/or
* because we are actually not making the read time either but the
* slack is taken up by track-buffering.
*
* Either way, we gain a vast improvement in throughput for writes and
* a modest gain for reads by looking ahead in the request chain and
* coalescing separate requests to consecutive blocks into a single
* multi-sector request (as far as the interface is concerned).
*/
nsec = secs = bp->b_resid / BSIZE;
bufcnt = 1;
while (bp->b_actf != NULL && bp->b_actf->b_bno == bp->b_bno + secs &&
bp->b_actf->b_req == bp->b_req && bp->b_actf->b_dev == bp->b_dev) {
bp = bp->b_actf;
/*******************************************************
* The sector count register on the controller is
* one byte long. Make sure that the number of sectors
* to read will fit. Notice that a 0 in this register
* means read 256 sectors.
*/
if (nsec + (secs = bp->b_resid / BSIZE) > at_nsecmax)
break;
nsec += secs;
++bufcnt;
}
at.at_bufcnt = bufcnt;
s = sphi();
ATBSYW(at.at_drv);
dp = atparm + at.at_drv;
wpcc = (unsigned) _CHAR2_TO_USHORT(dp->wpcc);
ncyl = (unsigned) _CHAR2_TO_USHORT(dp->ncyl);
outb (HF_REG, dp->ctrl);
/*
* If the write precompensation set for the drive is invalid
* then don't set the write precompensation register. According
* to "The Undocumented PC" as this function is no longer needed
* by PC drives so many controllers have usurped this function.
* Don't use it if we don't have to. We use the BIOS params for
* write precomp so the user has a way to force it off (by setting
* it invalid).
*/
if ((unsigned) wpcc < (unsigned) ncyl)
outb (AUX_REG, wpcc / 4);
outb (SEC_REG, start_sect);
outb (LCYL_REG, start_cyl);
outb (HCYL_REG, start_cyl >> 8);
outb (HDRV_REG, (CHS_MAGIC |
(at.at_drv << 4) |
(start_head & 0x0f)));
outb (NSEC_REG, nsec);
if (at.at_actf->b_req == BREAD) {
at.at_state = SREAD;
outb (CSR_REG, READ_CMD);
}
else {
at.at_state = SWRITE;
outb (CSR_REG, WRITE_CMD);
if (!atsend (at.at_actf))
atrecov();
}
drvl [AT_MAJOR].d_time = ATSECS;
spl(s);
} /* atstart() */
/*
* int
* aterror ()
*
* Action: Check for drive error.
* If found, increment error count and report it.
*
* Return: 0 = No error found.
* 1 = Error occurred.
*/
#if __USE_PROTO__
LOCAL int aterror (void)
#else
LOCAL int
aterror ()
#endif
{
int csr;
int aux;
char *errstr = NULL;
char *retrystr = NULL;
/***************************************************************
* The csr is not good for 400 ns after the busy status drops
* this routine should reflect that by waiting a little while
* before testing it.
*/
busyWait2(NULL, 2);
if ((csr = inb (ATSREG)) & (ERR_ST | WFLT_ST)) {
aux = inb (AUX_REG);
if (aux & BADBLK_ERR) {
at.at_tries = BADLIM;
}
if ((csr & RDY_ST) == 0) errstr = "Drive Not Ready";
if (csr & WFLT_ST) errstr = "Write Fault";
if (aux & DAM_ERR) errstr = "No Data Addr Mark";
if (aux & TR0_ERR) errstr = "Track 0 Not Found";
if (aux & ID_ERR) errstr = "ID Not Found";
if (aux & ECC_ERR) errstr = "Bad Data Checksum";
if (aux & ABT_ERR) errstr = "Command Aborted";
if (aux & BADBLK_ERR) errstr = "Block Flagged Bad";
if (at.at_tries < HARDLIM)
retrystr = "retrying...";
else
retrystr = "I/O failed.";
cmn_err(CE_WARN, "at: <%s> %s\n", errstr, retrystr);
return 1;
}
else
return 0;
} /* aterror() */
/*
* void
* atrecov ()
*
* Action: Attempt recovery.
*/
#if __USE_PROTO__
LOCAL void atrecov (void)
#else
LOCAL void
atrecov ()
#endif
{
buf_t *bp = at.at_actf;
struct hdparm_s *dp; /* This drive's paramters */
/***************************************************************
* These parameters are calculated for the recovery request.
*/
unsigned recov_cmd; /* Recovery command. */
unsigned recov_blkno; /* Recovery physical block no. */
int recov_cyl; /* Recovery sector */
int recov_head; /* Recovery head */
int recov_sect; /* Recovery sector */
/***************************************************************
* First, if we are here then an i/o operation failed. Bump
* the tries counter for this block.
*/
++at.at_tries;
/***************************************************************
* Next compute the block _we think_ failed for printing later.
* I still haven't eliminated the possiblity of phase errors
* in this driver.
*/
dp = &(atparm[at.at_drv]);
recov_blkno = bp->b_bno + ((bp->b_count - bp->b_resid) / BSIZE) + pparm[at.at_partn]. p_base;
recov_cyl = (recov_blkno / (dp->nspt * dp->nhead));
recov_head = ((recov_blkno - (recov_cyl * dp->nspt * dp->nhead)) / (dp->nspt));
recov_sect = (recov_blkno % (dp->nspt)) + 1;
cmn_err(CE_WARN,
"(%d,%d): <%s failed. Logical Block %u Cyl: %u Head: %lu Sect: %d >",
major(bp->b_dev),
minor(bp->b_dev),
(bp->b_req == BREAD) ? "Read" : "Write",
bp->b_bno,
recov_cyl,
recov_head,
(unsigned) recov_sect);
if (at.at_tries >= HARDLIM) {
at.at_actf = bp->b_actf;
bp->b_actf = bp->b_actl = NULL;
bp->b_flag |= BFERR;
bdone(bp);
at.at_bufcnt = 0;
drvl[AT_MAJOR].d_time = 0;
at.at_state = SIDLE;
}
else {
switch (at.at_tries) {
/*******************************************************
* At 1-2 tries seek in a cylinder.
*/
case 1:
case 2:
if (--recov_cyl < 0) recov_cyl += 2;
recov_cmd = SEEK(0);
break;
/*******************************************************
* At 3-4 tries seek out a cylinder.
*/
case 3:
case 4:
/*
* Move out 1 cylinder, then retry operation
*/
if (++ recov_cyl >= _CHAR2_TO_USHORT (dp->ncyl)) recov_cyl -= 2;
recov_cmd = SEEK(0);
break;
/*******************************************************
* At 5-6 tries seek to cylinder 0.
*/
case 5:
case 6:
recov_cyl = 0;
recov_cmd = SEEK(0);
break;
/*******************************************************
* Finally, restore the drive.
*/
default:
recov_cyl = 0;
recov_cmd = RESTORE (0);
break;
}
/*******************************************************
* Take recovery action. When this completes the state
* machine in atintr will restart the failed command
* with current information.
*/
drvl[AT_MAJOR].d_time = (recov_cmd == RESTORE (0)) ? ATSECS * 2
: ATSECS;
outb (LCYL_REG, recov_cyl);
outb (HCYL_REG, recov_cyl >> 8);
outb (HDRV_REG, (at.at_drv << 4) + 0xA0);
outb (CSR_REG, recov_cmd);
at.at_state = SRETRY;
}
} /* atrecov() */
/*
* void
* atintr () - Interrupt routine.
*
* Clear interrupt then defer actual processing.
*/
#if __USE_PROTO__
void atintr(void)
#else
void
atintr()
#endif
{
buf_t *bp;
if (!(bp = at.at_actf))
at.at_state = SIDLE;
(void) inb (CSR_REG); /* clears controller interrupt */
switch (at.at_state) {
case SIDLE:
cmn_err(CE_NOTE, "at: <Spurious interrupt>");
return;
case SRESET:
return;
case SRETRY:
at.at_state = SIDLE;
break;
case SREAD:
/*
* Check for I/O error before waiting for data.
*/
if (aterror ()) {
atrecov ();
break;
}
/*******************************************************
* Try to get the sector into the buffer. If this
* fails then try recovery steps.
*/
if (!atrecv(at.at_actf)) {
atrecov();
break;
}
/*******************************************************
* If there is no error then assume a block got
* read from the disk. If this finishes the block
* then give it back to the kernel.
*/
bp->b_resid -= BSIZE;
if (bp->b_resid == 0) {
at. at_actf = bp->b_actf;
bp->b_actf = bp->b_actl = NULL;
bdone(bp);
--at.at_bufcnt;
}
/*******************************************************
* Now, set the driver state variables to appropriately
* reflect what the driver expects to be doing.
*/
if (at.at_bufcnt > 0 && at.at_actf) {
drvl[AT_MAJOR].d_time = ATSECS * 2;
at.at_tries = 0;
}
else {
drvl[AT_MAJOR].d_time = 0;
at.at_state = SIDLE;
}
break;
case SWRITE:
/*
* Check for I/O error.
*/
if (aterror ()) {
atrecov ();
break;
}
/*******************************************************
* If there is no error then assume the block got
* written to the disk. If this finishes the block
* then give it back to the kernel.
*/
bp->b_resid -= BSIZE;
if (bp->b_resid == 0) {
at. at_actf = bp->b_actf;
bp->b_actf = bp->b_actl = NULL;
bdone(bp);
--at.at_bufcnt;
}
/*******************************************************
* Now, set the driver state variables to appropriately
* reflect what the driver expects to be doing.
*/
if (at.at_bufcnt > 0 && at.at_actf) {
drvl[AT_MAJOR].d_time = ATSECS * 2;
at.at_tries = 0;
if (!atsend (at.at_actf)) {
atrecov();
at.at_state = SRETRY;
}
}
else {
drvl[AT_MAJOR].d_time = 0;
at.at_state = SIDLE;
}
break;
} /* switch */
/***************************************************************
* If the driver is idle and there is work to be done then
*/
if (at.at_state == SIDLE && atdequeue())
atstart();
} /* atintr() */
CON atcon = {
DFBLK | DFCHR, /* Flags */
AT_MAJOR, /* Major index */
atopen, /* Open */
NULL, /* Close */
atblock, /* Block */
atread, /* Read */
atwrite, /* Write */
atioctl, /* Ioctl */
NULL, /* Powerfail */
atwatch, /* Timeout */
atload, /* Load */
atunload /* Unload */
};
/* End of file */