Coherent4.2.10/conf/haiss/src/haiss.c
/***********************************************************************
* haiss.c -- haiscsi compliant Host adapter routines for the Seagate
* ST01/ST02 and 8-bit Future Domain controllers.
*
* These controllers use memory mapped I/O like the IBM professional
* Graphics array controler.
*
* Copyright (c) 1993 Christopher Sean Hilton - All rights reserved.
*
* Last Modified: Wed Jul 6 15:04:35 1994 by [kroot]
*/
/***********************************************************************
* $Id$
*
* $Log$
*/
static char rcsid[] = "@(#) haiss.c $Revision$";
#include <sys/coherent.h>
#include <sys/con.h>
#include <sys/stat.h>
#include <kernel/timeout.h>
#include <sys/cmn_err.h>
#include <sys/haiscsi.h>
/* A forward declaration masquerading as an external. */
extern haft_t ss_haft;
#define LOCAL static
#define Register register /* For stupid compilers */
/***********************************************************************
* The Seagate ST01/02 and Future Domain 8xx 8-bit host adapters use
* the same memory mapped layout and SCSI Bus controller chip so they
* program similarly. The differences are:
*
* Controller CSR(hex) Data(hex) SCSI ID
* -------------------- ---------- ---------- ----------
* ST01/02 1a00 1c00 7
* TMC-845 Type 1c00 1e00 6
* TMC-840 Type 1a00 1c00 6
*
* These differences are taken care of in the ss_load() routine so
* the rest of the program need not concern itself with them.
*/
#define SS_SRAMSTART 0x1800 /* Start of static RAM for test */
#define SS_SRAMEND 0x187f /* End of Static Ram */
#define ST01_CSR 0x1a00 /* ST01 Control/Status Register */
#define ST01_DAT 0x1c00 /* ST01 Data Register */
#define FD845_CSR 0x1c00 /* Future Domain Control/Status Register */
#define FD845_DAT 0x1e00 /* Future Domain Data Register */
#define SS_RAMSIZE 0x2000 /* Total size of memory mapped area */
#define ST_ID 7
#define FD_ID 6
/* Command bits within the CSR */
#define RST_OUT bit(0) /* SCSI Reset Command */
#define SEL_OUT bit(1) /* SCSI Select */
#define BUSY_OUT bit(2) /* SCSI Busy */
#define ATTN_OUT bit(3) /* SCSI Attention */
#define STARTARB bit(4) /* Start Arbitration */
#define ENPARITY bit(5) /* Enable Parity */
#define ENINTR bit(6) /* Enable Interrupt */
#define ENSCSI bit(7) /* Enable SCSI bus */
#define HAIDLE (ENPARITY | ENINTR)
/* Status bits within the CSR */
#define BUSY_IN bit(0) /* SCSI Busy signal */
#define MSG_IN bit(1) /* SCSI Message signal */
#define IO_IN bit(2) /* SCSI I/O signal */
#define CD_IN bit(3) /* SCSI Control/Data signal */
#define REQ_IN bit(4) /* SCSI Request signal */
#define SEL_IN bit(5) /* SCSI Select signal */
#define PARITYERR bit(6) /* Parity Error. */
#define ARBITCMP bit(7) /* Arbitration Complete */
#define RESELECT (SEL_IN | IO_IN) /* SCSI reselect phase in progress */
/***********************************************************************
* SCSI Bus IO phases: use with IOPHASE below to match what the bus
* is trying to do.
*/
#define XP_DATAOUT (0) /* Init -> Trgt Data */
#define XP_DATAIN (IO_IN) /* Trgt -> Init Data */
#define XP_COMMAND (CD_IN) /* Init -> Trgt CDB */
#define XP_STATUS (CD_IN|IO_IN) /* Trgt -> Init CDB Status */
#define XP_MSGOUT (MSG_IN|CD_IN) /* Init -> Trgt Message */
#define XP_MSGIN (MSG_IN|CD_IN|IO_IN)/* Trgt -> Init Message */
/* SCSI Message Definitions */
#define MSG_CC 0x00 /* Command complete */
#define MSG_SAVEDPTR 0x02 /* Save data pointers */
#define MSG_RSTRDPTR 0x03 /* Restore data pointers */
#define MSG_DISCONNECT 0x04 /* Disconnect */
#define MSG_ABORT 0x06 /* Abort the current operation */
#define MSG_REJECT 0x07 /* Message reject (last message invalid) */
#define MSG_NOP 0x08 /* No operation */
#define MSG_LCC 0x0a /* Linked command complete */
#define MSG_LCCF 0x0b /* Linked command complete (with flag) */
#define MSG_BUSDEVRST 0x0c /* Bus device reset */
#define MSG_IDENTIFY 0x80 /* Identify message (Family) */
#define ID_DISCONNECT 0x40 /* Disconnect Flag for Identify message */
/***********************************************************************
* SCSI Bus Timings:
*
* These are the official SCSI-1 Bus Timings of the NBA, Get your
* gamecard now.
*/
/* Event Coh Time Event Actual Time */
/* (Abrv.) (usec) Full Name (usec) */
/* -------------- -------- ----------------- ----------- */
#define ARBIT_DLY 3 /* Arbitration Delay 2.200 */
#define ASSERT_PER 1 /* Assertion period 0.090 */
#define BUSCLR_DLY 1 /* Bus Clear Delay 0.800 */
#define BUSFREE_DLY 1 /* Bus Free Delay 0.800 */
#define BUSSET_DLY 2 /* Bus Set Delay 1.800 */
#define BUSSETTLE_DLY 1 /* Bus Settle Delay 0.400 */
#define CABLESKEW_DLY 1 /* Cable Skew Delay 0.010 */
#define DATARLS_DLY 1 /* Data Release Delay 0.400 */
#define DESKEW_DLY 1 /* Deskew Delay 0.045 */
#define HOLD_TM 1 /* Hold Time 0.045 */
#define NEGATION_PER 1 /* Negation Period 0.090 */
#define RSTHOLD_TM 25 /* Reset Hold Time 25.000 */
#define SELABORT_TM 200 /* Selection Abort Time 200.000 */
#define SELTO_DLY 250000 /* Sel. Timeout Delay 250,000.000 */
#define RETRY_DELAY 3 /* Wait on a busy SCSI bus */
#define MAXBUSYCOUNT 120 /* Number of busy bus before reset */
#define REQ_BUSDEVICERST 0x0001 /* Request Bus Device Reset */
#define REQ_ABORTSCSICMD 0x0002 /* Request Abort SCSI command */
#define REQ_STARTSCSICMD 0x0004 /* Request Start SCSI */
#define CMD_PENDING 0x0001 /* Command pending */
#define CMD_DEVBUSY 0x0002 /* Device is busy */
#define CMD_OVERRUN 0x0004 /* Data overrun on command */
#define CMD_UNDERRUN 0x0008 /* Data underrun on command */
#define CMD_NEEDREQACK 0x0010 /* Command need req/ack handshake */
#define HAST_OK 0x00
#define HAST_SELTO 0x11
#define HAST_OVERRUN 0x12
#define HAST_UNDERRUN 0x12
#define HAST_BUSFREE 0x13
#define HAST_BADBUSPH 0x14
#define HAST_INVDCDB 0x20
#define MAXSEGCOUNT 16
#define DATAREGSIZE 512
/***********************************************************************
* Device states.
*
* These states give an idea of what the device is doing in the context
* of this host adapter.
*/
typedef enum {
DEV_IDLE = 0, /* Device is idle in this host's context */
DEV_QUEUED, /* Device has a command queued and waiting */
DEV_SCSIPROT, /* Device is in some stage of the SCSI protocol */
DEV_WORKING, /* Device is working on a request */
DEV_FINISHED /* Device is finished with SCSI request */
} devstate_t;
typedef struct ssccb_s *ssccb_p;
typedef struct ssccb_s {
devstate_t c_state; /* Current state of the device. */
unsigned c_id; /* Command id number */
unsigned c_reqflags; /* Request flags */
unsigned c_cmdflags; /* Command flags */
unsigned char *c_cdb; /* Pointer to cdb to execute */
unsigned c_cdblen; /* Length of cdb */
unsigned c_time;
unsigned c_target;
srb_p c_srb; /* Current SCSI Request Block */
ssccb_p c_next; /* Next ccb in Request chain */
size_t c_segcount; /* Number of segments in the s/g list */
size_t c_dataofs; /* Offset into the current segment */
haisgsegm_p c_datain; /* pointer to s/g list for datain. */
haisgsegm_p c_dataout; /* pointer to s/g list for dataout. */
haisgsegm_t c_sgs[MAXSEGCOUNT]; /* Scatter gather list for xfer */
unsigned short c_scsistatus; /* Status of the SCSI command */
} ssccb_t;
static ssccb_p ssdevs[MAXDEVS] = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
static ssccb_t dummy;
static srb_t dummysrb;
static ssccb_p normal_queue = NULL,
priority_queue = NULL;
static unsigned char test_unit_ready[] = { 0, 0, 0, 0, 0, 0 };
/***********************************************************************
* Configurable variables - specified in Space.c */
enum {
ST01=0, /* ST01/02 controller */
FD845=1, /* TMC-845/850/860/875/885 */
FD840=2 /* TMC-840/841/880/881 */
};
extern int haiss_type; /* From the above enum */
extern int haiss_intr; /* IRQ number */
extern int haiss_base; /* Paragraph addr for BIOS */
extern int haiss_slowdev; /* Bitmap of devices needing slow handshake */
extern int HAI_DISK; /* Bit mapped values where disks are */
extern int HAI_TAPE; /* Bit mapped values for where tapes are */
extern int HAI_CDROM; /* Bit mapped values for where CD-ROMS are */
static int ss_devices; /* Bitwise-or of HAI device bitmaps. */
static ssccb_p rawccb = NULL;
static unsigned char *ss_vbase = NULL; /* Controller Base selector */
static unsigned char *ss_csr = NULL, /* Command/Status register */
*ss_dat = NULL; /* Data Register */
static unsigned short hostid; /* host adapter SCSI Bus address */
static unsigned short swap_status_bits = 0;
static char ss_xfrbuf[DATAREGSIZE]; /* transfer buffer for slow devices */
static TIM sstim;
#define CSR (*(volatile unsigned char *) ss_csr)
#define DAT (*(volatile unsigned char *) ss_dat)
#define setcsr(v) (CSR = (v))
#define setdat(v) (DAT = (v))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define reselect() ((CSR & (SEL_IN | IO_IN)) == (SEL_IN | IO_IN))
#define set_host_status(r, newstat) ((r)->hastat = (newstat))
#define DEBUG_TRACE 0
#define DEBUG_QTRACE 0
/***********************************************************************
* A squawk function for printing stuff out while debugging.
*/
#if DEBUG_TRACE
#define trace(str) cmn_err(CE_CONT, "!" (str))
#else
#define trace(str)
#endif
/***********************************************************************
* a quicker squawk function for more difficult bugs.
*/
#if DEBUG_QTRACE
static char spinnerstr[] = "-/|\\";
#define qtrace(x, y, c) _chirp((c), 2 * (80 * (y) + (x)))
#define spinner(x, y, i) \
{ if (++(i) >= sizeof(spinnerstr)) i = 0; qtrace((x), (y), spinnerstr[(i)]); }
#else
#define qtrace(x, y, c)
#endif
/***********************************************************************
* Forward declarations for things which must be defined out of order.
*/
#if __USE_PROTO__
LOCAL void ssintr(void);
#else
LOCAL void ssintr();
#endif
/***********************************************************************
* Utility routines for doing timing functions. All times here are
* measured in micro seconds and converted to Coherent Clock ticks.
*
* One nasty side effect of these timers is that you cannot have two
* contexts trying to access them at the same time. They use static
* values for masks. This shouldn't be a problem with this design
* as only one routine accesses the SCSI bus, and thus needs the timers
* and masks, anyway.
*/
#define usec2t0(usec) (((usec * 11932) + 5000) / 10000)
/*
* Forward declarations for kernel functions so gcc doesn't complain
*/
int busyWait __PROTO((int (*)(void), unsigned));
int busyWait2 __PROTO((int (*)(void), unsigned));
vaddr_t map_pv __PROTO((paddr_t, size_t));
void unmap_pv __PROTO((vaddr_t));
unsigned kucopy __PROTO((void *, void *, unsigned));
unsigned ukcopy __PROTO((void *, void *, unsigned));
void setivec __PROTO((int, void (*)(void)));
void clrivec __PROTO((int));
void pxcopy __PROTO((paddr_t, vaddr_t, size_t, int));
void xpcopy __PROTO((vaddr_t, paddr_t, size_t, int));
void timeout __PROTO((TIM *, int, void (*)(), ...));
/***********************************************************************
* eventwait() -- Wait for an event (really?)
*
* Wait up to ticks milliseconds for an event to happen. This is better
* than using a for loop.
*/
#if __USE_PROTO__
LOCAL int eventwait(int (*event)(), Register unsigned timeout_val)
#else
LOCAL int
eventwait(event, timeout_val)
int (*event)();
Register unsigned timeout_val;
#endif
{
if (timeout_val == 0) {
if (event)
return (*event)();
else
return 0;
}
else if (timeout_val < 10000)
return busyWait2(event, usec2t0(timeout_val));
else
return busyWait(event, (timeout_val + 5000) / 10000);
} /* eventwait() */
static unsigned chkmask, /* shared check value for allbitswait */
chkevent; /* shared check value for allbitswait */
#if __USE_PROTO__
LOCAL int allbchk(void)
#else
LOCAL int
allbchk()
#endif
{
return ((CSR & chkmask) == chkevent);
} /* allbchk() */
#if __USE_PROTO__
LOCAL int allbitswait(unsigned char mask, unsigned char event, unsigned ticks)
#else
LOCAL int
allbitswait(mask, event, ticks)
unsigned char mask,
event;
unsigned ticks;
#endif
{
chkmask = mask;
chkevent = event;
return (eventwait(&allbchk, ticks));
} /* allbitswait() */
#if __USE_PROTO__
LOCAL void hareset(unsigned char newstate)
#else
LOCAL void
hareset(newstate)
unsigned char newstate;
#endif
{
/*******************************************************
* Reset the Host adapter. This resets all of the
* devices on the bus also. The SCSI spec stats that
* the reset hold time is 25 usec or longer. Wait
* through two reset hold times here.
*/
setcsr(ENPARITY | ENSCSI | RST_OUT);
eventwait(NULL, 2 * RSTHOLD_TM);
setcsr(newstate);
/*******************************************************
* Allow 5 seconds for all devices on the bus to ready
* themselves after this reset. In practice only the
* disk drives will be ready after 5 sec but nobody
* should be trying to use the tape drive this soon
* anyway.
*/
eventwait(NULL, 5000000U);
}
/***********************************************************************
* hainit() -- Reset the host adapter and set it up to accept
* commands.
*
* This should look very familiar to you Hal.
*/
#if __USE_PROTO__
LOCAL int ss_load(void)
#else
LOCAL int
ss_load()
#endif
{
paddr_t pbase; /* Physical base of controller ram. */
ssccb_p ctrl; /* Pointer to a device control structure */
unsigned i, id,
numdevs;/* number of devices configured on the bus. */
int erf;
cmn_err(CE_CONT,
"Haiss: Seagate ST0x, Future Domain 8xx Host Adapter module.\n");
/* Map the HA's memory. */
pbase = ((paddr_t) (haiss_base << 4));
ss_vbase = (unsigned char *) map_pv(pbase, SS_RAMSIZE);
/*******************************************************************
* Check for the presence of the card by writing to the host
* adapter's Static Ram and then read back to make sure that the
* ram is there. If it is assume that the card is present.
*/
for (i = SS_SRAMSTART; i <= SS_SRAMEND; ++i)
ss_vbase[i] = (i & 0xff);
for (i = SS_SRAMSTART, erf = 0; !erf && i < SS_SRAMEND; ++i)
erf |= (ss_vbase[i] != (i & 0xff));
if (erf) {
cmn_err(CE_CONT, "\tStatic ram test failed.\n");
return (ss_haft.hf_present = 0);
}
/*******************************************************************
* Now setup the parameters used to work with the HA. The differences
* between the Seagates and the Future Domains are resolved here.
* Aside from the addressing differences earlier Future Domain
* controllers swapped the SCSI C/D and MESSAGE bits off the chip.
* This is compensated for with the flag swap_status_bits and with
* function busphase().
*/
swap_status_bits = 0;
switch (haiss_type) {
case ST01:
cmn_err(CE_CONT, "\tConfigured for Seagate ST01/ST02\n");
ss_csr = ss_vbase + ST01_CSR;
ss_dat = ss_vbase + ST01_DAT;
hostid = ST_ID;
break;
case FD845:
cmn_err(CE_CONT, "\tConfigured for Future Domain TMC-845/850/860/875/885\n");
ss_csr = ss_vbase + FD845_CSR;
ss_dat = ss_vbase + FD845_DAT;
hostid = FD_ID;
break;
case FD840:
cmn_err(CE_CONT, "\tConfigured for Future Domain TMC-840/841/880/881\n");
ss_csr = ss_vbase + ST01_CSR;
ss_dat = ss_vbase + ST01_DAT;
hostid = FD_ID;
swap_status_bits = 1;
break;
default:
cmn_err(CE_CONT, "\tbad configuration.\n");
return (ss_haft.hf_present = 0);
}
#if defined(INIT_RST)
hareset(ENPARITY);
#endif
/***************************************************************
* Lastly search through the installed devices mask and find
* out how many control structures we need to for this
* configuration.
*/
ss_devices = ( HAI_DISK | HAI_TAPE | HAI_CDROM );
numdevs = 0;
for (i = 0; i < MAXDEVS; ++i) {
if (ss_devices & bit(i))
++numdevs;
}
ctrl = rawccb = (ssccb_p) kalloc(numdevs * sizeof(ssccb_t));
if (!rawccb) {
cmn_err(CE_PANIC,
"haiss: Cannot allocate Host adapter control structures.\n");
return (ss_haft.hf_present = 0);
}
/***************************************************************
* Clear out the control structures and put them into the reference
* table for use when the driver is working.
*/
memset(rawccb, 0, numdevs * sizeof(ssccb_t));
for (id = 0; id < MAXDEVS; ++id)
if (ss_devices & bit(id)) {
ssdevs[id] = ctrl++;
ssdevs[id]->c_target = id;
}
/*******************************************************************
* All done here for now. If I were only controlling DASD devices
* I could now go and initialize all the devices on the bus. That
* is now the job of the individual device routines. This will be
* a problem because at some point in time I will have to read and
* interpret the partition table of the disk...
*/
/* Grab the Interrupt vector */
setivec(haiss_intr, ssintr);
cmn_err(CE_CONT,
"\tBase Memory:\t\t0x%x\n"
"\tInterrupt vector:\t%d\n",
haiss_base,
haiss_intr);
/*
* This call doesn't belong here. This really should be done from
* the disk drive init routines. But this is in testing so...
*/
{
extern int at_drive_ct;
int biosnum = at_drive_ct;
for (id = 0 ; id < MAXDEVS ; ++id)
if (HAI_DISK & bit(id))
loadbiosparms(biosnum++, id);
}
return (ss_haft.hf_present = 1) /* 1 */;
} /* hainit() */
/***********************************************************************
* ssunload() -- Return memory, IRQ, and Selector back to Kernel
* when done.
*/
#if __USE_PROTO__
LOCAL void ss_unload(void)
#else
LOCAL void
ss_unload()
#endif
{
/* Return memory occupied by control structures. */
if (rawccb)
kfree(rawccb);
/* Return the selector */
unmap_pv((vaddr_t) ss_vbase);
/* Return the IRQ */
clrivec(haiss_intr);
} /* ssunload() */
/***********************************************************************
* busphase() -- Get the I/O phase from the bus.
*
* Read the bus I/O phase signals. Swap the MSG_IN and CD_IN signals
* if this is an old Future Domain card which swaps them for us.
*/
#if __USE_PROTO__
LOCAL unsigned char busphase(void)
#else
LOCAL unsigned char
busphase()
#endif
{
unsigned rawphase = CSR & (MSG_IN | IO_IN | CD_IN);
if (swap_status_bits) {
unsigned ret = rawphase & ~(MSG_IN | CD_IN);
if (rawphase & MSG_IN)
ret |= CD_IN;
if (rawphase & CD_IN)
ret |= MSG_IN;
return (unsigned char) ret;
}
else
return (unsigned char) rawphase;
} /* busphase() */
/***********************************************************************
* macro busfree() -- Wait for a bus free phase.
*/
#define busfree() ((CSR & (BUSY_IN | SEL_IN)) == 0)
/***********************************************************************
* set_priority_queue() -- Set the priority queue pointer for the
* sswork loop.
*
* Reselect phases occur on the SCSI bus to finish up commands that
* allow disconnect. These will be seen as reselect phases from the
* interrupt routine or when trying to arbitrate for the bus. The
* work loop is designed to process these requests first. To do this
* it needs to know which device is trying to reselect the host.
* set_priority_queue puts the CCB for this device into the variable:
* priority_queue. This will be picked up by sswork and the device
* will be serviced.
*/
#if __USE_PROTO__
LOCAL ssccb_p set_priority_queue(void)
#else
LOCAL ssccb_p
set_priority_queue()
#endif
{
Register unsigned scsibus;
Register unsigned target;
Register unsigned hostmask = bit(hostid);
/*******************************************************
* Read the SCSI bus to see if we are needed.
*/
scsibus = DAT;
if ((scsibus & hostmask) == hostmask) {
/***********************************************
* Strip the host's tid bit from the SCSI
* Bus and make sure that what's left is a
* power of two. This will be the tid bit of
* the target that's attempting to reconnect
* to the host adapter.
*/
scsibus &= ~hostmask;
if (((scsibus - 1) & (scsibus)) == 0) {
for (target = 0 ; scsibus > 1 ; ++target, scsibus >>= 1)
;
while (scsibus > 1) {
scsibus >>= 1;
++target;
} /* while */
if (!ssdevs[target] || ssdevs[target]->c_state != DEV_WORKING) {
dummy.c_scsistatus = dummysrb.status = ST_PENDING;
dummysrb.target = hostid;
dummysrb.lun = 0;
dummy.c_datain = dummy.c_dataout = NULL;
dummy.c_srb = &dummysrb;
priority_queue = &dummy;
cmn_err(CE_WARN,
"haiss: Invalid reselect at target %d,"
" device state: %x",
target,
ssdevs[target]->c_state);
} /* if */
else
priority_queue = ssdevs[target];
} /* if */
else
cmn_err(CE_NOTE, "haiss: Invalid SCSI reselect.");
} /* if */
return priority_queue;
} /* set_priority_queue() */
/***********************************************************************
* SCSI bus phase handlers.
*
* These functions handle the various SCSI bus phase that we expect
* to see in normal operation.
*/
/***********************************************************************
* scsi_arbitrate() -- Arbitrate for the SCSI Bus.
*/
#if __USE_PROTO__
LOCAL int scsi_arbitrate(int host)
#else
LOCAL int
scsi_arbitrate(host)
int host;
#endif
{
if (busfree() && busfree()) {
setcsr(HAIDLE);
setdat(bit(host)); /* set up our address on the bus */
setcsr(ENINTR | ENPARITY | ENSCSI | STARTARB);
if (allbitswait(ARBITCMP, ARBITCMP, 100) != 0)
return 1;
else {
setcsr(HAIDLE);
return 0;
}
}
else
return 0;
} /* scsi_arbitrate() */
#if __USE_PROTO__
LOCAL int msgoutstart(void)
#else
LOCAL int
msgoutstart()
#endif
{
return ((CSR & REQ_IN) != 0 && (busphase() == XP_MSGOUT));
}
/***********************************************************************
* msgoutack() -- Return (1) if we have a message out phase.
*/
#if __USE_PROTO__
LOCAL int msgoutack(void)
#else
LOCAL int
msgoutack()
#endif
{
return (busphase() != XP_MSGOUT);
} /* msgoutack() */
/***********************************************************************
* scsi_connect() -- Connect to a target on the SCSI bus.
*
* After arbitration this establishes a connection to a target on
* the SCSI bus. If needed a message out phase is generated and a
* message will be passed to the target device.
*/
#if __USE_PROTO__
LOCAL int scsi_connect(int host, int target, unsigned char message)
#else
LOCAL int
scsi_connect(host, target, message)
int host;
int target;
unsigned char message;
#endif
{
int attn_req = (message != MSG_NOP);
int i;
/***************************************************************
* We won arbitration. Now begin the selection phase by
* asserting the sel signal on the SCSI bus. Keep busy high
* too since we are dropping the arbitration signal. Then
* wait a Bus Clear Delay and a Bus Settle Delay.
*/
setcsr(ENSCSI | ENINTR | ENPARITY | SEL_OUT | BUSY_OUT);
busyWait2(NULL, 3); /* Wait 2.6 us */
/***************************************************************
* Drive the requested device's id onto the bus, wait 2 deskew
* delays.
*/
setdat(bit(host) | bit(target));
busyWait2(NULL, 2);
setcsr((attn_req) ? (ENSCSI | ENPARITY | ENINTR | SEL_OUT | ATTN_OUT)
: (ENSCSI | ENPARITY | ENINTR | SEL_OUT));
if (!allbitswait(BUSY_IN, BUSY_IN, SELTO_DLY)) {
setdat(0);
setcsr(ENSCSI | ENPARITY | ENINTR | SEL_OUT);
if (!allbitswait(BUSY_IN, BUSY_IN, SELABORT_TM + (2 * DESKEW_DLY))) {
setcsr(HAIDLE);
cmn_err(CE_NOTE, "haiss: Select Timed Out on ID%d", target);
return 0;
}
}
busyWait2(NULL, 2);
if (attn_req) {
setcsr(ENSCSI | ENPARITY | ENINTR | ATTN_OUT);
if (!busyWait(&msgoutstart, 500)) {
cmn_err(CE_WARN, "Cannot start message out phase!\n");
return 0;
}
setcsr(ENSCSI | ENPARITY | ENINTR); /* Drop the ATTN Condition */
setdat(message); /* Send the message */
setcsr(ENSCSI | ENPARITY | ENINTR);
for (i = 0 ; i < 3; ++i) {
if (busyWait(&msgoutack, 5)) {
return 1;
}
else {
cmn_err(CE_NOTE,
"haiss: Resending message: (0x%x)\n", message);
setdat(message);
setcsr(ENSCSI | ENPARITY | ENPARITY);
message = MSG_NOP;
}
}
return 0;
}
setcsr(ENSCSI | ENPARITY | ENINTR);
return 1;
} /* scsi_connect() */
/***********************************************************************
* scsi_reconnect() -- Reconnect to a device on the bus after
* disconnect.
*
* Connect is Host Adapter Driven, reconnect is target driver. A target
* has grabbed the bus and selected the host. This function handshakes
* the bus into the ioxfer phase.
*/
#if __USE_PROTO__
LOCAL int scsi_reconnect(void)
#else
LOCAL int scsi_reconnect()
#endif
{
setcsr(ENPARITY | ENSCSI | ENINTR | BUSY_OUT);
if (allbitswait(SEL_IN, 0, SELTO_DLY)) {
setcsr(ENPARITY | ENSCSI | ENINTR);
return 1;
}
else {
setcsr(HAIDLE);
cmn_err(CE_NOTE, "haiss: SCSI Reselect failed.");
}
return 0;
} /* scsi_reconnect() */
static int bsyhigh;
#if __USE_PROTO__
LOCAL int reqcheck(void)
#else
LOCAL int
reqcheck()
#endif
{
unsigned char csr = CSR;
if (csr & REQ_IN)
return 1;
if ((csr & BUSY_IN) == 0) {
bsyhigh = 0;
return 1;
}
return 0;
} /* reqcheck() */
/***********************************************************************
* reqwait() -- Wait for the request and busy signals from the
* target.
*
* During the I/O phases the target should assert REQ_IN and BUSY_IN
* while it wants to do I/O. Poll for those values in a loop which
* will timeout eventually. Like all other loops in this driver this
* one will have to be replaced with the new busyWait function when
* I get documentation and the function has been standardized (not
* in that order).
*
* Input: Pointer to caller's timeout flag. This will be set to
* one if the function if the function times out, zero otherwise.
*/
#if __USE_PROTO__
LOCAL int reqwait(Register int *toptr)
#else
LOCAL int
reqwait(toptr)
Register int *toptr;
#endif
{
#if 0
if (toptr)
*toptr = 0;
bsyhigh = 1;
if (eventwait(&reqcheck, (4 * SELTO_DLY)) && bsyhigh)
return 1;
else if (bsyhigh && toptr) {
cmn_err(CE_WARN, "haiss: SCSI REQ Timeout");
if (ss_msgin_cc)
cmn_err(CE_WARN, "haiss: MSGIN:CC");
*toptr = 1;
}
return 0;
#else
int counter;
unsigned char csr;
if (toptr)
*toptr = 0;
counter = 10000000L;
do {
csr = (CSR & (BUSY_IN | REQ_IN));
if ((csr & REQ_IN) == REQ_IN)
return 1;
if ((csr & BUSY_IN) == 0)
return 0;
} while (--counter > 0);
cmn_err(CE_WARN, "haiss: SCSI REQ Timeout (CSR: 0x%x)", CSR);
if (toptr)
*toptr = 1;
return 0;
#endif
} /* reqwait() */
/***********************************************************************
* sswrite() -- byte by byte copy with handshake.
*
* Copy byte by byte from kernel memory to kernel memory.
*
* Assumes that one of the address is the data port from the Host
* Adapter.
*/
#if __USE_PROTO__
LOCAL size_t sswrite(void *source,
void *destination,
size_t size,
unsigned char phase)
#else
LOCAL size_t
sswrite(source, destination, size, phase)
char *source; /* Source Address */
char *destination; /* Destination Address */
size_t size; /* Size of copy */
unsigned char phase; /* required busphase */
#endif
{
Register char *src = source;
Register char *dst = destination;
Register int index = 0;
#if 0
static char capbuf[64];
while (index < sizeof(capbuf) && index < size && busphase() == phase) {
/*******************************************************
* For kernel reads and writes syncronize each byte
* going back and forth over the host. This is closer
* to the behaviour that the old ss driver used to use.
*/
if (reqwait(NULL)) {
capbuf[index] = dst[index] = src[index];
++index;
}
else
break;
}
#endif
while (index < size && busphase() == phase) {
/*******************************************************
* For kernel reads and writes syncronize each byte
* going back and forth over the host. This is closer
* to the behaviour that the old ss driver used to use.
*/
if (reqwait(NULL)) {
dst[index] = src[index];
++index;
}
else
break;
}
#if 0
dumpmem("\nsswrite capture buffer:", capbuf, min(index, sizeof(capbuf)));
#endif
return index;
} /* sswrite() */
/***********************************************************************
* dataout() -- Handle the Data out phase of the bus. This gets
* special treatment because of the way Coherent's
* memory management works. Up for review when the
* memory management system changes.
*/
#if __USE_PROTO__
LOCAL void dataout(ssccb_p c, int *toflag)
#else
LOCAL void
dataout(c, toflag)
ssccb_p c;
unsigned *toflag;
#endif
{
haisgsegm_p dataend;
haisgsegm_p dataseg;
size_t dataofs;
char *dst = (c->c_cmdflags & CMD_NEEDREQACK) ? ss_xfrbuf : (char *) ss_dat;
size_t xfrsz;
int s;
s = sphi();
dataend = (c->c_sgs + c->c_segcount);
dataseg = c->c_dataout;
dataofs = c->c_dataofs;
while (dataseg && busphase() == XP_DATAOUT) {
xfrsz = dataseg->sgs_segsize - dataofs;
if (xfrsz > DATAREGSIZE)
xfrsz = DATAREGSIZE;
pxcopy(dataseg->sgs_segstart + dataofs, dst, xfrsz, SEL_386_KD);
if (dst != (char *) ss_dat)
sswrite(dst, ss_dat, xfrsz, XP_DATAOUT);
/*
* The fast copy routine doesn't modify xfrsz so we could have
* problems here but the errors should be obvious in any case
* the slow transfer case can count bytes so it does. This is
*/
dataofs += xfrsz;
if (dataofs >= dataseg->sgs_segsize) {
dataofs = 0;
if (++dataseg >= dataend)
dataseg = NULL;
} /* if */
if (!reqwait(toflag))
break;
} /* while */
c->c_dataofs = dataofs;
c->c_dataout = dataseg;
spl(s);
if (reqwait(toflag) && busphase() == XP_DATAOUT) {
while (reqwait(toflag) && busphase() == XP_DATAOUT)
DAT = 0x00;
c->c_cmdflags |= CMD_UNDERRUN;
} /* if */
if (!c->c_dataout)
c->c_cmdflags &= ~CMD_OVERRUN;
} /* dataout() */
/***********************************************************************
* datain() -- Handle the Data out phase of the bus. This gets
* special treatment because of the way Coherent's
* memory management works. Up for review when the
* memory management system changes.
*/
#if __USE_PROTO__
LOCAL void datain(Register ssccb_p c, int *toflag)
#else
LOCAL void
datain(c, toflag)
ssccb_p c;
int *toflag;
#endif
{
haisgsegm_p dataend;
haisgsegm_p dataseg;
size_t dataofs;
size_t xfrsz;
char *src = (c->c_cmdflags & CMD_NEEDREQACK) ? ss_xfrbuf : (char *) ss_dat;
unsigned char bb;
int s;
s = sphi();
dataend = (c->c_sgs + c->c_segcount);
dataseg = c->c_datain;
dataofs = c->c_dataofs;
while (dataseg && busphase() == XP_DATAIN) {
xfrsz = dataseg->sgs_segsize - dataofs;
if (xfrsz > DATAREGSIZE)
xfrsz = DATAREGSIZE;
if (src != (char *) ss_dat)
sswrite(ss_dat, src, xfrsz, XP_DATAIN);
xpcopy(src, dataseg->sgs_segstart + dataofs, xfrsz, SEL_386_KD);
dataofs += xfrsz;
if (dataofs >= dataseg->sgs_segsize) {
dataofs = 0;
if (++dataseg >= dataend)
dataseg = NULL;
} /* if */
if (!reqwait(toflag))
break;
} /* while */
c->c_dataofs = dataofs;
c->c_datain = dataseg;
spl(s);
if (reqwait(toflag) && busphase() == XP_DATAIN) {
while (reqwait(toflag) && busphase() == XP_DATAIN)
bb = DAT;
c->c_cmdflags |= CMD_OVERRUN;
}
if (!c->c_datain)
c->c_cmdflags &= ~CMD_UNDERRUN;
} /* datain() */
/***********************************************************************
* scsi_ioxfer() -- Handle SCSI I/O transfer phases.
*
* SCSI I/O phases are driven by the target. This function follows
* the target until the I/O phases are complete.
*/
#if __USE_PROTO__
LOCAL int scsi_ioxfer(Register ssccb_p c)
#else
LOCAL int
scsi_ioxfer(c)
Register ssccb_p c;
#endif
{
Register srb_p r = c->c_srb;
unsigned msgin;
unsigned bb;
int toflag = 0;
int errors = 0;
int done = 0;
/***************************************************************
* For as long as the target is requesting information send some
* even if it comes from the bit bucket. This may cause an infinite
* loop but only if the target is broken or incredibly hungry.
* This may need to be changed in the future, no problem.
*/
if (c == NULL)
cmn_err(CE_PANIC, "haiss: Null buffer passed to scsi_ioxfer");
while (!done && reqwait(&toflag)) {
if (errors) {
cmn_err(CE_WARN, "haiss: Error in SCSI I/O phases.");
c->c_datain = c->c_dataout = NULL;
c->c_cdb = NULL;
c->c_cdblen = 0;
setcsr(ENSCSI | ENPARITY | ENINTR | ATTN_OUT);
}
switch (busphase()) {
/*******************************************************
* COMMAND: Transfer a SCSI CDB from the initiator to the
* target. Ex: Send the read command to the Hard disk.
*/
case XP_COMMAND:
if (c->c_cdb) {
while (c->c_cdblen > 0 && reqwait(&toflag)) {
setdat(*c->c_cdb++);
if (--c->c_cdblen == 0) {
c->c_cdb = NULL;
c->c_cmdflags |= CMD_PENDING;
}
}
}
else {
++errors;
sswrite(test_unit_ready, ss_dat, sizeof(test_unit_ready), XP_COMMAND);
}
break;
case XP_DATAOUT:
dataout(c, &toflag);
break;
case XP_DATAIN:
datain(c, &toflag);
break;
/*******************************************************
* STATUS: Transfer the status byte from the last CDB
* to the Initiator. This allows us to figure out if
* the transfer went okay. An unexpected status byte
* is not treated as an error.
*/
case XP_STATUS:
if (errors) {
bb = DAT;
++errors;
}
else
c->c_scsistatus = DAT;
break;
/*******************************************************
* MSG_OUT: This is the last phase of an error
* condition. It should happen for perhaps a bus
* cyle or two before the target acknowledges the
* Abort message and drops the SCSI bus.
*/
case XP_MSGOUT:
if (errors < 3)
DAT = MSG_ABORT;
else
DAT = MSG_BUSDEVRST;
break;
/*******************************************************
* MSGIN: Get a message in from the device. There
* are actually only a handful that we care about
* but I'm sure that some will have to be added because
* there are some that require a response which will
* add some logic to the driver.
*/
case XP_MSGIN:
if (errors) {
bb = DAT;
++errors;
break;
}
msgin = DAT;
switch (msgin) {
/***********************************************
* Command Complete Message.
*/
case MSG_CC:
c->c_state = DEV_FINISHED;
c->c_cmdflags &= ~CMD_PENDING;
if (c->c_scsistatus == ST_BUSY)
c->c_cmdflags |= CMD_DEVBUSY;
if (c->c_scsistatus == ST_PENDING)
c->c_scsistatus = ST_GOOD;
done = 1;
break;
/***********************************************
* Disconnect Message.
*/
case MSG_DISCONNECT:
c->c_state = DEV_WORKING;
done = 1;
break;
case MSG_NOP:
case MSG_SAVEDPTR:
case MSG_RSTRDPTR:
break;
/***********************************************
* Whole host of messages that we could care
* less about.
*/
default:
if (!(msgin & MSG_IDENTIFY))
cmn_err(CE_NOTE, "haiss: Bad message 0x%x\n", msgin);
break;
} /* switch */
break;
default:
cmn_err(CE_WARN, "haiss: Invalid SCSI Bus Phase.");
++errors;
break;
} /* switch */
} /* while */
setcsr(HAIDLE);
if (errors != 0) {
c->c_state = DEV_FINISHED;
c->c_scsistatus = ST_ABRT;
r->hastat = HAST_BADBUSPH;
}
return (errors == 0);
} /* scsi_ioxfer() */
/***********************************************************************
* ssdone()
*/
#if __USE_PROTO__
LOCAL void ssdone(Register ssccb_p c)
#else
LOCAL void
ssdone(c)
Register ssccb_p c;
#endif
{
Register srb_p r = c->c_srb;
drvl[SCSIMAJOR].d_time &= ~(bit(c->c_target));
c->c_cmdflags = 0;
c->c_cdblen = 0;
c->c_cdb = NULL;
c->c_datain = c->c_dataout = NULL;
c->c_srb = NULL;
c->c_state = DEV_IDLE;
if (r) {
r->status = c->c_scsistatus;
if (r->cleanup)
(*(r->cleanup))(r);
}
} /* ssdone() */
/***********************************************************************
* sswork() -- Process work requests which require the SCSI
* Bus.
*
* This loop handles all work requeusts which require the SCSI Bus.
* This is needed because the SCSI bus may be busy when a device tries
* to start a command.
*
*
* Returns 1 if a new context was generated and 0 if not.
*/
#if __USE_PROTO__
LOCAL int sswork(void)
#else
LOCAL int
sswork()
#endif
{
Register ssccb_p q;
Register srb_p r;
static int buserrors = 0; /* Start buserrors at 0 */
static volatile int locked = 0; /* Activity flag */
int alldone = 0; /* Assume not! */
int s;
/***************************************************************
* It could get confusing if more than one context of this
* routine was loaded at any one time.I also cannot figure
* out a locking mechanism to for each call so I chose to
* do this here at the work loop.
*/
s = sphi();
if (locked) {
spl(s);
return 0;
}
else
++locked;
spl(s);
/***************************************************************
* Check here to see if the bus has been busy too long. If
* it has then do a SCSI Bus Reset. This will cancel all
* commands on all devices and return their states to normal.
*
* This removes two failure conditions:
* 1. When the bus is truly hung and needs to be reset.
*
* 2. When a third party SCSI command goes awry or
* takes to long and causes 1.
*/
if (buserrors > MAXBUSYCOUNT) {
cmn_err(CE_WARN, "haiss: SCSI bus hung, resetting");
hareset(HAIDLE);
buserrors = 0;
}
/***************************************************************
* Loop in here until there isn't any work to do or we can't
* get the bus. The order in here is for interrupt service
* when most of the time we will be processing normal
* requests.
*/
for ( ; ; ) {
/*******************************************************
* Come here to either start work anew or finish up
* some previous request. The busy could be in any
* phase here but we shouldn't be driving it at all.
*/
if ((q = priority_queue) != NULL) {
priority_queue = NULL;
r = q->c_srb;
/***********************************************
* Try the reselect. If it fails just drop
* the task. The device is the only thing
* that can initiate closure here.
*/
if (!scsi_reconnect()) {
set_host_status(r, HAST_SELTO);
break;
}
scsi_ioxfer(q);
s = sphi();
if (q->c_state == DEV_FINISHED)
ssdone(q);
buserrors = 0;
spl(s);
} /* if */
/*******************************************************
* Next start any normal work that needs to get done.
* This may be a mistake but so far all work requires
* the SCSI Bus. The arbitration code for the bus
* goes here.
*/
else if ((q = normal_queue) != NULL) {
r = q->c_srb;
/***********************************************
* So far all requests require arbitration
* so do it here. This also allows us to check
* for a reselect/arbitration colision here.
*/
if (!scsi_arbitrate(hostid)) {
if (reselect() && set_priority_queue())
continue;
else
break;
} /* if */
/***********************************************
* Only do one of the next three actions.
* Bus Device Reset is highest priority followed
* by Abort Command, followed by Start Command.
*/
if (q->c_reqflags & REQ_BUSDEVICERST) {
if (!scsi_connect(hostid, q->c_target, MSG_BUSDEVRST)) {
set_host_status(r, HAST_SELTO);
cmn_err(CE_WARN,
"haiss: SCSI Reset at ID %d failed (SELTO)",
q->c_target);
break;
}
q->c_state = DEV_FINISHED;
} /* else if */
else if (q->c_reqflags & REQ_ABORTSCSICMD) {
if (!scsi_connect(hostid, r->target, MSG_ABORT)) {
set_host_status(r, HAST_SELTO);
cmn_err(CE_WARN,
"haiss: Abort SCSI command failed (SELTO)");
break;
}
q->c_scsistatus = ST_DRVABRT;
q->c_state = DEV_FINISHED;
cmn_err(CE_NOTE,
"haiss: Command 0x%x at ID %d LUN %d aborted.",
r->cdb.g0.opcode,
r->target,
r->lun);
} /* else if */
else if (q->c_reqflags & REQ_STARTSCSICMD) {
if (!scsi_connect(hostid,
r->target,
MSG_IDENTIFY | ID_DISCONNECT | r->lun)) {
set_host_status(r, HAST_SELTO);
break;
}
scsi_ioxfer(q);
}
s = sphi();
q->c_reqflags = 0;
normal_queue = q->c_next;
q->c_next = NULL;
if (q->c_state == DEV_FINISHED)
ssdone(q);
buserrors = 0;
spl(s);
} /* else ... if */
/*******************************************************
* Finally if we get down here then there isn't any
* work to do so mark the alldone flag and exit.
*/
else {
alldone = 1;
break;
} /* else */
/*******************************************************
* At this point the host should be Idle. Wait like
* that for a bus settle delay or so to clear out
* any pending commands that the devices have finished.
*/
eventwait(NULL, 4 * BUSSETTLE_DLY);
} /* for */
/***************************************************************
* If this doesn't finish up all the work the bus is probably
* busy, do something else for a couple of cycles and come
* back here.
*/
if (!alldone) {
timeout(&sstim, 2, (void (*)()) &sswork, 0);
++buserrors;
}
locked = 0;
return 1;
} /* sswork() */
/***********************************************************************
* ssintr() -- Handle hardware interrupts.
*
* In the Implementations that I see of this card the SCSI Select line
* is tied to the interrupt generation hardware. External assertion
* of this line causes an interrupt to be generated. This will normally
* be caused by a SCSI reselect phase on the bus. The proper action
* here is to determine if a reconnect is happening and if so whether
* or not the host is the reselected device.
*/
#if __USE_PROTO__
LOCAL void ssintr(void)
#else
LOCAL void
ssintr()
#endif
{
/***************************************************************
* Interrupt check for a reselect the expected reselect phase.
*/
while (reselect()) {
/*******************************************************
* Try and set up the priority queue from the reselect
* info and then run with it.
*/
if (set_priority_queue()) {
if (!sswork())
break;
}
} /* while */
} /* ssintr() */
/***********************************************************************
* ssinsqueue() -- Insert a request into the driver's work queue.
*
* Insert a request into the driver's work queue as long as one's
* not there already.
*/
#if __USE_PROTO__
LOCAL void ssinsqueue(Register ssccb_p c, int newreqflags)
#else
LOCAL void
ssinsqueue(c, newreqflags)
Register ssccb_p c;
int newreqflags;
#endif
{
Register ssccb_p q;
int s;
s = sphi();
c->c_reqflags |= newreqflags;
if (!normal_queue) {
normal_queue = c;
}
else {
q = normal_queue;
for ( ; ; ) {
if (q == c) {
q = NULL;
break;
}
if (!q->c_next)
break;
else
q = q->c_next;
}
if (q) {
q->c_next = c;
c->c_next = NULL;
}
}
spl(s);
} /* ssinsqueue() */
/***********************************************************************
* ss_start() -- Start a scsi command.
*
* For this host adapter make sure the request is valid, set the
* flags and start the work queue.
*/
#if __USE_PROTO__
LOCAL int ss_start(Register srb_p r)
#else
LOCAL int
ss_start(r)
Register srb_p r;
#endif
{
Register ssccb_p c;
static int srbid = 0; /* Number of srb */
int s;
s = sphi();
++r->tries;
if (r->target >= MAXDEVS ||
r->lun >= MAXUNITS ||
!(c = ssdevs[r->target])) {
spl(s);
cmn_err(CE_WARN,
"haiss: Illegal device ID%d LUN%d\n",
r->target,
r->lun);
return 0;
}
if (c->c_state != DEV_IDLE) {
spl(s);
cmn_err(CE_WARN, "haiss: ID%d busy.", r->target);
return 0;
}
r->status = c->c_scsistatus = ST_INVDSRB;
spl(s);
c->c_srb = r;
c->c_cdb = &(r->cdb.g0.opcode);
switch (c->c_cdb[0] & GROUPMASK) {
case GROUP0:
c->c_cdblen = 6;
break;
case GROUP1:
case GROUP2:
c->c_cdblen = 10;
break;
case GROUP5:
c->c_cdblen = 12;
break;
default:
c->c_state = DEV_FINISHED;
c->c_cdb = NULL;
c->c_cdblen = 0;
r->hastat = HAST_INVDCDB;
return 0;
}
/***************************************************************
* Setup the data area.
*/
c->c_id = 0x10000000 | (r->target << 24) | (srbid++ & 0xffffff);
c->c_time = r->timeout;
c->c_cmdflags = 0;
if ((r->buf.space & WATCH_REQACK) != 0 ||
(haiss_slowdev & bit(c->c_target)) != 0)
c->c_cmdflags |= CMD_NEEDREQACK;
if (r->buf.ba_phys) {
switch (r->buf.space & SPACE_MASK) {
case PHYS_ADDR: /* Physical Address - (who knows) */
c->c_segcount = 1;
c->c_sgs[0].sgs_segstart = r->buf.ba_phys;
c->c_sgs[0].sgs_segsize = r->buf.size;
break;
case KRNL_ADDR: /* Kernel Address */
case USER_ADDR: /* User Address */
c->c_segcount = ukbuftosgl(r->buf.ba_virt,
r->buf.size,
&(c->c_sgs[0]),
MAXSEGCOUNT);
if (c->c_segcount == 0) {
cmn_err(CE_WARN, "haiss: Cannot create scatter/gather list.");
return 0;
}
break;
case SYSGLBL_ADDR: /* System Global address (yeah) */
c->c_segcount = sysgbuftosgl(r->buf.ba_phys,
r->buf.size,
&(c->c_sgs[0]),
MAXSEGCOUNT);
if (c->c_segcount == 0) {
cmn_err(CE_WARN, "haiss: Cannot create scatter/gather list.");
return 0;
}
break;
case SGLIST_ADDR: /* Scatter gather list */
if (r->buf.size > MAXSEGCOUNT) {
cmn_err(CE_WARN, "haiss: Cannot handle scatter/gather list.");
return 0;
}
else {
c->c_segcount = r->buf.size;
memcpy(&(c->c_sgs[0]),
r->buf.ba_sglist,
r->buf.size * sizeof(haisgsegm_t));
}
break;
} /* switch */
} /* if */
c->c_datain = c->c_dataout = NULL;
c->c_dataofs = 0;
if (r->xferdir & DMAWRITE) {
c->c_dataout = &(c->c_sgs[0]);
c->c_cmdflags |= CMD_OVERRUN;
} /* if */
else if (r->xferdir & DMAREAD) {
c->c_datain = &(c->c_sgs[0]);
c->c_cmdflags |= CMD_UNDERRUN;
} /* else - if */
r->status = c->c_scsistatus = ST_PENDING;
drvl[SCSIMAJOR].d_time |= bit(r->target);
ssinsqueue(c, REQ_STARTSCSICMD);
sswork();
return 1;
} /* ss_start() */
/***********************************************************************
* ss_abort() -- Abort a SCSI command in progress.
*
* Stop a scsi command on the bus or just drop one that's in a long
* retry loop.
*/
#if __USE_PROTO__
LOCAL void ss_abort(Register srb_p r)
#else
void
LOCAL ss_abort(r)
Register srb_p r;
#endif
{
Register ssccb_p c;
Register int s;
s = sphi();
if (!r || r->target >= MAXDEVS || r->lun >= MAXUNITS)
return;
if (!(c = ssdevs[r->target]) || c->c_state != DEV_WORKING || c->c_srb != r)
return;
cmn_err(CE_NOTE,
"haiss: Abort SCSI command ID: %d LUN: %d opcode 0x%x",
r->target,
r->lun,
r->cdb.g0.opcode);
if (!(c->c_cmdflags & CMD_PENDING))
return;
ssinsqueue(c, REQ_ABORTSCSICMD);
sswork();
spl(s);
} /* ss_abort() */
/***********************************************************************
* ss_bdr() -- Reset a SCSI device.
*
* Send a SCSI device the Bus Device Reset message. The should cause
* the device to go through it's power on reset sequence of operations
* and then come back to a known state.
*/
#if __USE_PROTO__
LOCAL void ss_bdr(Register int target)
#else
LOCAL void
ss_bdr(target)
Register int target;
#endif
{
Register ssccb_p c;
int s;
s = sphi();
if (target >= MAXDEVS || !(c = ssdevs[target]))
return;
ssinsqueue(c, REQ_BUSDEVICERST);
sswork();
spl(s);
return;
} /* ss_bdr() */
#if __USE_PROTO__
LOCAL void ss_timer(void)
#else
LOCAL void
ss_timer()
#endif
{
Register ssccb_p c;
Register int id;
int s;
for (id = 0 ; id < MAXDEVS ; ++id) {
if ((c = ssdevs[id]) != NULL) {
s = sphi();
if ((drvl[SCSIMAJOR].d_time & bit(id)) &&
c->c_time != 0 &&
--(c->c_time) == 0) {
spl(s);
if (c->c_srb) {
cmn_err(CE_WARN, "haiss: timeout at ID %d", c->c_target);
ss_abort(c->c_srb);
}
}
else
spl(s);
}
}
return;
} /* hatimer() */
/*
* The host adapter function table.
*/
haft_t ss_haft = {
0,
ss_timer, /* Timeout handler */
ss_load, /* Initialization routine */
ss_unload, /* shutdown routine */
ss_start, /* Start a SCSI command from an srb */
ss_abort, /* Abort a SCSI command from an srb */
ss_bdr /* Reset a scsi device */
};