/* * $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 */