/* $Header: /ker/io.386/RCS/ss.c,v 2.4 93/08/19 10:39:29 nigel Exp Locker: nigel $ */ /* * Device driver for Seagate ST01/ST02 scsi host adapters. * To do: * nonzero LUN's * start new command during disconnect * rewrite as single state machine, instead of 7 of them * separate SCSI layer from host-dependent stuff * * $Log: ss.c,v $ * Revision 2.4 93/08/19 10:39:29 nigel * r83 ioctl (), corefile, new headers * * Revision 2.3 93/08/19 04:03:19 nigel * Nigel's R83 */ #include <sys/stat.h> #include <sys/errno.h> #include <sys/cmn_err.h> #include <kernel/typed.h> #include <kernel/fakeff.h> #include <sys/coherent.h> #include <sys/uproc.h> #include <sys/buf.h> #include <sys/dmac.h> #include <sys/io.h> #include <sys/sched.h> #include <sys/proc.h> #include <sys/con.h> #include <sys/devices.h> /* SCSI_MAJOR */ #include <sys/fdisk.h> #include <sys/hdioctl.h> #include <sys/scsiwork.h> /* * Debug levels. * DEBUG = 0 No debug output. * DEBUG = 1 Debug output on error only. * DEBUG = 2 Debug output on error and at other selected places. * DEBUG = 3 Print state machine trace. * DEBUG = 4 Print info xfer phases and msg_in values. */ #if (DEBUG >= 1) static int s_id; #define PR1(str) printf("%s%d ", str, s_id) #else #define PR1(str) #endif #if (DEBUG >= 2) #define PR2(str) printf(str) #else #define PR2(str) #endif #if (DEBUG >= 3) #define PR3(str) printf("%s%d ", str, s_id) #else #define PR3(str) #endif #if (DEBUG >= 4) #define PR4(str) printf("%s%d ", str, s_id) #else #define PR4(str) #endif #define SS_RAM 0x1800 /* Offset of parameter RAM */ /* Future Domain */ #define FD_CSR 0x1C00 /* Offset of control/status register */ #define FD_DAT 0x1E00 /* Offset of data port */ /* Seagate */ #define SS_CSR 0x1A00 /* Offset of control/status register */ #define SS_DAT 0x1C00 /* Offset of data port */ #define SS_RAM_LEN 128 /* ST0x has 128 bytes of RAM */ #define SS_DAT_LEN 0x400 /* Byte range mapped to data port */ #define SS_SEL_LEN 0x2000 /* Total size of memory-mapped area */ #define WC_ENABLE_SCSI 0x80 /* Write Control (WC) register bits */ #define WC_ENABLE_IRPT 0x40 #define WC_ENABLE_PRTY 0x20 #define WC_ARBITRATE 0x10 #define WC_ATTENTION 0x08 #define WC_BUSY 0x04 #define WC_SELECT 0x02 #define WC_SCSI_RESET 0x01 #define RS_ARBIT_COMPL 0x80 /* Read STATUS (RS) register bits */ #define RS_PRTY_ERROR 0x40 #define RS_SELECT 0x20 #define RS_REQUEST 0x10 #define RS_CTRL_DATA 0x08 #define RS_I_O 0x04 #define RS_MESSAGE 0x02 #define RS_BUSY 0x01 #define DEV_SCSI_ID(dev) ((dev >> 4) & 0x0007) #define DEV_LUN(dev) ((dev >> 2) & 0x0003) #define DEV_DRIVE(dev) ((dev >> 2) & 0x001F) #define DEV_PARTN(dev) (dev & 0x0003) #define DEV_SPECIAL(dev) (dev & 0x0080) #define HIPRI_RETRIES 5000 /* # of times to retry while hogging CPU */ #define LOPRI_RETRIES 5 /* # of retries with sleep between tries */ #define WHOLE_DRIVE NPARTN #define RESET_TICKS 50 /* # of clock ticks for reset settling */ #define LOAD_DELAY 30000 /* Loop counter during ssload() only */ #define BUS_FREE ((ffbyte(ss_csr) & (RS_BUSY | RS_SELECT)) == 0) #define TGT_RSEL \ ( (ffbyte (ss_csr) & (RS_SELECT | RS_I_O )) \ && (ffbyte (ss_dat) & (host_id | (1 << s_id) )) ) #define DELAY_ARB 10 /* delays units are 10 msec (clock ticks) */ #define DELAY_BDR 30 #define DELAY_BSY 20 #define DELAY_RES 50 #define DELAY_RST 40 #define MAX_AVL_COUNT 100 #define MAX_BDR_COUNT 3 #define MAX_BSY_COUNT 3 #define MAX_TRY_COUNT 10 #define INL_MAX_REQ_POLL 800000L #define WKG_MAX_REQ_POLL 20000L typedef enum { /* values for current driver state */ SST_DEQUEUE = 0, SST_BUS_DEV_RESET, SST_HIPRI_RESET, SST_LOPRI_RESET, SST_POLL_ARBITN, SST_POLL_BEGIN_IO, SST_POLL_RESELECT, SST_REQ_SENSE, SST_RESET_OFF } SST_TYPE; typedef enum { /* values for input to recovery routine */ RV_A_TIMEOUT, RV_P_TIMEOUT, RV_R_TIMEOUT, RV_BF_TIMEOUT, RV_CS_BUSY, RV_CS_CHECK } RV_TYPE; typedef struct ss { ulong capacity; ulong blocklen; ulong bno; int msg_in; int dr_watch; unchar cmdbuf [G1CMDLEN]; int cmdlen; int cmd_bytes_out; int cmdstat; BUF * bp; /* current I/O request node, or NULL */ struct fdisk_s parmp [NPARTN + 1]; SST_TYPE state; TIM tim; /* for target-specific timers */ unchar avl_count; unchar bdr_count; unchar bsy_count; unchar try_count; uint busy:1; /* 1 if command uses local buffer */ uint expired:1; /* 1 if target's timer has expired */ uint ptab_read:1; /* 1 if partition table has been read */ uint waiting:1; /* 1 if target timer is running */ } ss_type; /* functions from bufq.c */ extern int bufq_init (); extern void bufq_rlse (); extern void bufq_wr_tail (); extern BUF * bufq_rd_head (); extern BUF * bufq_rm_head (); /* functions from ssas.s */ extern void ss_get (); extern int ss_put (); static int bus_dev_reset (); /* additional support functions */ static int chk_reconn (); static void do_connect (); static void dummy_reconn (); static int far_info_xfer (); static int host_ident (); static int init_call (); static void init_pointers (); static int inquiry (); static int local_info_xfer (); static int mode_sense (); static void next_req (); static void nonpolled (); static int read_cap (); static void recover (); static int req_sense (); static int rsel_handshake (); static void ssdelay (); static void ss_finished (); static void ss_mach (); static void set_timeout (); static int ssinit (); static void ssintr (); static int start_arb (); static void stop_timeout (); static void tbparms (); static unchar xpmod (); extern short at_drive_ct; /* set by atcount () before any load routines run */ /* * Configurable variables * * In the low byte of NSDRIVE, bit n is 1 if SCSI ID n is an installed target. * The high byte indicates which type of host adapter: * 00 - ST01/ ST02 * 80 - TMC-845/850/860/875/885 * 40 - TMC-840/841/880/881 */ extern unsigned int NSDRIVE; extern unsigned int SS_INT; /* ST0 [12] use either IRQ3 or IRQ5 */ extern unsigned int SS_BASE; /* Segment addr of ST0x comm area */ /* ncyl, nhead, nspt */ extern _drv_parm_t _sd_drv_parm []; static paddr_t ss_base; /* physical address of ST0x comm area */ static faddr_t ss_fp; /* (far *) to ST0x comm area */ static faddr_t ss_ram; /* (far *) to parameter RAM */ static faddr_t ss_csr; /* (far *) to control / status */ static faddr_t ss_dat; /* (far *) to data port */ static TIM delay_tim; /* needed for calls to ssdelay () */ static int do_sst_op; /* 1 when state machine iteration continues */ static int ss_expired; /* 1 after local timeout */ static uint max_req_poll; /* this changes after initialization */ static unchar host_id; /* Host is SCSI ID #7 for Seagate, 6 for FD */ static unchar swap_status_bits; static ss_type * ss_tbl; /* points to block of "ss" structs */ static ss_type * ss [MAX_SCSI_ID]; /* * host_claimed is -1 if host is available, else it's the SCSI id of the * target that claims the host. * * host is claimed at start of any of the follwoing: * SCSI bus reset * arbitration for block I/O request * reselect * * host is released at: * end of SCSI bus reset * completion (successful or not) of block I/O request (ss_finished) * disconnect <-- temporarily disabled */ static int host_claimed; /* * ssload () - load routine. * * Action: The controller is reset and the interrupt vector is grabbed. * The drive characteristics are set up at this time. */ static void ssload () { int erf = 0; /* 1 if error occurs */ int i; int max_id = -1; int num_drives = 0; int tbnum; /* * Allocate a selector to map into ST0x memory-mapped comm area. */ ss_base = (paddr_t)((long)(unsigned) SS_BASE << 4); ss_fp = map_pv (ss_base, (fsize_t )SS_SEL_LEN); ss_ram = ss_fp + SS_RAM; /* * Primitive test of ST0x RAM. */ sfword (ss_ram, 0xA55A); sfword (ss_ram + 2, 0x3CC3); sfword (ss_ram + SS_RAM_LEN - 4, 0xA55A); sfword (ss_ram + SS_RAM_LEN - 2, 0x3CC3); if (ffword (ss_ram) != 0xA55A || /* fetch a "far" word */ ffword (ss_ram + 2) != 0x3CC3 || ffword (ss_ram + SS_RAM_LEN - 4) != 0xA55A || ffword (ss_ram + SS_RAM_LEN - 2) != 0x3CC3) { printf ("Error - host failed memory test\n"); erf = 1; } /* * Set host-dependent constants. */ switch (NSDRIVE >> 8) { case 0x00: /* ST01/ ST02 */ ss_csr = ss_fp + SS_CSR; ss_dat = ss_fp + SS_DAT; host_id = 0x80; /* host is id #7 */ break; case 0x80: /* TMC-845/850/860/875/885 */ ss_csr = ss_fp + FD_CSR; ss_dat = ss_fp + FD_DAT; host_id = 0x40; /* host is id #6 */ break; case 0x40: /* TMC-840/841/880/881 */ ss_csr = ss_fp + SS_CSR; ss_dat = ss_fp + SS_DAT; host_id = 0x40; /* host is id #6 */ swap_status_bits = 1; break; } NSDRIVE &= ~ (uint) host_id; /* * Allocate drive structs. * * Do a single call to kalloc () then put allocated pieces into * array ss. * * First allocate and clear storage. Then hook up the pointers. */ if (! erf) { for (i = 0; i < MAX_SCSI_ID; i ++) if ((NSDRIVE >> i) & 1) { max_id = i; num_drives ++; } if (num_drives == 0) { printf ("Error - ss has no valid target id's\n"); erf = 1; } else if ((ss_tbl = kalloc (num_drives * sizeof (ss_type))) == NULL) { printf ("Error - ss can't allocate structs\n"); erf = 1; } else memset (ss_tbl, 0, num_drives * sizeof (ss_type)); } if (! erf) { ss_type * foo = ss_tbl; for (i = 0; i < MAX_SCSI_ID; i ++) if ((NSDRIVE >> i) & 1) ss [i] = foo ++; } /* * Claim IRQ vector. */ setivec (SS_INT, ssintr); /* * Initialize drives we know about (i.e. in NSDRIVE bitmap). * * Part of this is getting parameters from tboot, if any. * The drive number in tboot's data block must be matched with * the SCSI id in question. Drive numbering in tboot is assumed * to start with any "at" drives (at_drive_ct counts these) * then proceed with SCSI drives in increasing id number order. */ tbnum = at_drive_ct; /* tboot drive number for first SCSI drive */ host_claimed = -1; bufq_init (max_id + 1); max_req_poll = INL_MAX_REQ_POLL; if (! erf) { for (i = 0; i < MAX_SCSI_ID; i ++) if ((NSDRIVE >> i) & 1) { tbparms (tbnum, i); /* get tboot parms */ ssinit (i); tbnum ++; } } max_req_poll = WKG_MAX_REQ_POLL; } /* * ssunload () - unload routine. */ static void ssunload () { /* * Deallocate driver heap space. */ if (ss_tbl) kfree (ss_tbl); bufq_rlse (); /* * Free the ST0x selector. */ unmap_pv (ss_fp); /* * Release IRQ vector. */ clrivec (SS_INT); } /* * ssopen () * * 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. */ static void ssopen (dev, mode) dev_t dev; { int drive, partn; struct fdisk_s * fdp; ss_type * ssp; int s_id; unchar * msg; /* * Set up local variables. */ drive = DEV_SCSI_ID (dev); partn = DEV_PARTN (dev); s_id = DEV_SCSI_ID (dev); ssp = ss [s_id]; fdp = ssp->parmp; #if (DEBUG >= 3) devmsg (dev, "ssopen"); #endif /* * LUN must be zero. * SCSI id must have corresponding 1 in NSDRIVE bitmapped variable. */ if (DEV_LUN (dev) != 0 || ((1 << drive) & NSDRIVE) == 0) { msg = "bad LUN or SCSI id"; set_user_error (ENXIO); goto bad_open; } /* * If "special" bit is set, partition field must be zero. */ if (DEV_SPECIAL (dev) && partn != 0) { msg = "bad special partition"; set_user_error (ENXIO); goto bad_open; } /* * Subscripting gimmick for partition table. */ if (dev & SDEV) partn = WHOLE_DRIVE; /* * If not accessing whole drive and the partition table has not * been read yet, try to read it now. * Do this by calling fdisk () with partition table device on the drive * that is being accessed. */ if (partn != WHOLE_DRIVE && !(ssp->ptab_read)) { int fdisk_dev; fdisk_dev = (dev | SDEV) & 0xfff0; #if (DEBUG >= 3) devmsg (fdisk_dev, "calling fdisk"); if (fdisk (fdisk_dev, fdp)) { int p; fdp [WHOLE_DRIVE].p_size = ssp->capacity; fdp [WHOLE_DRIVE].p_base = 0; printf ("fdisk () succeeded\n"); for (p = 0; p < WHOLE_DRIVE ; p ++) printf ("p =%d base =%ld size =%ld\n", p, fdp [p].p_base, fdp [p].p_size); ssp->ptab_read = 1; } else { printf ("fdisk () failed\n"); set_user_error (ENXIO); goto bad_open; } #else if (fdisk (fdisk_dev, fdp)) { fdp [WHOLE_DRIVE].p_size = ssp->capacity; fdp [WHOLE_DRIVE].p_base = 0; ssp->ptab_read = 1; } else { msg = "bad partition table"; set_user_error (ENXIO); goto bad_open; } #endif } /* * Ensure partition lies within drive boundaries and is non-zero size. */ if (partn != WHOLE_DRIVE && (fdp [partn].p_base + fdp [partn].p_size) > fdp [WHOLE_DRIVE].p_size) { msg = "partition exceeds drive capacity"; set_user_error (EINVAL); goto bad_open; } if (partn != WHOLE_DRIVE && fdp [partn].p_size == 0) { msg = "partition not found"; set_user_error (ENODEV); goto bad_open; } /* * OK to open the device. * Start watchdog timer (if not already started) for the host adapter. */ ++ drvl [SCSI_MAJOR].d_time; ++ ssp->dr_watch; return; bad_open: devmsg (dev, msg); } /* * ssclose () */ static void ssclose (dev) dev_t dev; { ss_type * ssp; int s_id; s_id = DEV_SCSI_ID (dev); ssp = ss [s_id]; /* * Decrement the number of watchdog timer requests open for host * adapter and for target. */ -- drvl [SCSI_MAJOR].d_time; -- ssp->dr_watch; #if (DEBUG >= 3) devmsg (dev, "ssclose"); #endif } /* * ssread () - read a block from the raw disk * * Input: dev = disk device to be written to. * iop = pointer to source I/O structure. * * Action: Invoke the common raw I/O processing code. */ static void ssread (dev, iop) dev_t dev; IO * iop; { ioreq (NULL, iop, dev, BREAD, BFRAW | BFBLK | BFIOC); } /* * sswrite () - write a block to the raw disk * * Input: dev = disk device to be written to. * iop = pointer to source I/O structure. * * Action: Invoke the common raw I/O processing code. */ static void sswrite (dev, iop) dev_t dev; IO * iop; { ioreq (NULL, iop, dev, BWRITE, BFRAW | BFBLK | BFIOC); } /* * ssioctl () * * Input: dev = disk device to be operated on. * cmd = input / output request to be performed. * vec = (pointer to) optional argument. */ static void ssioctl (dev, cmd, vec) dev_t dev; int cmd; char * vec; { hdparm_t hdparm; struct fdisk_s * fdp; int s_id; ss_type * ssp; s_id = DEV_SCSI_ID (dev); ssp = ss [s_id]; fdp = ssp->parmp; switch (cmd) { case HDGETA: /* * Get hard disk attributes. */ PR3("HDGETA"); fdp = ssp->parmp; * (short *) & hdparm.landc [0] = * (short *) & hdparm.ncyl [0] = _sd_drv_parm [s_id].ncyl; hdparm.nhead = _sd_drv_parm [s_id].nhead; hdparm.nspt = _sd_drv_parm [s_id].nspt; #if (DEBUG >= 3) printf ("ncyl =%d nhead =%d nspt =%d\n", hdparm.ncyl [0]+((int)hdparm.ncyl [1]<<8), (int)hdparm.nhead, (int)hdparm.nspt); #endif kucopy (& hdparm, vec, sizeof hdparm); break; case HDSETA: /* * Set hard disk attributes. */ PR3("HDSETA"); fdp = ssp->parmp; ukcopy (vec, & hdparm, sizeof hdparm); _sd_drv_parm [s_id].ncyl = * (short *) & hdparm.ncyl [0]; _sd_drv_parm [s_id].nhead = hdparm.nhead; _sd_drv_parm [s_id].nspt = hdparm.nspt; #if (DEBUG >= 3) printf ("ncyl =%d nhead =%d nspt =%d\n", hdparm.ncyl [0]+((int)hdparm.ncyl [1]<<8), (int)hdparm.nhead, (int)hdparm.nspt); #endif break; default: set_user_error (EINVAL); } } /* * ssblock () - 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. */ static void ssblock (bp) BUF * bp; { struct fdisk_s * fdp; int partition, drive, s_id; dev_t dev; ss_type * ssp; unchar * msg = NULL; /* * Set up local variables. */ dev = bp->b_dev; partition = DEV_PARTN (dev); drive = DEV_DRIVE (dev); s_id = DEV_SCSI_ID (dev); ssp = ss [s_id]; if (dev & SDEV) partition = WHOLE_DRIVE; fdp = ssp->parmp; bp->b_resid = bp->b_count; #if (DEBUG >= 2) if (bp->b_count != BSIZE) printf ("b_count =%d ", bp->b_count); #endif /* * Range check disk region. */ if (ssp->ptab_read == 0) { if (partition == WHOLE_DRIVE) { #if 0 /* Why did we only allow people to access the first block of WHOLE_DRIVE? in cases where there was not a valid partition table? */ if ((bp->b_bno != 0) || (bp->b_count != BSIZE)) { msg = "invalid request"; bp->b_flag |= BFERR; goto bad_blk; } #endif } else { msg = "no partition table"; bp->b_flag |= BFERR; goto bad_blk; } } else if (bp->b_req == BREAD && bp->b_bno == fdp [partition].p_size) { /* * Read at end of partition. * Need to return with b_resid = BSIZE to signal end of volume. */ goto bad_blk; } else if ((bp->b_bno + (bp->b_count / BSIZE)) > fdp [partition].p_size) { /* * Read past end of partition. */ msg = "partition overrun"; bp->b_flag |= BFERR; goto bad_blk; } /* * Fail if request is for zero bytes or is not even # of blocks. */ if ((bp->b_count % BSIZE) || bp->b_count == 0) { msg = "invalid byte count"; bp->b_flag |= BFERR; goto bad_blk; } /* * Operation appears valid. * Fill fields in the node and queue the request. */ bufq_wr_tail (s_id, bp); ss_mach (s_id); return; /* * Operation cannot be done. Release the kernel buffer structure. * Value of "bp->b_flag" tells caller if error occurred. */ bad_blk: if (msg) devmsg (dev, msg); bdone (bp); } /* * ssintr () - Interrupt routine. * * If we have been reselected by a recognized target device * let kernel get out of interrupt mode (defer) and do SCSI * reconnect stuff. */ static void ssintr () { int s_id; s_id = chk_reconn (); if (s_id != -1) { if (ss [s_id]->state == SST_POLL_RESELECT) defer (ss_mach, s_id); else defer (dummy_reconn, s_id); PR3("!"); } } /* * dummy_reconn () * * Somehow we are in a state where the driver software does not expect * a reconnect but a device is trying one anyway. Go thru the motions * of reconnect because not servicing a hanging reselect seems to leave * the target hung - in such a way that it fails to respond to reset * messages and to reset on the SCSI bus. */ static void dummy_reconn (s_id) int s_id; { int bus_timeout; unchar phase_type; int s; int msg_in; int cmdstat; int xfer_good = 1; PR1("DUM"); if (ss [s_id]->state == SST_POLL_RESELECT) { defer (ss_mach, s_id); return; } if (! rsel_handshake ()) return; s = sphi (); while (req_wait (& bus_timeout) && xfer_good) { phase_type = ffbyte (ss_csr) & (RS_MESSAGE | RS_I_O | RS_CTRL_DATA); switch (xpmod (phase_type)) { case XP_MSG_IN: msg_in = ffbyte (ss_dat); switch (msg_in){ case MSG_CMD_CMPLT: case MSG_DISCONNECT: sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT); break; } break; case XP_MSG_OUT: sfbyte (ss_dat, MSG_NOP); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI); break; case XP_STAT_IN: cmdstat = ffbyte (ss_dat); break; case XP_CMD_OUT: case XP_DATA_OUT: xfer_good = 0; break; case XP_DATA_IN: ffbyte (ss_dat); break; default: break; } /* endswitch */ } /* endwhile */ spl (s); } /* * sswatch () * * Invoked once per second if any devices going through this driver are open. * Poll for any reselect, in case interrupt got lost. */ static void sswatch () { int s_id; ss_type * ssp; for (s_id = 0; s_id < MAX_SCSI_ID; s_id ++) { ssp = ss [s_id]; if (ssp && ssp->dr_watch) defer (ss_mach, s_id); } /* endfor */ } /* * bus_wait () * * Wait for specified bit values to appear in Status Register. * This uses a tight loop and does not expect to be interrupted. * * Argument "flags" is a double-byte value; the high byte is ANDed with * status register contents, and the result is tested for equality with * the low byte. * * Return 1 if values wanted appeared, 0 if timeout occurred. */ static int bus_wait (flags) unsigned short flags; { int found, i; unsigned char status; found = 0; for ( i = 0; i < HIPRI_RETRIES; i ++) { status = ffbyte (ss_csr); if ((status & (flags >> 8)) == (flags & 0xff)) { found = 1; break; } } #if (DEBUG >= 1) if (! found) printf ("TO:f =%x s =%x ", flags, status); #endif return found; } /* * ssinit () * * Attempt to initialize the (unique) drive with a given SCSI id. * Assume only one drive per SCSI id, having LUN = 0. * * Return 1 if success, 0 if failure. */ static int ssinit (s_id) int s_id; { int retval = 1; unchar query_buf [MODESENSELEN]; ss_type * ssp = ss [s_id]; int dev = ((SCSI_MAJOR << 8) | 0x80 | (s_id << 4)); printf ("SCSI ID %d LUN 0\n", s_id); if (retval) if (init_call (inquiry, s_id, query_buf)) { query_buf [INQUIRYLEN] = 0; #if (debug >= 2) devmsg (dev, query_buf + 8); #endif if (query_buf [0] == 0) { retval = 1; } else devmsg (dev, "Not Direct Access Device"); } else devmsg (dev, "Inquiry Failed"); if (retval) if (init_call (read_cap, s_id, query_buf)) { retval = 1; ssp->capacity = query_buf [3] | (query_buf [2] << 8) | (((long)(query_buf [1])) << 16) | (((long)(query_buf [0])) << 24); ssp->blocklen = query_buf [7] | (query_buf [6] << 8) | (((long)(query_buf [5])) << 16) | (((long)(query_buf [4])) << 24); printf ("Capacity =%ld blocks Block length =%ld\n", ssp->capacity, ssp->blocklen); } else devmsg (dev, "Read Capacity Failed"); if (retval) if (init_call (mode_sense, s_id, query_buf)) { /* * Display physical drive parameters. */ #define FMT_PG (4 + 8 + 8 + 12) #define DDG_PG (4 + 8 + 8 + 12 + 24) unchar heads; unsigned short spt; ulong cyls; spt =((int)query_buf [FMT_PG + 10]<<8) + query_buf [FMT_PG + 11]; cyls =((int)query_buf [DDG_PG + 2]<<16) + ((int)query_buf [DDG_PG + 3]<<8) + query_buf [DDG_PG + 4]; heads = query_buf [DDG_PG + 5]; printf ("Physical: cylinders =%ld ", cyls); printf ("heads =%d ", heads); printf ("spt =%d\n", spt); if (_sd_drv_parm [s_id].ncyl == 0) { _sd_drv_parm [s_id].ncyl = cyls; _sd_drv_parm [s_id].nhead = heads; _sd_drv_parm [s_id].nspt = spt; } else { printf ("Logical: cylinders =%d ", _sd_drv_parm [s_id].ncyl); printf ("heads =%d ", _sd_drv_parm [s_id].nhead); printf ("spt =%d\n", _sd_drv_parm [s_id].nspt); } } else devmsg (dev, "Mode Sense Failed"); return retval; } /* * far_info_xfer () * * Do bus cycle information transfer phases. * This includes message in / out, command in / out, and data in / out. * * If cmdlen is nonzero, cmdbuf is an array of bytes of that length, * to be sent to the target. * * Return 1 if bus timeout did not occur, else 0. * * pseudocode: * * while (wait for REQ true or BUSY false on SCSI bus) * if (BUSY false) * break from while loop * else * switch (xfer phase = RS_CTRL_DATA | RS_I_O | RS_MESSAGE) * case XP_MSG_IN / XP_MSG_OUT /... * handle the indicated information transfer phase * endswitch * endif * endwhile */ static int far_info_xfer (s_id) int s_id; { int bus_timeout; unchar phase_type; unchar msg_in; int s; int bytes_to_send; ss_type * ssp = ss [s_id]; BUF * bp = ssp->bp; int xfer_good = 1; int xfer_count = bp->b_count - bp->b_resid; int irpts_masked; int block_done = 0; ssp->cmd_bytes_out = 0; ssp->msg_in = -1; irpts_masked = 0; while (req_wait (& bus_timeout) && xfer_good) { phase_type = ffbyte (ss_csr) & (RS_MESSAGE | RS_I_O | RS_CTRL_DATA); if (! irpts_masked) { s = sphi (); irpts_masked = 1; } switch (xpmod (phase_type)) { case XP_MSG_IN: msg_in = ffbyte (ss_dat); switch (msg_in){ case MSG_CMD_CMPLT: PR4("Mcc"); ssp->msg_in = msg_in; sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT); break; case MSG_DISCONNECT: PR4("Mdc"); ssp->msg_in = msg_in; sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT); break; case MSG_SAVE_DPTR: PR4("Msd"); break; case MSG_RSTOR_DPTR: PR4("Mrd"); break; case MSG_ABORT: PR4("Mab"); break; case MSG_DEV_RESET: PR4("Mdr"); break; case MSG_IDENTIFY: PR4("Mmi"); break; case MSG_IDENT_DC: PR4("Mmd"); break; } break; case XP_MSG_OUT: PR4("MO"); /* * This case shouldn't happen. We weren't * asserting ATTENTION. Abort the bus cycle. */ sfbyte (ss_dat, MSG_NOP); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI); break; case XP_STAT_IN: PR4("SI"); ssp->cmdstat = ffbyte (ss_dat); break; case XP_CMD_OUT: /* * Ship out command bytes. * Reset SCSI bus if too many command bytes are wanted. */ bytes_to_send = ssp->cmdlen - ssp->cmd_bytes_out; if (bytes_to_send > 0) { sfbyte (ss_dat, ssp->cmdbuf [ssp->cmd_bytes_out ++]); /* * If just sent last byte, allow interrupts. */ if (bytes_to_send == 1) { PR4("CO"); if (bp->b_req == BREAD) { if (irpts_masked) { spl (s); irpts_masked = 0; } } } } else { /* This case should not happen. */ xfer_good = 0; } break; case XP_DATA_IN: /* * If caller's buffer has room, keep incoming * data byte. */ if (block_done) { xfer_good = 0; PR1("Data in overrun"); } else if (bp->b_req != BREAD) { xfer_good = 0; } else { block_done = 1; if (BSIZE != xpcopy (ss_dat, bp->b_paddr + xfer_count, BSIZE, SEL_386_KD | SEG_VIRT)) { devmsg (bp->b_dev, "XP_DATA_IN: ss_dat: %x, bp->bpaddr: %x, xfer_count: %x\n", ss_dat, bp->b_paddr, xfer_count); break; } } break; case XP_DATA_OUT: /* * Copy output buffer bytes to data register. */ if (block_done) { xfer_good = 0; PR1("Data out overrun"); } else if (bp->b_req != BWRITE) { xfer_good = 0; } else { block_done = 1; if (BSIZE != pxcopy (bp->b_paddr + xfer_count, ss_dat, BSIZE, SEL_386_KD | SEG_VIRT)) { devmsg (bp->b_dev, "XP_DATA_OUT: bp->b_paddr: %x, xfer_count: %x, ss_dat: %x\n", bp->b_paddr, xfer_count, ss_dat); break; } if (irpts_masked) { spl (s); irpts_masked = 0; } } break; default: break; } /* endswitch */ } if (irpts_masked) spl (s); #if (DEBUG >= 1) switch (ssp->cmdstat) { case -1: if (msg_in != MSG_DISCONNECT) printf ("CS-", ssp->cmdstat); break; case CS_GOOD: break; case CS_CHECK: printf ("CSK", ssp->cmdstat); break; case CS_BUSY: printf ("CSY", ssp->cmdstat); break; case CS_RESERVED: default: printf ("CS%x", ssp->cmdstat); } #endif return (bus_timeout) ? 0 : 1 ; } /* * req_wait () * * This routine is called at the start of each information transfer * phase and after the last such phase. * * It returns 1 if REQ is asserted on the SCSI bus, meaning another phase * may begin, and 0 otherwise. A REQ signal will not be seen if the function * times out or if BUSY drops. A value of 1 is written to the pointer argument * if timeout occurred, else 0 is written. */ static int req_wait (to_ptr) int * to_ptr; { int req_found; unsigned char status; ulong poll_ct; int s; s = splo (); * to_ptr = 1; req_found = 0; for (poll_ct = 0L; poll_ct < max_req_poll; poll_ct ++) { status = ffbyte (ss_csr); if (status & RS_REQUEST) { req_found = 1; * to_ptr = 0; break; } else if ((status & RS_BUSY) == 0) { * to_ptr = 0; break; } } #if (DEBUG >= 1) if (* to_ptr) { printf ("TX: s =%x ", status); } #endif spl (s); return req_found; } /* * req_sense () * * Request Sense for a device. The main reason for doing this is to * clear a standing Command Status of Device Check. * * Full results are discarded. Return 1 if Device returns No Sense or * or Unit Attention. Else return 0. * */ static int req_sense (s_id) int s_id; { unchar sense_buf [SENSELEN]; unchar cmdbuf [G0CMDLEN]; int ret = 0; cmdbuf [0] = ScmdREQUESTSENSE; cmdbuf [1] = 0; cmdbuf [2] = 0; cmdbuf [3] = 0; cmdbuf [4] = SENSELEN; cmdbuf [5] = 0; #if (DEBUG >= 2) {int i; for (i = 0; i < SENSELEN; i ++) sense_buf [i]= 0;} #endif PR2("rqs:"); if (! start_arb ()) { PR2("NO arb"); #if (DEBUG >= 2) printf ("status =%x ", ffbyte (ss_csr)); #endif goto rqs_done; } if (! host_ident (s_id, 0)) { PR2("NO host ident"); #if (DEBUG >= 2) printf ("status =%x ", ffbyte (ss_csr)); #endif goto rqs_done; } if (! local_info_xfer (cmdbuf, G0CMDLEN, sense_buf, SENSELEN, NULL, 0)) { PR2("NO local xfer"); goto rqs_done; } else { /* * Return 1 if drive responded with any of these sense keys: * 0x00 No Sense * 0x06 Unit Attention * 0x0B Aborted Command * In any of the above cases, a retry will likely succeed * without Buse Device Reset or SCSI Bus Reset. */ switch (sense_buf [2]) { case 0x00: case 0x06: case 0x0B: ret = 1; break; } /* endswitch */ } rqs_done: #if (DEBUG >= 2) { int i; for (i = 0; i < SENSELEN;i ++) printf ("%x ", sense_buf [i]); printf ("\n"); } #endif return ret; } /* * inquiry () * * Inquiry command for a device. * Find out if device is direct access, removable, etc. * * Put result of inquiry into supplied buffer. * Return 1 if command succeeds, else 0. */ static int inquiry (s_id, buf) int s_id; unchar * buf; { int ret = 0; unchar cmdbuf [G0CMDLEN]; cmdbuf [0] = ScmdINQUIRY; cmdbuf [1] = 0; cmdbuf [2] = 0; cmdbuf [3] = 0; cmdbuf [4] = INQUIRYLEN; cmdbuf [5] = 0; if (start_arb () && host_ident (s_id, 0) && local_info_xfer (cmdbuf, G0CMDLEN, buf, INQUIRYLEN, NULL, 0)) ret = 1; return ret; } /* * mode_sense () * * Mode Sense command for a device. * Use this to get disk parameters: * number of cylinders * number of heads * number of sectors per track. * * Put result of mode sense into supplied buffer. * Return 1 if command succeeds, else 0. */ static int mode_sense (s_id, buf) int s_id; unchar * buf; { int ret = 0; unchar cmdbuf [G0CMDLEN]; cmdbuf [0] = ScmdMODESENSE; cmdbuf [1] = 0; cmdbuf [2] = 0x3F; cmdbuf [3] = 0; cmdbuf [4] = MODESENSELEN; cmdbuf [5] = 0; if (start_arb () && host_ident (s_id, 0) && local_info_xfer (cmdbuf, G0CMDLEN, buf, MODESENSELEN, NULL, 0)) ret = 1; return ret; } /* * read_cap () * * Read Capacity command for a device. * * Return 1 if command succeeds, else 0. */ static int read_cap (s_id, buf) int s_id; unchar * buf; { int ret = 0; unchar cmdbuf [G1CMDLEN]; cmdbuf [0] = ScmdREADCAPACITY; cmdbuf [1] = 0; cmdbuf [2] = 0; cmdbuf [3] = 0; cmdbuf [4] = 0; cmdbuf [5] = 0; cmdbuf [6] = 0; cmdbuf [7] = 0; cmdbuf [8] = 0; cmdbuf [9] = 0; if (start_arb () && host_ident (s_id, 0) && local_info_xfer (cmdbuf, G1CMDLEN, buf, READCAPLEN, NULL, 0)) ret = 1; return ret; } /* * bus_dev_reset () * * Send Bus Device Reset message to the given SCSI id. * Return 1 if host adapter was not busy and no obvious timeouts occurred, * else 0. */ static int bus_dev_reset (s_id) int s_id; { int bdr_ok = 1; int dev = ((SCSI_MAJOR << 8) | 0x80 | (s_id << 4)); PR1("BDR"); if (bdr_ok) { /* * Do ST0x arbitration. * * De-assert SCSI enable bit. * Write my SCSI id to port. * Start arbitration. */ sfbyte (ss_csr, WC_ENABLE_PRTY); sfbyte (ss_dat, host_id); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ARBITRATE); /* * SCSI spec says there is "no maximum" to the wait for * arbitration complete. */ if (! bus_wait (RS_ARBIT_COMPL << 8 | RS_ARBIT_COMPL)) { bdr_ok = 0; } } /* * Arbitration complete. Now select, with ATN to allow messages. */ if (bdr_ok) { sfbyte (ss_dat, host_id | (1 << s_id)); /* Write both SCSI id's */ sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION | WC_SELECT); if (! bus_wait (RS_BUSY << 8 | RS_BUSY)) bdr_ok = 0; } if (bdr_ok) { sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION); if (! bus_wait (((RS_REQUEST | RS_CTRL_DATA | RS_I_O | RS_MESSAGE) << 8) | (RS_REQUEST | RS_CTRL_DATA | RS_MESSAGE))) bdr_ok = 0; } if (bdr_ok) { sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI); sfbyte (ss_dat, MSG_DEV_RESET); if (! bus_wait ((0xFF << 8) | 0)) bdr_ok = 0; } return bdr_ok; } /* * chk_reconn () * * Check SELECT to see if any SCSI device has tried to reconnect to the host * adapter. Called if there is an interrupt, and by the timer in case * we somehow lose an interrupt. * * Return -1 if no reselect detected, or the SCSI ID of the reselecting * target if there is one. */ static int chk_reconn () { unchar csr, dat; int s_id = -1; csr = ffbyte (ss_csr); if (csr & (RS_SELECT | RS_I_O)) { dat = ffbyte (ss_dat); if ((dat & host_id) && (dat & NSDRIVE)) { dat &= ~ host_id; s_id = 0; while (dat >>= 1) s_id ++; } } return s_id; } /* * ss_mach () * * Gives a distinct state machine for each target device. */ void ss_mach (s_id) int s_id; { ss_type * ssp = ss [s_id]; BUF * bp; do_sst_op = 1; /* plan to run this routine again in most cases */ while (do_sst_op) { bp = ssp->bp; /* nonpolled () below can change ssp->bp */ switch (ssp->state) { /* * Polling states execute whether ssp->waiting or not. */ case SST_POLL_ARBITN: PR3("XPAR"); if (ffbyte (ss_csr) & RS_ARBIT_COMPL) { ssp->waiting = 0; if (host_ident (s_id, 1)) do_connect (s_id); else recover (s_id, RV_P_TIMEOUT); } else { if (ssp->expired) { ssp->expired = 0; recover (s_id, RV_A_TIMEOUT); } else do_sst_op = 0; } break; case SST_POLL_RESELECT: PR3("XPRS"); if (TGT_RSEL) { ssp->waiting = 0; if (host_claimed == -1) host_claimed = s_id; else if (host_claimed != s_id) { #if (DEBUG >= 1) printf ("%d->%d ", host_claimed, s_id); #endif } if (rsel_handshake ()) { do_connect (s_id); } else { recover (s_id, RV_P_TIMEOUT); } } else { /* Reselect poll is negative */ if (ssp->expired) { ssp->expired = 0; recover (s_id, RV_R_TIMEOUT); } else do_sst_op = 0; } break; case SST_POLL_BEGIN_IO: PR3("XPBI"); if (bp == NULL) ssp->state = SST_DEQUEUE; else { /* * At this point a SCSI command is about to * be initiated. It may be a retry. */ if (host_claimed == -1 && BUS_FREE && BUS_FREE) { ssp->waiting = 0; init_pointers (s_id); if (start_arb ()) { host_claimed = s_id; if (host_ident (s_id, 1)) { do_connect (s_id); } else { recover (s_id, RV_P_TIMEOUT); } } else { /* * If arbitration does not succeed right away, it is usually * because another drive is trying to reselect the host. */ set_timeout (s_id, DELAY_ARB); } } else { /* host busy or bus not free */ int o_id; if ((o_id = chk_reconn ()) != -1) defer (dummy_reconn, s_id); ++ ssp->avl_count; if (ssp->avl_count >= MAX_AVL_COUNT) recover (s_id, RV_BF_TIMEOUT); else set_timeout (s_id, DELAY_BSY); } } break; default: if (ssp->waiting) do_sst_op = 0; else { /* * Nonpolling states execute only if no * target timer is running. */ nonpolled (s_id); } } /* endswitch */ } /* endwhile */ } /* * nonpolled () * * Part of ss_mach () - handling of nonpolling states is taken out simply * for readability. */ static void nonpolled (s_id) int s_id; { ss_type * ssp = ss [s_id]; BUF * bp = ssp->bp; struct fdisk_s * fdp; int partition; dev_t dev; switch (ssp->state) { case SST_BUS_DEV_RESET: PR3("XBDR"); if (bus_dev_reset (s_id)) { do_sst_op = 0; set_timeout (s_id, DELAY_BDR); ssp->state = SST_REQ_SENSE; } else recover (s_id, RV_P_TIMEOUT); break; case SST_DEQUEUE: if (bufq_rd_head (s_id) != NULL && ! ssp->busy) { PR3("XDQU"); ssp->busy = 1; bp = bufq_rm_head (s_id); ssp->bp = bp; dev = bp->b_dev; partition = DEV_PARTN (dev); if (dev & SDEV) partition = WHOLE_DRIVE; fdp = ssp->parmp; if (partition != WHOLE_DRIVE) ssp->bno = fdp [partition].p_base + bp->b_bno; else ssp->bno = bp->b_bno; if (bp->b_req == BREAD) ssp->cmdbuf [0] = ScmdREADEXTENDED; else ssp->cmdbuf [0] = ScmdWRITEXTENDED; ssp->cmdbuf [1] = 0; ssp->cmdbuf [2] = ssp->bno >> 24; ssp->cmdbuf [3] = ssp->bno >> 16; ssp->cmdbuf [4] = ssp->bno >> 8; ssp->cmdbuf [5] = ssp->bno; ssp->cmdbuf [6] = 0; ssp->cmdbuf [7] = 0; ssp->cmdbuf [8] = 1; ssp->cmdbuf [9] = 0; ssp->cmdlen = G1CMDLEN; init_pointers (s_id); ssp->bdr_count = 0; ssp->bsy_count = 0; ssp->try_count = 0; ssp->state = SST_POLL_BEGIN_IO; } else /* queue is empty or ssp->busy */ do_sst_op = 0; break; case SST_HIPRI_RESET: case SST_LOPRI_RESET: PR1("XRST"); /* * SST_LOPRI_RESET is same as SST_HIPRI_RESET for now. * Later, can implement a delay to allow other targets to * finish pending operations. */ if (host_claimed == s_id || host_claimed == -1) { host_claimed = s_id; sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_SCSI_RESET); /* reset ON */ ssp->state = SST_RESET_OFF; set_timeout (s_id, DELAY_RST); PR1("+"); } else set_timeout (s_id, DELAY_RST); break; case SST_REQ_SENSE: PR1("XRQS"); /* * Come here at end of SCSI Bus reset (and at other times). * If we have host claimed, release it. */ if (host_claimed == s_id) host_claimed = -1; if (req_sense (s_id)) ssp->state = SST_POLL_BEGIN_IO; else recover (s_id, RV_P_TIMEOUT); break; case SST_RESET_OFF: PR3("XRFF"); sfbyte (ss_csr, WC_ENABLE_PRTY); /* reset OFF */ ssp->state = SST_REQ_SENSE; set_timeout (s_id, DELAY_RST); } /* endswitch */ } /* * start_arb () * * return 1 if host adapter returned Arbitration Complete within allotted * number of tries, else 0 */ static int start_arb () { int ret = 0; int poll_ct; sfbyte (ss_csr, WC_ENABLE_PRTY); sfbyte (ss_dat, host_id); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ARBITRATE); /* * SCSI spec says there is "no maximum" to the wait for arbitration * complete. */ for (poll_ct = 0; poll_ct < HIPRI_RETRIES; poll_ct ++) { if (ffbyte (ss_csr) & RS_ARBIT_COMPL) { ret = 1; break; } else if (chk_reconn () != -1) { sfbyte (ss_csr, WC_ENABLE_PRTY); break; } } #if (DEBUG >= 1) if (! ret) PR1("oSA"); #endif return ret; } /* * host_ident () * * This routine is the bridge in a SCSI bus cycle between Abitration * Complete and the Information Transfer phases. * * return 1 if everything went ok, 0 in case of timeout */ static int host_ident (s_id, disconnect) int s_id; int disconnect; { int ret = 0; /* * Arbitration complete. Now select, with ATN to allow messages. */ sfbyte (ss_dat, host_id | (1 << s_id)); /* Write both SCSI id's */ sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION | WC_SELECT); if (bus_wait (RS_BUSY << 8 | RS_BUSY)) { /* * Assert ATTN so target expects incoming message byte. */ sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ATTENTION); if (bus_wait (((RS_REQUEST | RS_CTRL_DATA | RS_I_O | RS_MESSAGE) << 8) | (RS_REQUEST | RS_CTRL_DATA | RS_MESSAGE))) { if (disconnect) { sfbyte (ss_dat, MSG_IDENT_DC); } else { sfbyte (ss_dat, MSG_IDENTIFY); } sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_ENABLE_IRPT); ret = 1; } else { PR1("oHI2"); } } else { PR1("oHI1"); } return ret; } /* * rsel_handshake () * * After Reselect is detected, a couple steps are needed before entering * Information Transfer phases. This routine does those steps. * * return 1 if ok, 0 in case of timeout. */ static int rsel_handshake () { int ret = 0; sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_BUSY); if (bus_wait (RS_SELECT << 8 | 0)) { sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI); ret = 1; } return ret; } /* * set_timeout () * * Start a timer so as not to wait forever in case something goes wrong while * waiting for an event. Available delays are: * * DELAY_ARB - wait for arbitration complete * DELAY_BDR - allow settling time after Bus Device Reset * DELAY_BSY - wait for not HOST_BUSY and bus free * DELAY_RES - wait for reselect by target * DELAY_RST - allow settling times when doing SCSI Bus Reset * * Second argument is number of clock ticks to wait until timer expiration. */ static void set_timeout (s_id, delay) int s_id, delay; { ss_type * ssp = ss [s_id]; ssp->expired = 0; ssp->waiting = 1; do_sst_op = 0; timeout (& ssp->tim, delay, stop_timeout, s_id); } /* * stop_timeout () * * Called on expiration of the timer for a given target. * Don't expire a timer if it's no longer active. */ static void stop_timeout (s_id) int s_id; { ss_type * ssp = ss [s_id]; if (ssp->waiting) { ssp->expired = 1; ssp->waiting = 0; } ss_mach (s_id); } /* * init_pointers () * * Initialize command and data pointers when starting (or restarting) * a block I/O command. */ static void init_pointers (s_id) int s_id; { ss_type * ssp = ss [s_id]; BUF * bp = ssp->bp; ssp->cmdstat = -1; ssp->cmd_bytes_out = 0; ssp->avl_count = 0; } /* * recover () * * This routine is called directly or indirectly from ss_mach (). It * determines what to do when the interface fails to behave as desired. * * Arguments are the SCSI id of the target HDC and an error type. * Error types are: * * RV_A_TIMEOUT (arbitration timeout) * Host adapter takes too long to respond with arbitration complete. * * RV_P_TIMEOUT (protocol timeout) * Timeout waiting for desired SCSI bus status while connected to a target. * * RV_R_TIMEOUT (reconnect timeout) * Timeout after target disconnects, waiting for reconnect. * * RV_BF_TIMEOUT (bus free timeout) * Waited too long for host not busy and BUS_FREE. * * RV_CS_BUSY (target device busy) * Command status returned was Busy. * * RV_CS_CHECK (target device check) * Command status returned was CHECK. * * Whenever an error occurs, one of the above inputs, together with the SCSI id * of the target, is sent to the recovery process. The recovery process in turn * programs the next state for the machine. */ static void recover (s_id, errtype) int s_id; RV_TYPE errtype; { ss_type * ssp = ss [s_id]; BUF * bp = ssp->bp; #if (DEBUG >= 1) int foo; if ((foo = chk_reconn ()) != -1) printf ("HONK%d ", foo); #endif ++ ssp->try_count; if (ssp->try_count < MAX_TRY_COUNT) { switch (errtype) { case RV_CS_BUSY: ++ ssp->bsy_count; if (ssp->bsy_count < MAX_BSY_COUNT) { ssp->state = SST_POLL_BEGIN_IO; set_timeout (s_id, DELAY_BSY); } else ssp->state = SST_BUS_DEV_RESET; break; case RV_CS_CHECK: ssp->state = SST_REQ_SENSE; break; case RV_P_TIMEOUT: /* fall thru */ case RV_R_TIMEOUT: ++ ssp->bdr_count; if (ssp->bdr_count < MAX_BDR_COUNT) ssp->state = SST_BUS_DEV_RESET; else ssp->state = SST_LOPRI_RESET; break; case RV_BF_TIMEOUT: /* fall thru */ case RV_A_TIMEOUT: ssp->state = SST_HIPRI_RESET; } } else { /* try_count >= MAX_TRY_COUNT */ if (bp) { bp->b_flag |= BFERR; printf ("(%d,%d): ", major (bp->b_dev), minor (bp->b_dev)); printf ("%s error bno =%ld\n", (bp->b_req == BREAD) ? "read" : "write", bp->b_bno); } ss_finished (s_id); } } /* * ss_finished * * Release current I/O buffer to the O / S. */ static void ss_finished (s_id) int s_id; { ss_type * ssp = ss [s_id]; BUF * bp = ssp->bp; int go_again = 1; if (host_claimed == s_id) host_claimed = -1; ssp->busy = 0; if (bp) { if (!(bp->b_flag & BFERR)) bp->b_resid -= BSIZE; if ((bp->b_flag & BFERR) || bp->b_resid == 0) { ssp->bp = NULL; bdone (bp); go_again = 0; } } if (go_again) { ssp->state = SST_POLL_BEGIN_IO; ssp->bdr_count = 0; ssp->bsy_count = 0; ssp->try_count = 0; ssp->bno ++; ssp->cmdbuf [2] = ssp->bno >> 24; ssp->cmdbuf [3] = ssp->bno >> 16; ssp->cmdbuf [4] = ssp->bno >> 8; ssp->cmdbuf [5] = ssp->bno; } else { /* * After processing a kernel I/O request, stop the * state machine for the current id. Then start * this or some other machine which has a request * pending. */ do_sst_op = 0; ssp->state = SST_DEQUEUE; next_req (s_id); } } /* * next_req () * * Given the SCSI id where an I/O request just completed, start handling * another I/O request - which may be for the same or other SCSI id. * For now, use round-robin scheduling. */ static void next_req (s_id) int s_id; { int next_id = s_id; while (1) { next_id ++; if (next_id >= MAX_SCSI_ID) next_id = 0; if (ss [next_id] != NULL && (ss [next_id]->state != SST_DEQUEUE || bufq_rd_head (next_id))) { defer (ss_mach, next_id); break; } if (next_id == s_id) break; } } /* * do_connect () * * This function is called when the host is successfully connected to * the target. It invokes information transfer protocol and then sets * up some sort of recovery unless the command completed successfully * or there was a normal disconnect. */ static void do_connect (s_id) int s_id; { int result; ss_type * ssp = ss [s_id]; result = far_info_xfer (s_id); if (! result) recover (s_id, RV_P_TIMEOUT); else if (ssp->msg_in == MSG_DISCONNECT) { ssp->state = SST_POLL_RESELECT; set_timeout (s_id, DELAY_RES); #if 0 if (host_claimed == s_id) host_claimed = -1; #endif } else if (ssp->msg_in == MSG_CMD_CMPLT && ssp->cmdstat == CS_GOOD) ss_finished (s_id); else if (ssp->cmdstat == CS_BUSY) recover (s_id, RV_CS_BUSY); else if (ssp->cmdstat == CS_CHECK) recover (s_id, RV_CS_CHECK); else /* something else went wrong */ recover (s_id, RV_P_TIMEOUT); } /* * local_info_xfer () * * Do bus cycle information transfer phases. * Transfer is for a command which will produce local results in the driver. * Other ...info_xfer routine handles kernel block I/O commands. * * Return 1 if transfer succeeded, else 0. * */ static int local_info_xfer (cmdbuf, cmdlen, inbuf, inlen, outbuf, outlen) unchar * cmdbuf, * inbuf, * outbuf; uint cmdlen, inlen, outlen; { int bus_timeout; unchar phase_type; int s; int cmd_bytes_out = 0; int data_bytes_in = 0; int data_bytes_out = 0; int ret = 0; int xfer_good = 1; int cmdstat = -1; int msg_in = -1; #if (DEBUG >= 1) int x, xct = 0; unchar xch [100]; #endif s = sphi (); while (req_wait (& bus_timeout) && xfer_good) { phase_type = ffbyte (ss_csr) & (RS_MESSAGE | RS_I_O | RS_CTRL_DATA); #if (DEBUG >= 1) if (xct < 100) xch [xct ++]= phase_type; #endif switch (xpmod (phase_type)) { case XP_MSG_IN: msg_in = ffbyte (ss_dat); switch (msg_in){ case MSG_CMD_CMPLT: case MSG_DISCONNECT: sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_IRPT); break; } break; case XP_MSG_OUT: /* * This case shouldn't happen. We weren't * asserting ATTENTION. */ sfbyte (ss_dat, MSG_NOP); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI); break; case XP_STAT_IN: cmdstat = ffbyte (ss_dat); break; case XP_CMD_OUT: /* * Ship out command bytes. */ if (cmd_bytes_out < cmdlen) { sfbyte (ss_dat, cmdbuf [cmd_bytes_out ++]); #if 1 /* * If just sent last byte, allow interrupts. */ if (cmd_bytes_out == cmdlen) { spl (s); s = sphi (); } #endif } else { /* This case should not happen. */ xfer_good = 0; } break; case XP_DATA_IN: /* * If caller's buffer has room, keep incoming * data byte. Else toss it. */ if (data_bytes_in < inlen) { #if 0 do { inbuf [data_bytes_in ++] = ffbyte (ss_dat); } while (data_bytes_in < inlen); #else inbuf [data_bytes_in ++] = ffbyte (ss_dat); #endif } else xfer_good = 0; break; case XP_DATA_OUT: /* * Copy output buffer bytes to data register. */ if (data_bytes_out < outlen) { sfbyte (outbuf [data_bytes_out ++], ss_dat); } else { /* This case should not happen. */ xfer_good = 0; } break; default: break; } /* endswitch */ } spl (s); if (bus_timeout) { PR1("oLX1"); } else if (! xfer_good) { PR1("oLX2"); } else if (cmdstat != CS_GOOD) { PR1("oLX3"); #if (DEBUG >= 1) printf ("cmdstat =%x ", cmdstat); #endif } else ret = 1; #if (DEBUG >= 1) if (! ret) { printf ("csr =%x ", ffbyte (ss_csr)); printf ("xct =%d ", xct); for (x = 0; x < xct; x ++) printf ("%x ", xch [x]); } #endif return ret; } /* * scsireset () * * Reset the SCSI bus. * Allow settling time when turning reset on / off. * Settling times were determined empirically. * Each tick is 10 msec. */ static void scsireset () { int s; #if (DEBUG >= 1) printf ("scsireset "); #endif s = splo (); sfbyte (ss_csr, WC_ENABLE_PRTY | WC_ENABLE_SCSI | WC_SCSI_RESET); ssdelay (RESET_TICKS); sfbyte (ss_csr, WC_ENABLE_PRTY); ssdelay (RESET_TICKS); spl (s); } /* * ssdelay () * * Delay for some number of arbitrary ticks. * * Using sleep () causes a panic if this driver is linked to the kernel, * even though this routine is called only via ssload (). */ static void ssdelay (ticks) int ticks; { #if 0 timeout (& delay_tim, ticks, wakeup, (int)& delay_tim); sleep ((char *)& delay_tim, CVPAUSE, IVPAUSE, SVPAUSE); #else int i, j; for (i = 0; i < ticks; i ++) for (j = 0; j < LOAD_DELAY; j ++); #endif } /* * init_call () * * Call SCSI command function during initialization, with error recovery. * If the simple command fails, try a Bus Device Reset, then SCSI Bus reset. */ static int init_call (fn, s_id, buf) int (* fn)(); int s_id; unchar * buf; { int ret = 1; int i; int o_id; int s; s = sphi (); for (i = 0; i < 2; i ++) { o_id = chk_reconn (); if (o_id != -1) dummy_reconn (s_id); if ((* fn) (s_id, buf)) goto init_call_done; req_sense (s_id); if ((* fn) (s_id, buf)) goto init_call_done; if (bus_dev_reset (s_id)) { ssdelay (RESET_TICKS); req_sense (s_id); if ((* fn) (s_id, buf)) goto init_call_done; } scsireset (); req_sense (s_id); if ((* fn) (s_id, buf)) goto init_call_done; } ret = 0; init_call_done: spl (s); return ret; } /* * xpmod () * * Command / Data and Message bits are swapped on-board (outside the chip) * on older Future Domain host boards. */ static unchar xpmod (oldphase) unchar oldphase; { unchar ret = oldphase; if (swap_status_bits) { ret &= ~(RS_CTRL_DATA | RS_MESSAGE); if (oldphase & RS_MESSAGE) ret |= RS_CTRL_DATA; if (oldphase & RS_CTRL_DATA) ret |= RS_MESSAGE; } return ret; } /* * tbparms () * * If the drive table has already been patched for this SCSI id, do nothing. * Otherwise, given the real-mode drive number (tbnum) and the SCSI id (s_id), * look for drive parameters from tertiary boot, and copy into driver * data block if we find them. */ static void tbparms (tbnum, s_id) int tbnum, s_id; { FIFO * ffp; typed_space * tp; extern typed_space boot_gift; cmn_err (CE_NOTE, "tbparms (%d, %d) ", tbnum, s_id); if (_sd_drv_parm [s_id].ncyl == 0 && F_NULL != (ffp = fifo_open (& boot_gift, 0))) { while (tp = fifo_read (ffp)) { BIOS_DISK * bdp = (BIOS_DISK *)tp->ts_data; cmn_err (CE_NOTE, "ts_type = %x, drive = %d", tp->ts_type, bdp->dp_drive); if ((T_BIOS_DISK == tp->ts_type) && (tbnum == bdp->dp_drive) ) { _sd_drv_parm [s_id].ncyl = bdp->dp_cylinders; _sd_drv_parm [s_id].nhead = bdp->dp_heads; _sd_drv_parm [s_id].nspt = bdp->dp_sectors; break; } } fifo_close (ffp); } } CON sscon = { DFBLK | DFCHR, /* Flags */ SCSI_MAJOR, /* Major index */ ssopen, /* Open */ ssclose, /* Close */ ssblock, /* Block */ ssread, /* Read */ sswrite, /* Write */ ssioctl, /* Ioctl */ NULL, /* Powerfail */ sswatch, /* Timeout */ ssload, /* Load */ ssunload, /* Unload */ NULL /* Poll */ };