patch-2.4.3 linux/drivers/scsi/aic7xxx/aic7xxx.seq
Next file: linux/drivers/scsi/aic7xxx/aic7xxx_93cx6.c
Previous file: linux/drivers/scsi/aic7xxx/aic7xxx.reg
Back to the patch index
Back to the overall index
- Lines: 2902
- Date:
Sun Mar 4 14:30:18 2001
- Orig file:
v2.4.2/linux/drivers/scsi/aic7xxx/aic7xxx.seq
- Orig date:
Wed Jun 21 17:25:03 2000
diff -u --recursive --new-file v2.4.2/linux/drivers/scsi/aic7xxx/aic7xxx.seq linux/drivers/scsi/aic7xxx/aic7xxx.seq
@@ -1,7 +1,7 @@
/*
* Adaptec 274x/284x/294x device driver firmware for Linux and FreeBSD.
*
- * Copyright (c) 1994-1999 Justin Gibbs.
+ * Copyright (c) 1994-2001 Justin Gibbs.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -9,16 +9,12 @@
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions, and the following disclaimer,
- * without modification, immediately at the beginning of the file.
+ * without modification.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
- * Where this Software is combined with software released under the terms of
- * the GNU Public License (GPL) and the terms of the GPL would require the
- * combined work to also be released under the terms of the GPL, the terms
- * and conditions of this License will apply in addition to those of the
- * GPL with the exception of any terms or conditions of this License that
- * conflict with, or are expressly prohibited by, the GPL.
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU Public License ("GPL").
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
@@ -32,7 +28,9 @@
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: aic7xxx.seq,v 1.77 1998/06/28 02:58:57 gibbs Exp $
+ * $Id: //depot/src/aic7xxx/aic7xxx.seq#22 $
+ *
+ * $FreeBSD: src/sys/dev/aic7xxx/aic7xxx.seq,v 1.106 2000/11/12 05:19:46 gibbs Exp $
*/
#include "aic7xxx.reg"
@@ -48,7 +46,7 @@
* a later time. This problem cannot be resolved by holding a single entry
* in scratch ram since a reconnecting target can request sense and this will
* create yet another SCB waiting for selection. The solution used here is to
- * use byte 27 of the SCB as a pseudo-next pointer and to thread a list
+ * use byte 27 of the SCB as a psuedo-next pointer and to thread a list
* of SCBs that are awaiting selection. Since 0-0xfe are valid SCB indexes,
* SCB_LIST_NULL is 0xff which is out of range. An entry is also added to
* this list everytime a request sense occurs or after completing a non-tagged
@@ -56,200 +54,386 @@
* automatically consume the entries.
*/
-reset:
- clr SCSISIGO; /* De-assert BSY */
- and SXFRCTL1, ~BITBUCKET;
- /* Always allow reselection */
- mvi SCSISEQ, ENRSELI|ENAUTOATNP;
-
- if ((p->features & AHC_CMD_CHAN) != 0) {
- /* Ensure that no DMA operations are in progress */
- clr CCSGCTL;
- clr CCSCBCTL;
- }
-
- call clear_target_state;
+bus_free_sel:
+ /*
+ * Turn off the selection hardware. We need to reset the
+ * selection request in order to perform a new selection.
+ */
+ and SCSISEQ, TEMODE|ENSELI|ENRSELI|ENAUTOATNP, SCSISEQ;
+ and SIMODE1, ~ENBUSFREE;
poll_for_work:
+ call clear_target_state;
and SXFRCTL0, ~SPIOEN;
- if ((p->features & AHC_QUEUE_REGS) == 0) {
- mov A, QINPOS;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ clr SCSIBUSL;
}
-poll_for_work_loop:
- if ((p->features & AHC_QUEUE_REGS) == 0) {
- and SEQCTL, ~PAUSEDIS;
- }
- test SSTAT0, SELDO|SELDI jnz selection;
- test SCSISEQ, ENSELO jnz poll_for_work;
- if ((p->features & AHC_TWIN) != 0) {
- /*
- * Twin channel devices cannot handle things like SELTO
- * interrupts on the "background" channel. So, if we
- * are selecting, keep polling the current channel util
- * either a selection or reselection occurs.
- */
+ test SCSISEQ, ENSELO jnz poll_for_selection;
+ if ((ahc->features & AHC_TWIN) != 0) {
xor SBLKCTL,SELBUSB; /* Toggle to the other bus */
- test SSTAT0, SELDO|SELDI jnz selection;
- test SCSISEQ, ENSELO jnz poll_for_work;
- xor SBLKCTL,SELBUSB; /* Toggle back */
+ test SCSISEQ, ENSELO jnz poll_for_selection;
}
cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting;
+poll_for_work_loop:
+ if ((ahc->features & AHC_TWIN) != 0) {
+ xor SBLKCTL,SELBUSB; /* Toggle to the other bus */
+ }
+ test SSTAT0, SELDO|SELDI jnz selection;
test_queue:
/* Has the driver posted any work for us? */
- if ((p->features & AHC_QUEUE_REGS) != 0) {
+BEGIN_CRITICAL
+ if ((ahc->features & AHC_QUEUE_REGS) != 0) {
test QOFF_CTLSTA, SCB_AVAIL jz poll_for_work_loop;
- mov NONE, SNSCB_QOFF;
- inc QINPOS;
} else {
- or SEQCTL, PAUSEDIS;
+ mov A, QINPOS;
cmp KERNEL_QINPOS, A je poll_for_work_loop;
- inc QINPOS;
- and SEQCTL, ~PAUSEDIS;
}
+ mov ARG_1, NEXT_QUEUED_SCB;
-/*
- * We have at least one queued SCB now and we don't have any
- * SCBs in the list of SCBs awaiting selection. If we have
- * any SCBs available for use, pull the tag from the QINFIFO
- * and get to work on it.
- */
- if ((p->flags & AHC_PAGESCBS) != 0) {
+ /*
+ * We have at least one queued SCB now and we don't have any
+ * SCBs in the list of SCBs awaiting selection. Allocate a
+ * card SCB for the host's SCB and get to work on it.
+ */
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
mov ALLZEROS call get_free_or_disc_scb;
- }
-
-dequeue_scb:
- add A, -1, QINPOS;
- mvi QINFIFO_OFFSET call fetch_byte;
-
- if ((p->flags & AHC_PAGESCBS) == 0) {
+ } else {
/* In the non-paging case, the SCBID == hardware SCB index */
- mov SCBPTR, RETURN_2;
+ mov SCBPTR, ARG_1;
}
+ or SEQ_FLAGS2, SCB_DMA;
+END_CRITICAL
dma_queued_scb:
-/*
- * DMA the SCB from host ram into the current SCB location.
- */
+ /*
+ * DMA the SCB from host ram into the current SCB location.
+ */
mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET;
- mov RETURN_2 call dma_scb;
-
-/*
- * Preset the residual fields in case we never go through a data phase.
- * This isn't done by the host so we can avoid a DMA to clear these
- * fields for the normal case of I/O that completes without underrun
- * or overrun conditions.
- */
- if ((p->features & AHC_CMD_CHAN) != 0) {
- bmov SCB_RESID_DCNT, SCB_DATACNT, 3;
- } else {
- mov SCB_RESID_DCNT[0],SCB_DATACNT[0];
- mov SCB_RESID_DCNT[1],SCB_DATACNT[1];
- mov SCB_RESID_DCNT[2],SCB_DATACNT[2];
- }
- mov SCB_RESID_SGCNT, SCB_SGCOUNT;
-
-start_scb:
+ mov ARG_1 call dma_scb;
/*
- * Place us on the waiting list in case our selection
- * doesn't win during bus arbitration.
+ * Check one last time to see if this SCB was canceled
+ * before we completed the DMA operation. If it was,
+ * the QINFIFO next pointer will not match our saved
+ * value.
*/
+ mov A, ARG_1;
+BEGIN_CRITICAL
+ cmp NEXT_QUEUED_SCB, A jne abort_qinscb;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ cmp SCB_TAG, A je . + 2;
+ mvi SCB_MISMATCH call set_seqint;
+ }
+ mov NEXT_QUEUED_SCB, SCB_NEXT;
mov SCB_NEXT,WAITING_SCBH;
mov WAITING_SCBH, SCBPTR;
+ if ((ahc->features & AHC_QUEUE_REGS) != 0) {
+ mov NONE, SNSCB_QOFF;
+ } else {
+ inc QINPOS;
+ }
+ and SEQ_FLAGS2, ~SCB_DMA;
+END_CRITICAL
start_waiting:
/*
- * Pull the first entry off of the waiting SCB list.
+ * Start the first entry on the waiting SCB list.
*/
mov SCBPTR, WAITING_SCBH;
call start_selection;
- jmp poll_for_work;
+
+poll_for_selection:
+ /*
+ * Twin channel devices cannot handle things like SELTO
+ * interrupts on the "background" channel. So, while
+ * selecting, keep polling the current channel until
+ * either a selection or reselection occurs.
+ */
+ test SSTAT0, SELDO|SELDI jz poll_for_selection;
+
+selection:
+ /*
+ * We aren't expecting a bus free, so interrupt
+ * the kernel driver if it happens.
+ */
+ mvi CLRSINT1,CLRBUSFREE;
+ or SIMODE1, ENBUSFREE;
+
+ /*
+ * Guard against a bus free after (re)selection
+ * but prior to enabling the busfree interrupt. SELDI
+ * and SELDO will be cleared in that case.
+ */
+ test SSTAT0, SELDI|SELDO jz bus_free_sel;
+ test SSTAT0,SELDO jnz select_out;
+select_in:
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ if ((ahc->flags & AHC_INITIATORROLE) != 0) {
+ test SSTAT0, TARGET jz initiator_reselect;
+ }
+ mvi CLRSINT0, CLRSELDI;
+
+ /*
+ * We've just been selected. Assert BSY and
+ * setup the phase for receiving messages
+ * from the target.
+ *
+ * If bus reset interrupts have been disabled (from a
+ * previous reset), re-enable them now. Resets are only
+ * of interest when we have outstanding transactions, so
+ * we can safely defer re-enabling the interrupt until,
+ * as a target, we start receiving transactions again.
+ */
+ test SIMODE1, ENSCSIRST jnz . + 3;
+ mvi CLRSINT1, CLRSCSIRSTI;
+ or SIMODE1, ENSCSIRST;
+ mvi SCSISIGO, P_MESGOUT|BSYO;
+
+ /*
+ * Setup the DMA for sending the identify and
+ * command information.
+ */
+ or SEQ_FLAGS, CMDPHASE_PENDING;
+
+ mov A, TQINPOS;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mvi DINDEX, CCHADDR;
+ mvi SHARED_DATA_ADDR call set_32byte_addr;
+ mvi CCSCBCTL, CCSCBRESET;
+ } else {
+ mvi DINDEX, HADDR;
+ mvi SHARED_DATA_ADDR call set_32byte_addr;
+ mvi DFCNTRL, FIFORESET;
+ }
+
+ /* Initiator that selected us */
+ and SAVED_SCSIID, SELID_MASK, SELID;
+ /* The Target ID we were selected at */
+ if ((ahc->features & AHC_MULTI_TID) != 0) {
+ and A, OID, TARGIDIN;
+ } else if ((ahc->features & AHC_ULTRA2) != 0) {
+ and A, OID, SCSIID_ULTRA2;
+ } else {
+ and A, OID, SCSIID;
+ }
+ or SAVED_SCSIID, A;
+ if ((ahc->features & AHC_TWIN) != 0) {
+ test SBLKCTL, SELBUSB jz . + 2;
+ or SAVED_SCSIID, TWIN_CHNLB;
+ }
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, SAVED_SCSIID;
+ } else {
+ mov DFDAT, SAVED_SCSIID;
+ }
+
+ /*
+ * If ATN isn't asserted, the target isn't interested
+ * in talking to us. Go directly to bus free.
+ * XXX SCSI-1 may require us to assume lun 0 if
+ * ATN is false.
+ */
+ test SCSISIGI, ATNI jz target_busfree;
+
+ /*
+ * Watch ATN closely now as we pull in messages from the
+ * initiator. We follow the guidlines from section 6.5
+ * of the SCSI-2 spec for what messages are allowed when.
+ */
+ call target_inb;
+
+ /*
+ * Our first message must be one of IDENTIFY, ABORT, or
+ * BUS_DEVICE_RESET.
+ */
+ test DINDEX, MSG_IDENTIFYFLAG jz host_target_message_loop;
+ /* Store for host */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, DINDEX;
+ } else {
+ mov DFDAT, DINDEX;
+ }
+
+ /* Remember for disconnection decision */
+ test DINDEX, MSG_IDENTIFY_DISCFLAG jnz . + 2;
+ /* XXX Honor per target settings too */
+ or SEQ_FLAGS, NO_DISCONNECT;
+
+ test SCSISIGI, ATNI jz ident_messages_done;
+ call target_inb;
+ /*
+ * If this is a tagged request, the tagged message must
+ * immediately follow the identify. We test for a valid
+ * tag message by seeing if it is >= MSG_SIMPLE_Q_TAG and
+ * < MSG_IGN_WIDE_RESIDUE.
+ */
+ add A, -MSG_SIMPLE_Q_TAG, DINDEX;
+ jnc ident_messages_done;
+ add A, -MSG_IGN_WIDE_RESIDUE, DINDEX;
+ jc ident_messages_done;
+ /* Store for host */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, DINDEX;
+ } else {
+ mov DFDAT, DINDEX;
+ }
+
+ /*
+ * If the initiator doesn't feel like providing a tag number,
+ * we've got a failed selection and must transition to bus
+ * free.
+ */
+ test SCSISIGI, ATNI jz target_busfree;
+
+ /*
+ * Store the tag for the host.
+ */
+ call target_inb;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, DINDEX;
+ } else {
+ mov DFDAT, DINDEX;
+ }
+ mov INITIATOR_TAG, DINDEX;
+ or SEQ_FLAGS, TARGET_CMD_IS_TAGGED;
+ test SCSISIGI, ATNI jz . + 2;
+ /* Initiator still wants to give us messages */
+ call target_inb;
+ jmp ident_messages_done;
+
+ /*
+ * Pushed message loop to allow the kernel to
+ * run it's own target mode message state engine.
+ */
+host_target_message_loop:
+ mvi HOST_MSG_LOOP call set_seqint;
+ cmp RETURN_1, EXIT_MSG_LOOP je target_ITloop;
+ test SSTAT0, SPIORDY jz .;
+ jmp host_target_message_loop;
+
+ident_messages_done:
+ /* If ring buffer is full, return busy or queue full */
+ if ((ahc->features & AHC_HS_MAILBOX) != 0) {
+ and A, HOST_TQINPOS, HS_MAILBOX;
+ } else {
+ mov A, KERNEL_TQINPOS;
+ }
+ cmp TQINPOS, A jne tqinfifo_has_space;
+ mvi P_STATUS|BSYO call change_phase;
+ test SEQ_FLAGS, TARGET_CMD_IS_TAGGED jz . + 3;
+ mvi STATUS_QUEUE_FULL call target_outb;
+ jmp target_busfree_wait;
+ mvi STATUS_BUSY call target_outb;
+ jmp target_busfree_wait;
+tqinfifo_has_space:
+ /* Terminate the ident list */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mvi CCSCBRAM, SCB_LIST_NULL;
+ } else {
+ mvi DFDAT, SCB_LIST_NULL;
+ }
+ or SEQ_FLAGS, TARG_CMD_PENDING|IDENTIFY_SEEN;
+ test SCSISIGI, ATNI jnz target_mesgout_pending;
+ jmp target_ITloop;
+ }
+
+if ((ahc->flags & AHC_INITIATORROLE) != 0) {
+/*
+ * Reselection has been initiated by a target. Make a note that we've been
+ * reselected, but haven't seen an IDENTIFY message from the target yet.
+ */
+initiator_reselect:
+ /* XXX test for and handle ONE BIT condition */
+ or SXFRCTL0, SPIOEN|CLRSTCNT|CLRCHN;
+ and SAVED_SCSIID, SELID_MASK, SELID;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ and A, OID, SCSIID_ULTRA2;
+ } else {
+ and A, OID, SCSIID;
+ }
+ or SAVED_SCSIID, A;
+ if ((ahc->features & AHC_TWIN) != 0) {
+ test SBLKCTL, SELBUSB jz . + 2;
+ or SAVED_SCSIID, TWIN_CHNLB;
+ }
+ mvi CLRSINT0, CLRSELDI;
+ jmp ITloop;
+}
+
+abort_qinscb:
+ call add_scb_to_free_list;
+ jmp poll_for_work_loop;
start_selection:
- if ((p->features & AHC_TWIN) != 0) {
+ /*
+ * If bus reset interrupts have been disabled (from a previous
+ * reset), re-enable them now. Resets are only of interest
+ * when we have outstanding transactions, so we can safely
+ * defer re-enabling the interrupt until, as an initiator,
+ * we start sending out transactions again.
+ */
+ test SIMODE1, ENSCSIRST jnz . + 3;
+ mvi CLRSINT1, CLRSCSIRSTI;
+ or SIMODE1, ENSCSIRST;
+ if ((ahc->features & AHC_TWIN) != 0) {
and SINDEX,~SELBUSB,SBLKCTL;/* Clear channel select bit */
- and A,SELBUSB,SCB_TCL; /* Get new channel bit */
- or SINDEX,A;
+ test SCB_SCSIID, TWIN_CHNLB jz . + 2;
+ or SINDEX, SELBUSB;
mov SBLKCTL,SINDEX; /* select channel */
}
initialize_scsiid:
- if ((p->features & AHC_ULTRA2) != 0) {
- and A, TID, SCB_TCL; /* Get target ID */
- and SCSIID_ULTRA2, OID; /* Clear old target */
- or SCSIID_ULTRA2, A;
- } else {
- and A, TID, SCB_TCL; /* Get target ID */
- and SCSIID, OID; /* Clear old target */
- or SCSIID, A;
- }
- mov SCSIDATL, ALLZEROS; /* clear out the latched */
- /* data register, this */
- /* fixes a bug on some */
- /* controllers where the */
- /* last byte written to */
- /* this register can leak */
- /* onto the data bus at */
- /* bad times, such as during */
- /* selection timeouts */
- mvi SCSISEQ, ENSELO|ENAUTOATNO|ENRSELI|ENAUTOATNP ret;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ mov SCSIID_ULTRA2, SCB_SCSIID;
+ } else if ((ahc->features & AHC_TWIN) != 0) {
+ and SCSIID, TWIN_TID|OID, SCB_SCSIID;
+ } else {
+ mov SCSIID, SCB_SCSIID;
+ }
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ mov SINDEX, SCSISEQ_TEMPLATE;
+ test SCB_CONTROL, TARGET_SCB jz . + 2;
+ or SINDEX, TEMODE;
+ mov SCSISEQ, SINDEX ret;
+ } else {
+ mov SCSISEQ, SCSISEQ_TEMPLATE ret;
+ }
/*
- * Initialize Ultra mode setting and clear the SCSI channel.
+ * Initialize transfer settings and clear the SCSI channel.
* SINDEX should contain any additional bit's the client wants
- * set in SXFRCTL0.
+ * set in SXFRCTL0. We also assume that the current SCB is
+ * a valid SCB for the target we wish to talk to.
*/
initialize_channel:
- or SXFRCTL0, CLRSTCNT|CLRCHN, SINDEX;
- if ((p->features & AHC_ULTRA) != 0) {
-ultra:
- mvi SINDEX, ULTRA_ENB+1;
- test SAVED_TCL, 0x80 jnz ultra_2; /* Target ID > 7 */
- dec SINDEX;
-ultra_2:
- mov FUNCTION1,SAVED_TCL;
- mov A,FUNCTION1;
- test SINDIR, A jz ndx_dtr;
+ or SXFRCTL0, SPIOEN|CLRSTCNT|CLRCHN;
+set_transfer_settings:
+ if ((ahc->features & AHC_ULTRA) != 0) {
+ test SCB_CONTROL, ULTRAENB jz . + 2;
or SXFRCTL0, FAST20;
}
-/*
- * Initialize SCSIRATE with the appropriate value for this target.
- * The SCSIRATE settings for each target are stored in an array
- * based at TARG_SCSIRATE.
- */
-ndx_dtr:
- shr A,4,SAVED_TCL;
- if ((p->features & AHC_TWIN) != 0) {
- test SBLKCTL,SELBUSB jz ndx_dtr_2;
- or SAVED_TCL, SELBUSB;
- or A,0x08; /* Channel B entries add 8 */
-ndx_dtr_2:
- }
-
- if ((p->features & AHC_ULTRA2) != 0) {
- add SINDEX, TARG_OFFSET, A;
- mov SCSIOFFSET, SINDIR;
+ /*
+ * Initialize SCSIRATE with the appropriate value for this target.
+ */
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ bmov SCSIRATE, SCB_SCSIRATE, 2 ret;
+ } else {
+ mov SCSIRATE, SCB_SCSIRATE ret;
}
- add SINDEX,TARG_SCSIRATE,A;
- mov SCSIRATE,SINDIR ret;
-
-
-selection:
- test SSTAT0,SELDO jnz select_out;
+if ((ahc->flags & AHC_TARGETROLE) != 0) {
/*
- * Reselection has been initiated by a target. Make a note that we've been
- * reselected, but haven't seen an IDENTIFY message from the target yet.
+ * We carefully toggle SPIOEN to allow us to return the
+ * message byte we receive so it can be checked prior to
+ * driving REQ on the bus for the next byte.
*/
-initiator_reselect:
- mvi CLRSINT0, CLRSELDI;
- /* XXX test for and handle ONE BIT condition */
- and SAVED_TCL, SELID_MASK, SELID;
- mvi CLRSINT1,CLRBUSFREE;
- or SIMODE1, ENBUSFREE; /*
- * We aren't expecting a
- * bus free, so interrupt
- * the kernel driver if it
- * happens.
- */
- mvi SPIOEN call initialize_channel;
- mvi MSG_OUT, MSG_NOOP; /* No message to send */
- jmp ITloop;
+target_inb:
+ /*
+ * Drive REQ on the bus by enabling SCSI PIO.
+ */
+ or SXFRCTL0, SPIOEN;
+ /* Wait for the byte */
+ test SSTAT0, SPIORDY jz .;
+ /* Prevent our read from triggering another REQ */
+ and SXFRCTL0, ~SPIOEN;
+ /* Save latched contents */
+ mov DINDEX, SCSIDATL ret;
+}
/*
* After the selection, remove this SCB from the "waiting SCB"
@@ -259,33 +443,198 @@
*/
select_out:
/* Turn off the selection hardware */
- mvi SCSISEQ, ENRSELI|ENAUTOATNP; /*
- * ATN on parity errors
- * for "in" phases
- */
+ and SCSISEQ, TEMODE|ENSELI|ENRSELI|ENAUTOATNP, SCSISEQ;
mvi CLRSINT0, CLRSELDO;
mov SCBPTR, WAITING_SCBH;
mov WAITING_SCBH,SCB_NEXT;
- mov SAVED_TCL, SCB_TCL;
- mvi CLRSINT1,CLRBUSFREE;
- or SIMODE1, ENBUSFREE; /*
- * We aren't expecting a
- * bus free, so interrupt
- * the kernel driver if it
- * happens.
- */
- mvi SPIOEN call initialize_channel;
-/*
- * As soon as we get a successful selection, the target should go
- * into the message out phase since we have ATN asserted.
- */
+ mov SAVED_SCSIID, SCB_SCSIID;
+ mov SAVED_LUN, SCB_LUN;
+ call initialize_channel;
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ test SSTAT0, TARGET jz initiator_select;
+
+ /*
+ * We've just re-selected an initiator.
+ * Assert BSY and setup the phase for
+ * sending our identify messages.
+ */
+ mvi P_MESGIN|BSYO call change_phase;
+
+ /*
+ * Start out with a simple identify message.
+ */
+ or SCB_LUN, MSG_IDENTIFYFLAG call target_outb;
+
+ /*
+ * If we are the result of a tagged command, send
+ * a simple Q tag and the tag id.
+ */
+ test SCB_CONTROL, TAG_ENB jz . + 3;
+ mvi MSG_SIMPLE_Q_TAG call target_outb;
+ mov SCB_TARGET_INFO[SCB_INITIATOR_TAG] call target_outb;
+target_synccmd:
+ /*
+ * Now determine what phases the host wants us
+ * to go through.
+ */
+ mov SEQ_FLAGS, SCB_TARGET_INFO[SCB_TARGET_PHASES];
+
+target_ITloop:
+ /*
+ * Start honoring ATN signals now that
+ * we properly identified ourselves.
+ */
+ test SCSISIGI, ATNI jnz target_mesgout;
+ test SEQ_FLAGS, CMDPHASE_PENDING jnz target_cmdphase;
+ test SEQ_FLAGS, DPHASE_PENDING jnz target_dphase;
+ test SEQ_FLAGS, SPHASE_PENDING jnz target_sphase;
+
+ /*
+ * No more work to do. Either disconnect or not depending
+ * on the state of NO_DISCONNECT.
+ */
+ test SEQ_FLAGS, NO_DISCONNECT jz target_disconnect;
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ mov ALLZEROS call get_free_or_disc_scb;
+ }
+ mov RETURN_1, ALLZEROS;
+ call complete_target_cmd;
+ cmp RETURN_1, CONT_MSG_LOOP jne .;
+ mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET;
+ mov SCB_TAG call dma_scb;
+ jmp target_synccmd;
+
+target_mesgout:
+ mvi SCSISIGO, P_MESGOUT|BSYO;
+target_mesgout_continue:
+ call target_inb;
+target_mesgout_pending:
+ /* Local Processing goes here... */
+ jmp host_target_message_loop;
+
+target_disconnect:
+ mvi P_MESGIN|BSYO call change_phase;
+ test SEQ_FLAGS, DPHASE jz . + 2;
+ mvi MSG_SAVEDATAPOINTER call target_outb;
+ mvi MSG_DISCONNECT call target_outb;
+
+target_busfree_wait:
+ /* Wait for preceeding I/O session to complete. */
+ test SCSISIGI, ACKI jnz .;
+target_busfree:
+ and SIMODE1, ~ENBUSFREE;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ clr SCSIBUSL;
+ }
+ clr SCSISIGO;
+ mvi LASTPHASE, P_BUSFREE;
+ call complete_target_cmd;
+ jmp poll_for_work;
+
+target_cmdphase:
+ mvi P_COMMAND|BSYO call change_phase;
+ call target_inb;
+ mov A, DINDEX;
+ /* Store for host */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, A;
+ } else {
+ mov DFDAT, A;
+ }
+
+ /*
+ * Determine the number of bytes to read
+ * based on the command group code via table lookup.
+ * We reuse the first 8 bytes of the TARG_SCSIRATE
+ * BIOS array for this table. Count is one less than
+ * the total for the command since we've already fetched
+ * the first byte.
+ */
+ shr A, CMD_GROUP_CODE_SHIFT;
+ add SINDEX, CMDSIZE_TABLE, A;
+ mov A, SINDIR;
+
+ test A, 0xFF jz command_phase_done;
+ or SXFRCTL0, SPIOEN;
+command_loop:
+ test SSTAT0, SPIORDY jz .;
+ cmp A, 1 jne . + 2;
+ and SXFRCTL0, ~SPIOEN; /* Last Byte */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ mov CCSCBRAM, SCSIDATL;
+ } else {
+ mov DFDAT, SCSIDATL;
+ }
+ dec A;
+ test A, 0xFF jnz command_loop;
+
+command_phase_done:
+ and SEQ_FLAGS, ~CMDPHASE_PENDING;
+ jmp target_ITloop;
+
+target_dphase:
+ /*
+ * Data phases on the bus are from the
+ * perspective of the initiator. The dma
+ * code looks at LASTPHASE to determine the
+ * data direction of the DMA. Toggle it for
+ * target transfers.
+ */
+ xor LASTPHASE, IOI, SCB_TARGET_INFO[SCB_TARGET_DATA_DIR];
+ or SCB_TARGET_INFO[SCB_TARGET_DATA_DIR], BSYO
+ call change_phase;
+ jmp p_data;
+
+target_sphase:
+ mvi P_STATUS|BSYO call change_phase;
+ mvi LASTPHASE, P_STATUS;
+ mov SCB_TARGET_INFO[SCB_TARGET_STATUS] call target_outb;
+ /* XXX Watch for ATN or parity errors??? */
+ mvi SCSISIGO, P_MESGIN|BSYO;
+ /* MSG_CMDCMPLT is 0, but we can't do an immediate of 0 */
+ mov ALLZEROS call target_outb;
+ jmp target_busfree_wait;
+
+complete_target_cmd:
+ test SEQ_FLAGS, TARG_CMD_PENDING jnz . + 2;
+ mov SCB_TAG jmp complete_post;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ /* Set the valid byte */
+ mvi CCSCBADDR, 24;
+ mov CCSCBRAM, ALLONES;
+ mvi CCHCNT, 28;
+ or CCSCBCTL, CCSCBEN|CCSCBRESET;
+ test CCSCBCTL, CCSCBDONE jz .;
+ clr CCSCBCTL;
+ } else {
+ /* Set the valid byte */
+ or DFCNTRL, FIFORESET;
+ mvi DFWADDR, 3; /* Third 64bit word or byte 24 */
+ mov DFDAT, ALLONES;
+ mvi 28 call set_hcnt;
+ or DFCNTRL, HDMAEN|FIFOFLUSH;
+ call dma_finish;
+ }
+ inc TQINPOS;
+ mvi INTSTAT,CMDCMPLT ret;
+ }
+
+if ((ahc->flags & AHC_INITIATORROLE) != 0) {
+initiator_select:
+ /*
+ * As soon as we get a successful selection, the target
+ * should go into the message out phase since we have ATN
+ * asserted.
+ */
mvi MSG_OUT, MSG_IDENTIFYFLAG;
or SEQ_FLAGS, IDENTIFY_SEEN;
-/*
- * Main loop for information transfer phases. Wait for the target
- * to assert REQ before checking MSG, C/D and I/O for the bus phase.
- */
+ /*
+ * Main loop for information transfer phases. Wait for the
+ * target to assert REQ before checking MSG, C/D and I/O for
+ * the bus phase.
+ */
+mesgin_phasemis:
ITloop:
call phase_lock;
@@ -297,17 +646,20 @@
cmp A,P_STATUS je p_status;
cmp A,P_MESGIN je p_mesgin;
- mvi INTSTAT,BAD_PHASE; /* unknown phase - signal driver */
+ mvi BAD_PHASE call set_seqint;
jmp ITloop; /* Try reading the bus again. */
await_busfree:
and SIMODE1, ~ENBUSFREE;
- call clear_target_state;
mov NONE, SCSIDATL; /* Ack the last byte */
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ clr SCSIBUSL; /* Prevent bit leakage durint SELTO */
+ }
and SXFRCTL0, ~SPIOEN;
test SSTAT1,REQINIT|BUSFREE jz .;
test SSTAT1, BUSFREE jnz poll_for_work;
- mvi INTSTAT, BAD_PHASE;
+ mvi MISSED_BUSFREE call set_seqint;
+}
clear_target_state:
/*
@@ -316,42 +668,160 @@
* clear DFCNTRL too.
*/
clr DFCNTRL;
+ or SXFRCTL0, CLRSTCNT|CLRCHN;
/*
* We don't know the target we will connect to,
* so default to narrow transfers to avoid
* parity problems.
*/
- if ((p->features & AHC_ULTRA2) != 0) {
- bmov SCSIRATE, ALLZEROS, 2;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ bmov SCSIRATE, ALLZEROS, 2;
} else {
- clr SCSIRATE;
- and SXFRCTL0, ~(FAST20);
+ clr SCSIRATE;
+ if ((ahc->features & AHC_ULTRA) != 0) {
+ and SXFRCTL0, ~(FAST20);
+ }
}
mvi LASTPHASE, P_BUSFREE;
/* clear target specific flags */
clr SEQ_FLAGS ret;
+sg_advance:
+ clr A; /* add sizeof(struct scatter) */
+ add SCB_RESIDUAL_SGPTR[0],SG_SIZEOF;
+ adc SCB_RESIDUAL_SGPTR[1],A;
+ adc SCB_RESIDUAL_SGPTR[2],A;
+ adc SCB_RESIDUAL_SGPTR[3],A ret;
+
+if ((ahc->features & AHC_CMD_CHAN) != 0) {
+disable_ccsgen:
+ test CCSGCTL, CCSGEN jz return;
+ test CCSGCTL, CCSGDONE jz .;
+disable_ccsgen_fetch_done:
+ clr CCSGCTL;
+ test CCSGCTL, CCSGEN jnz .;
+ ret;
+idle_loop:
+ /* Did we just finish fetching segs? */
+ cmp CCSGCTL, CCSGEN|CCSGDONE je idle_sgfetch_complete;
+
+ /* Are we actively fetching segments? */
+ test CCSGCTL, CCSGEN jnz return;
+
+ /*
+ * Do we need any more segments?
+ */
+ test SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG jnz return;
+
+ /*
+ * Do we have any prefetch left???
+ */
+ cmp CCSGADDR, SG_PREFETCH_CNT jne idle_sg_avail;
+
+ /*
+ * Need to fetch segments, but we can only do that
+ * if the command channel is completely idle. Make
+ * sure we don't have an SCB prefetch going on.
+ */
+ test CCSCBCTL, CCSCBEN jnz return;
+
+ /*
+ * We fetch a "cacheline aligned" and sized amount of data
+ * so we don't end up referencing a non-existant page.
+ * Cacheline aligned is in quotes because the kernel will
+ * set the prefetch amount to a reasonable level if the
+ * cacheline size is unknown.
+ */
+ mvi CCHCNT, SG_PREFETCH_CNT;
+ and CCHADDR[0], SG_PREFETCH_ALIGN_MASK, SCB_RESIDUAL_SGPTR;
+ bmov CCHADDR[1], SCB_RESIDUAL_SGPTR[1], 3;
+ mvi CCSGCTL, CCSGEN|CCSGRESET ret;
+idle_sgfetch_complete:
+ call disable_ccsgen_fetch_done;
+ and CCSGADDR, SG_PREFETCH_ADDR_MASK, SCB_RESIDUAL_SGPTR;
+idle_sg_avail:
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ /* Does the hardware have space for another SG entry? */
+ test DFSTATUS, PRELOAD_AVAIL jz return;
+ bmov HADDR, CCSGRAM, 4;
+ bmov SINDEX, CCSGRAM, 1;
+ test SINDEX, 0x1 jz . + 2;
+ xor DATA_COUNT_ODD, 0x1;
+ bmov HCNT[0], SINDEX, 1;
+ bmov HCNT[1], CCSGRAM, 2;
+ bmov SCB_RESIDUAL_DATACNT[3], CCSGRAM, 1;
+ call sg_advance;
+ mov SINDEX, SCB_RESIDUAL_SGPTR[0];
+ test DATA_COUNT_ODD, 0x1 jz . + 2;
+ or SINDEX, ODD_SEG;
+ test SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG jz . + 2;
+ or SINDEX, LAST_SEG;
+ mov SG_CACHE_PRE, SINDEX;
+ /* Load the segment by writing DFCNTRL again */
+ mov DFCNTRL, DMAPARAMS;
+ }
+ ret;
+}
+
+if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0 && ahc->pci_cachesize != 0) {
+/*
+ * Calculate the trailing portion of this S/G segment that cannot
+ * be transferred using memory write and invalidate PCI transactions.
+ * XXX Can we optimize this for PCI writes only???
+ */
+calc_mwi_residual:
+ /*
+ * If the ending address is on a cacheline boundary,
+ * there is no need for an extra segment.
+ */
+ mov A, HCNT[0];
+ add A, A, HADDR[0];
+ and A, CACHESIZE_MASK;
+ test A, 0xFF jz return;
+
+ /*
+ * If the transfer is less than a cachline,
+ * there is no need for an extra segment.
+ */
+ test HCNT[1], 0xFF jnz calc_mwi_residual_final;
+ test HCNT[2], 0xFF jnz calc_mwi_residual_final;
+ add NONE, INVERTED_CACHESIZE_MASK, HCNT[0];
+ jnc return;
+
+calc_mwi_residual_final:
+ mov MWI_RESIDUAL, A;
+ not A;
+ inc A;
+ add HCNT[0], A;
+ adc HCNT[1], -1;
+ adc HCNT[2], -1 ret;
+}
+
/*
* If we re-enter the data phase after going through another phase, the
* STCNT may have been cleared, so restore it from the residual field.
*/
data_phase_reinit:
- if ((p->features & AHC_ULTRA2) != 0) {
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ /*
+ * The preload circuitry requires us to
+ * reload the address too, so pull it from
+ * the shaddow address.
+ */
bmov HADDR, SHADDR, 4;
- bmov HCNT, SCB_RESID_DCNT, 3;
- }
- if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) {
- bmov STCNT, SCB_RESID_DCNT, 3;
- }
- if ((p->features & AHC_CMD_CHAN) == 0) {
+ bmov HCNT, SCB_RESIDUAL_DATACNT, 3;
+ } else if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov STCNT, SCB_RESIDUAL_DATACNT, 3;
+ } else {
mvi DINDEX, STCNT;
- mvi SCB_RESID_DCNT call bcopy_3;
+ mvi SCB_RESIDUAL_DATACNT call bcopy_3;
}
+ and DATA_COUNT_ODD, 0x1, SCB_RESIDUAL_DATACNT[0];
jmp data_phase_loop;
p_data:
- if ((p->features & AHC_ULTRA2) != 0) {
+ if ((ahc->features & AHC_ULTRA2) != 0) {
mvi DMAPARAMS, PRELOADEN|SCSIEN|HDMAEN;
} else {
mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET;
@@ -362,8 +832,9 @@
* Ensure entering a data
* phase is okay - seen identify, etc.
*/
- if ((p->features & AHC_CMD_CHAN) != 0) {
- mvi CCSGADDR, CCSGADDR_MAX;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ /* We don't have any valid S/G elements */
+ mvi CCSGADDR, SG_PREFETCH_CNT;
}
test SEQ_FLAGS, DPHASE jnz data_phase_reinit;
@@ -372,256 +843,471 @@
/*
* Initialize the DMA address and counter from the SCB.
- * Also set SG_COUNT and SG_NEXT in memory since we cannot
- * modify the values in the SCB itself until we see a
- * save data pointers message.
+ * Also set SCB_RESIDUAL_SGPTR, including the LAST_SEG
+ * flag in the highest byte of the data count. We cannot
+ * modify the saved values in the SCB until we see a save
+ * data pointers message.
*/
- if ((p->features & AHC_CMD_CHAN) != 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
bmov HADDR, SCB_DATAPTR, 7;
- bmov STCNT, HCNT, 3;
- bmov SG_COUNT, SCB_SGCOUNT, 5;
+ bmov SCB_RESIDUAL_DATACNT[3], SCB_DATACNT[3], 5;
} else {
mvi DINDEX, HADDR;
mvi SCB_DATAPTR call bcopy_7;
- call set_stcnt_from_hcnt;
- mvi DINDEX, SG_COUNT;
- mvi SCB_SGCOUNT call bcopy_5;
+ mvi DINDEX, SCB_RESIDUAL_DATACNT + 3;
+ mvi SCB_DATACNT + 3 call bcopy_5;
+ }
+ if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0 && ahc->pci_cachesize != 0) {
+ call calc_mwi_residual;
+ }
+ and SCB_RESIDUAL_SGPTR[0], ~SG_FULL_RESID;
+ and DATA_COUNT_ODD, 0x1, HCNT[0];
+
+ if ((ahc->features & AHC_ULTRA2) == 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov STCNT, HCNT, 3;
+ } else {
+ call set_stcnt_from_hcnt;
+ }
}
data_phase_loop:
-/* Guard against overruns */
- test SG_COUNT, 0xff jnz data_phase_inbounds;
-/*
- * Turn on 'Bit Bucket' mode, set the transfer count to
- * 16meg and let the target run until it changes phase.
- * When the transfer completes, notify the host that we
- * had an overrun.
- */
+ /* Guard against overruns */
+ test SCB_RESIDUAL_SGPTR[0], SG_LIST_NULL jz data_phase_inbounds;
+
+ /*
+ * Turn on `Bit Bucket' mode, wait until the target takes
+ * us to another phase, and then notify the host.
+ */
+ and DMAPARAMS, DIRECTION;
+ mov DFCNTRL, DMAPARAMS;
or SXFRCTL1,BITBUCKET;
- and DMAPARAMS, ~(HDMAEN|SDMAEN);
- if ((p->features & AHC_CMD_CHAN) != 0) {
- if ((p->features & AHC_ULTRA2) != 0) {
- bmov HCNT, ALLONES, 3;
- }
- bmov STCNT, ALLONES, 3;
- } else {
- mvi STCNT[0], 0xFF;
- mvi STCNT[1], 0xFF;
- mvi STCNT[2], 0xFF;
- }
+ test SSTAT1,PHASEMIS jz .;
+ and SXFRCTL1, ~BITBUCKET;
+ mvi DATA_OVERRUN call set_seqint;
+ jmp ITloop;
+
data_phase_inbounds:
-/* If we are the last SG block, tell the hardware. */
- cmp SG_COUNT,0x01 jne data_phase_wideodd;
- if ((p->features & AHC_ULTRA2) == 0) {
- and DMAPARAMS, ~WIDEODD;
- } else {
- mvi SG_CACHEPTR, LAST_SEG;
- }
-data_phase_wideodd:
- if ((p->features & AHC_ULTRA2) != 0) {
- mov SINDEX, ALLONES;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ mov SINDEX, SCB_RESIDUAL_SGPTR[0];
+ test SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG jz . + 2;
+ or SINDEX, LAST_SEG;
+ test DATA_COUNT_ODD, 0x1 jz . + 2;
+ or SINDEX, ODD_SEG;
+ mov SG_CACHE_PRE, SINDEX;
mov DFCNTRL, DMAPARAMS;
- test SSTAT0, SDONE jnz .;
-data_phase_dma_loop:
- test SSTAT0, SDONE jnz data_phase_dma_done;
- test SSTAT1,PHASEMIS jz data_phase_dma_loop; /* ie. underrun */
-data_phase_dma_phasemis:
- test SSTAT0,SDONE jnz data_phase_dma_done;
- clr SINDEX; /* Remember the phasemiss */
- } else {
- mov DMAPARAMS call dma;
- }
+ultra2_dma_loop:
+ call idle_loop;
+ /*
+ * The transfer is complete if either the last segment
+ * completes or the target changes phase.
+ */
+ test SG_CACHE_SHADOW, LAST_SEG_DONE jnz ultra2_dmafinish;
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ /*
+ * As a target, we control the phases,
+ * so ignore PHASEMIS.
+ */
+ test SSTAT0, TARGET jnz ultra2_dma_loop;
+ }
+ if ((ahc->flags & AHC_INITIATORROLE) != 0) {
+ test SSTAT1,PHASEMIS jz ultra2_dma_loop;
+ }
-data_phase_dma_done:
-/* Go tell the host about any overruns */
- test SXFRCTL1,BITBUCKET jnz data_phase_overrun;
+ultra2_dmafinish:
+ test DFCNTRL, DIRECTION jnz ultra2_dmafifoempty;
+ if ((ahc->features & AHC_DT) == 0) {
+ and DFCNTRL, ~SCSIEN;
+ test DFCNTRL, SCSIEN jnz .;
+ }
+ultra2_dmafifoflush:
+ if ((ahc->bugs & AHC_AUTOFLUSH_BUG) != 0) {
+ /*
+ * On Rev A of the aic7890, the autoflush
+ * features doesn't function correctly.
+ * Perform an explicit manual flush. During
+ * a manual flush, the FIFOEMP bit becomes
+ * true every time the PCI FIFO empties
+ * regardless of the state of the SCSI FIFO.
+ * It can take up to 4 clock cycles for the
+ * SCSI FIFO to get data into the PCI FIFO
+ * and for FIFOEMP to de-assert. Here we
+ * guard against this condition by making
+ * sure the FIFOEMP bit stays on for 5 full
+ * clock cycles.
+ */
+ or DFCNTRL, FIFOFLUSH;
+ test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
+ test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
+ test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
+ test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
+ }
+ test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
+ultra2_dmafifoempty:
+ /* Don't clobber an inprogress host data transfer */
+ test DFSTATUS, MREQPEND jnz ultra2_dmafifoempty;
+ultra2_dmahalt:
+ and DFCNTRL, ~(SCSIEN|HDMAEN);
+ test DFCNTRL, HDMAEN jnz .;
-/* Exit if we had an underrun. dma clears SINDEX in this case. */
- test SINDEX,0xff jz data_phase_finish;
+ /*
+ * If, by chance, we stopped before being able
+ * to fetch additional segments for this transfer,
+ * yet the last S/G was completely exhausted,
+ * call our idle loop until it is able to load
+ * another segment. This will allow us to immediately
+ * pickup on the next segment on the next data phase.
+ *
+ * If we happened to stop on the last segment, then
+ * our residual information is still correct from
+ * the idle loop and there is no need to perform
+ * any fixups. Just jump to data_phase_finish.
+ */
+ultra2_ensure_sg:
+ test SG_CACHE_SHADOW, LAST_SEG jz ultra2_shvalid;
+ /* Record if we've consumed all S/G entries */
+ test SG_CACHE_SHADOW, LAST_SEG_DONE jz data_phase_finish;
+ or SCB_RESIDUAL_SGPTR[0], SG_LIST_NULL;
+ jmp data_phase_finish;
+
+ultra2_shvalid:
+ test SSTAT2, SHVALID jnz sgptr_fixup;
+ call idle_loop;
+ jmp ultra2_ensure_sg;
-/*
- * Advance the scatter-gather pointers if needed
- */
-sg_advance:
- dec SG_COUNT; /* one less segment to go */
+sgptr_fixup:
+ /*
+ * Fixup the residual next S/G pointer. The S/G preload
+ * feature of the chip allows us to load two elements
+ * in addition to the currently active element. We
+ * store the bottom byte of the next S/G pointer in
+ * the SG_CACEPTR register so we can restore the
+ * correct value when the DMA completes. If the next
+ * sg ptr value has advanced to the point where higher
+ * bytes in the address have been affected, fix them
+ * too.
+ */
+ test SG_CACHE_SHADOW, 0x80 jz sgptr_fixup_done;
+ test SCB_RESIDUAL_SGPTR[0], 0x80 jnz sgptr_fixup_done;
+ add SCB_RESIDUAL_SGPTR[1], -1;
+ adc SCB_RESIDUAL_SGPTR[2], -1;
+ adc SCB_RESIDUAL_SGPTR[3], -1;
+sgptr_fixup_done:
+ and SCB_RESIDUAL_SGPTR[0], SG_ADDR_MASK, SG_CACHE_SHADOW;
+ clr DATA_COUNT_ODD;
+ test SG_CACHE_SHADOW, ODD_SEG jz . + 2;
+ or DATA_COUNT_ODD, 0x1;
+ clr SCB_RESIDUAL_DATACNT[3]; /* We are not the last seg */
+ } else {
+ /* If we are the last SG block, tell the hardware. */
+ if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0
+ && ahc->pci_cachesize != 0) {
+ test MWI_RESIDUAL, 0xFF jnz dma_mid_sg;
+ }
+ test SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG jz dma_mid_sg;
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ test SSTAT0, TARGET jz dma_last_sg;
+ if ((ahc->flags & AHC_TMODE_WIDEODD_BUG) != 0) {
+ test DMAPARAMS, DIRECTION jz dma_mid_sg;
+ }
+ }
+dma_last_sg:
+ and DMAPARAMS, ~WIDEODD;
+dma_mid_sg:
+ /* Start DMA data transfer. */
+ mov DFCNTRL, DMAPARAMS;
+dma_loop:
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ call idle_loop;
+ }
+ test SSTAT0,DMADONE jnz dma_dmadone;
+ test SSTAT1,PHASEMIS jz dma_loop; /* ie. underrun */
+dma_phasemis:
+ /*
+ * We will be "done" DMAing when the transfer count goes to
+ * zero, or the target changes the phase (in light of this,
+ * it makes sense that the DMA circuitry doesn't ACK when
+ * PHASEMIS is active). If we are doing a SCSI->Host transfer,
+ * the data FIFO should be flushed auto-magically on STCNT=0
+ * or a phase change, so just wait for FIFO empty status.
+ */
+dma_checkfifo:
+ test DFCNTRL,DIRECTION jnz dma_fifoempty;
+dma_fifoflush:
+ test DFSTATUS,FIFOEMP jz dma_fifoflush;
+dma_fifoempty:
+ /* Don't clobber an inprogress host data transfer */
+ test DFSTATUS, MREQPEND jnz dma_fifoempty;
- test SG_COUNT, 0xff jz data_phase_finish; /* Are we done? */
-/*
- * Load a struct scatter and set up the data address and length.
- * If the working value of the SG count is nonzero, then
- * we need to load a new set of values.
- *
- * This, like all DMA's, assumes little-endian host data storage.
- */
-sg_load:
- if ((p->features & AHC_CMD_CHAN) != 0) {
/*
- * Do we have any prefetch left???
+ * Now shut off the DMA and make sure that the DMA
+ * hardware has actually stopped. Touching the DMA
+ * counters, etc. while a DMA is active will result
+ * in an ILLSADDR exception.
+ */
+dma_dmadone:
+ and DFCNTRL, ~(SCSIEN|SDMAEN|HDMAEN);
+dma_halt:
+ /*
+ * Some revisions of the aic78XX have a problem where, if the
+ * data fifo is full, but the PCI input latch is not empty,
+ * HDMAEN cannot be cleared. The fix used here is to drain
+ * the prefetched but unused data from the data fifo until
+ * there is space for the input latch to drain.
*/
- cmp CCSGADDR, CCSGADDR_MAX jne prefetched_segs_avail;
+ if ((ahc->bugs & AHC_PCI_2_1_RETRY_BUG) != 0) {
+ mov NONE, DFDAT;
+ }
+ test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz dma_halt;
+
+ /* See if we have completed this last segment */
+ test STCNT[0], 0xff jnz data_phase_finish;
+ test STCNT[1], 0xff jnz data_phase_finish;
+ test STCNT[2], 0xff jnz data_phase_finish;
/*
- * Fetch MIN(CCSGADDR_MAX, (SG_COUNT * 8)) bytes.
+ * Advance the scatter-gather pointers if needed
*/
- add A, -(CCSGRAM_MAXSEGS + 1), SG_COUNT;
- mvi A, CCSGADDR_MAX;
- jc . + 2;
- shl A, 3, SG_COUNT;
- mov CCHCNT, A;
- bmov CCHADDR, SG_NEXT, 4;
- mvi CCSGCTL, CCSGEN|CCSGRESET;
- test CCSGCTL, CCSGDONE jz .;
- and CCSGCTL, ~CCSGEN;
- test CCSGCTL, CCSGEN jnz .;
- mvi CCSGCTL, CCSGRESET;
-prefetched_segs_avail:
- bmov HADDR, CCSGRAM, 8;
- if ((p->features & AHC_ULTRA2) == 0) {
- bmov STCNT, HCNT, 3;
+ if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0
+ && ahc->pci_cachesize != 0) {
+ test MWI_RESIDUAL, 0xFF jz no_mwi_resid;
+ /*
+ * Reload HADDR from SHADDR and setup the
+ * count to be the size of our residual.
+ */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov HADDR, SHADDR, 4;
+ mov HCNT, MWI_RESIDUAL;
+ bmov HCNT[1], ALLZEROS, 2;
+ } else {
+ mvi DINDEX, HADDR;
+ mvi SHADDR call bcopy_4;
+ mov MWI_RESIDUAL call set_hcnt;
+ }
+ clr MWI_RESIDUAL;
+ jmp sg_load_done;
+no_mwi_resid:
}
- } else {
- mvi DINDEX, HADDR;
- mvi SG_NEXT call bcopy_4;
+ test SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG jz sg_load;
+ or SCB_RESIDUAL_SGPTR[0], SG_LIST_NULL;
+ jmp data_phase_finish;
+sg_load:
+ /*
+ * Load the next SG element's data address and length
+ * into the DMA engine. If we don't have hardware
+ * to perform a prefetch, we'll have to fetch the
+ * segment from host memory first.
+ */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ /* Wait for the idle loop to complete */
+ test CCSGCTL, CCSGEN jz . + 3;
+ call idle_loop;
+ test CCSGCTL, CCSGEN jnz . - 1;
+ bmov HADDR, CCSGRAM, 7;
+ test CCSGRAM, SG_LAST_SEG jz . + 2;
+ or SCB_RESIDUAL_DATACNT[3], SG_LAST_SEG;
+ } else {
+ mvi DINDEX, HADDR;
+ mvi SCB_RESIDUAL_SGPTR call bcopy_4;
- mvi HCNT[0],SG_SIZEOF;
- clr HCNT[1];
- clr HCNT[2];
+ mvi SG_SIZEOF call set_hcnt;
- or DFCNTRL, HDMAEN|DIRECTION|FIFORESET;
+ or DFCNTRL, HDMAEN|DIRECTION|FIFORESET;
- call dma_finish;
+ call dma_finish;
- /*
- * Copy data from FIFO into SCB data pointer and data count.
- * This assumes that the SG segments are of the form:
- * struct ahc_dma_seg {
- * u_int32_t addr; four bytes, little-endian order
- * u_int32_t len; four bytes, little endian order
- * };
- */
- mvi HADDR call dfdat_in_7;
- call set_stcnt_from_hcnt;
- }
+ mvi DINDEX, HADDR;
+ call dfdat_in_7;
+ mov SCB_RESIDUAL_DATACNT[3], DFDAT;
+ }
-/* Advance the SG pointer */
- clr A; /* add sizeof(struct scatter) */
- add SG_NEXT[0],SG_SIZEOF;
- adc SG_NEXT[1],A;
+ if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0
+ && ahc->pci_cachesize != 0) {
+ call calc_mwi_residual;
+ }
- test SSTAT1, REQINIT jz .;
- test SSTAT1,PHASEMIS jz data_phase_loop;
+ /* Point to the new next sg in memory */
+ call sg_advance;
-/* This drops the last SG segment down to the shadow layer for us */
- if ((p->features & AHC_ULTRA2) != 0) {
- mov DFCNTRL, DMAPARAMS;
- test SSTAT0, SDONE jnz .;
- }
+sg_load_done:
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov STCNT, HCNT, 3;
+ } else {
+ call set_stcnt_from_hcnt;
+ }
+ /* Track odd'ness */
+ test HCNT[0], 0x1 jz . + 2;
+ xor DATA_COUNT_ODD, 0x1;
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ test SSTAT0, TARGET jnz data_phase_loop;
+ }
+ }
data_phase_finish:
-/*
- * After a DMA finishes, save the SG and STCNT residuals back into the SCB
- * We use STCNT instead of HCNT, since it's a reflection of how many bytes
- * were transferred on the SCSI (as opposed to the host) bus.
- */
- if ((p->features & AHC_ULTRA2) != 0) {
- call ultra2_dmafinish;
- }
- if ((p->features & AHC_ULTRA2) == 0) {
- if ((p->features & AHC_CMD_CHAN) != 0) {
- bmov SCB_RESID_DCNT, STCNT, 3;
- mov SCB_RESID_SGCNT, SG_COUNT;
- } else {
- mov SCB_RESID_DCNT[0],STCNT[0];
- mov SCB_RESID_DCNT[1],STCNT[1];
- mov SCB_RESID_DCNT[2],STCNT[2];
- mov SCB_RESID_SGCNT, SG_COUNT;
+ /*
+ * If the target has left us in data phase, loop through
+ * the dma code again. In the case of ULTRA2 adapters,
+ * we should only loop if there is a data overrun. For
+ * all other adapters, we'll loop after each S/G element
+ * is loaded as well as if there is an overrun.
+ */
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ test SSTAT0, TARGET jnz data_phase_done;
+ }
+ if ((ahc->flags & AHC_INITIATORROLE) != 0) {
+ test SSTAT1, REQINIT jz .;
+ test SSTAT1,PHASEMIS jz data_phase_loop;
+ }
+
+data_phase_done:
+ /*
+ * After a DMA finishes, save the SG and STCNT residuals back into
+ * the SCB. We use STCNT instead of HCNT, since it's a reflection
+ * of how many bytes were transferred on the SCSI (as opposed to the
+ * host) bus.
+ */
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ /* Kill off any pending prefetch */
+ call disable_ccsgen;
+ }
+
+ if ((ahc->bugs & AHC_PCI_MWI_BUG) != 0
+ && ahc->pci_cachesize != 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ test MWI_RESIDUAL, 0xFF jz bmov_resid;
+ }
+ mov A, MWI_RESIDUAL;
+ add SCB_RESIDUAL_DATACNT[0], A, STCNT[0];
+ clr A;
+ adc SCB_RESIDUAL_DATACNT[1], A, STCNT[1];
+ adc SCB_RESIDUAL_DATACNT[2], A, STCNT[2];
+ clr MWI_RESIDUAL;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ jmp . + 2;
+bmov_resid:
+ bmov SCB_RESIDUAL_DATACNT, STCNT, 3;
}
+ } else if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov SCB_RESIDUAL_DATACNT, STCNT, 3;
+ } else {
+ mov SCB_RESIDUAL_DATACNT[0], STCNT[0];
+ mov SCB_RESIDUAL_DATACNT[1], STCNT[1];
+ mov SCB_RESIDUAL_DATACNT[2], STCNT[2];
}
- jmp ITloop;
+ /*
+ * Since we've been through a data phase, the SCB_RESID* fields
+ * are now initialized. Clear the full residual flag.
+ */
+ and SCB_SGPTR[0], ~SG_FULL_RESID;
-data_phase_overrun:
- if ((p->features & AHC_ULTRA2) != 0) {
- call ultra2_dmafinish;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ /* Clear the channel in case we return to data phase later */
+ or SXFRCTL0, CLRSTCNT|CLRCHN;
+ or SXFRCTL0, CLRSTCNT|CLRCHN;
}
-/*
- * Turn off BITBUCKET mode and notify the host
- */
- and SXFRCTL1, ~BITBUCKET;
- mvi INTSTAT,DATA_OVERRUN;
- jmp ITloop;
-ultra2_dmafinish:
- if ((p->features & AHC_ULTRA2) != 0) {
- test DFCNTRL, DIRECTION jnz ultra2_dmahalt;
- and DFCNTRL, ~SCSIEN;
- test DFCNTRL, SCSIEN jnz .;
-ultra2_dmafifoflush:
- or DFCNTRL, FIFOFLUSH;
- test DFSTATUS, FIFOEMP jz . - 1;
+ if ((ahc->flags & AHC_TARGETROLE) != 0) {
+ test SEQ_FLAGS, DPHASE_PENDING jz ITloop;
+ and SEQ_FLAGS, ~DPHASE_PENDING;
/*
- * hardware bug alert! This needless set of jumps is to
- * protect against a FIFOEMP status bit glitch in the
- * silicon.
+ * For data-in phases, wait for any pending acks from the
+ * initiator before changing phase.
*/
- test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
- test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
- test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
- test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
- test DFSTATUS, FIFOEMP jz ultra2_dmafifoflush;
- test DFSTATUS, MREQPEND jnz .;
-ultra2_dmahalt:
- test SCSIOFFSET, 0x7f jnz ultra2_shutdown;
-ultra2_await_nreq:
- test SCSISIGI, REQI jz ultra2_shutdown;
- test SSTAT1, (PHASEMIS|REQINIT) jz ultra2_await_nreq;
-ultra2_shutdown:
- and DFCNTRL, ~(HDMAEN|SCSIEN);
- test DFCNTRL, (HDMAEN|SCSIEN) jnz .;
- bmov SCB_RESID_DCNT, STCNT, 3;
- mov SCB_RESID_SGCNT, SG_COUNT;
- or SXFRCTL0, CLRSTCNT|CLRCHN;
- ret;
+ test DFCNTRL, DIRECTION jz target_ITloop;
+ test SSTAT1, REQINIT jnz .;
+ jmp target_ITloop;
+ } else {
+ jmp ITloop;
}
+if ((ahc->flags & AHC_INITIATORROLE) != 0) {
/*
* Command phase. Set up the DMA registers and let 'er rip.
*/
p_command:
call assert;
-/*
- * Load HADDR and HCNT.
- */
- if ((p->features & AHC_CMD_CHAN) != 0) {
- bmov HADDR, SCB_CMDPTR, 5;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ bmov HCNT[0], SCB_CDB_LEN, 1;
bmov HCNT[1], ALLZEROS, 2;
- if ((p->features & AHC_ULTRA2) == 0) {
- bmov STCNT, HCNT, 3;
- }
+ mvi SG_CACHE_PRE, LAST_SEG;
+ } else if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov STCNT[0], SCB_CDB_LEN, 1;
+ bmov STCNT[1], ALLZEROS, 2;
} else {
- mvi DINDEX, HADDR;
- mvi SCB_CMDPTR call bcopy_5;
- clr HCNT[1];
- clr HCNT[2];
- call set_stcnt_from_hcnt;
+ mov STCNT[0], SCB_CDB_LEN;
+ clr STCNT[1];
+ clr STCNT[2];
+ }
+ add NONE, -13, SCB_CDB_LEN;
+ mvi SCB_CDB_STORE jnc p_command_embedded;
+p_command_from_host:
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ bmov HADDR[0], SCB_CDB_PTR, 4;
+ mvi DFCNTRL, (PRELOADEN|SCSIEN|HDMAEN|DIRECTION);
+ } else {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov HADDR[0], SCB_CDB_PTR, 4;
+ bmov HCNT, STCNT, 3;
+ } else {
+ mvi DINDEX, HADDR;
+ mvi SCB_CDB_PTR call bcopy_4;
+ mov SCB_CDB_LEN call set_hcnt;
+ }
+ mvi DFCNTRL, (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET);
}
-
- if ((p->features & AHC_ULTRA2) == 0) {
- mvi (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET) call dma;
+ jmp p_command_loop;
+p_command_embedded:
+ /*
+ * The data fifo seems to require 4 byte alligned
+ * transfers from the sequencer. Force this to
+ * be the case by clearing HADDR[0] even though
+ * we aren't going to touch host memeory.
+ */
+ clr HADDR[0];
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ mvi DFCNTRL, (PRELOADEN|SCSIEN|DIRECTION);
+ bmov DFDAT, SCB_CDB_STORE, 12;
+ } else if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ /*
+ * On the 7895 the data FIFO will
+ * get corrupted if you try to dump
+ * data from external SCB memory into
+ * the FIFO while it is enabled. So,
+ * fill the fifo and then enable SCSI
+ * transfers.
+ */
+ mvi DFCNTRL, (DIRECTION|FIFORESET);
+ } else {
+ mvi DFCNTRL, (SCSIEN|SDMAEN|DIRECTION|FIFORESET);
+ }
+ bmov DFDAT, SCB_CDB_STORE, 12;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ mvi DFCNTRL, (SCSIEN|SDMAEN|DIRECTION|FIFOFLUSH);
+ } else {
+ or DFCNTRL, FIFOFLUSH;
+ }
} else {
- mvi DFCNTRL, (PRELOADEN|SCSIEN|HDMAEN|DIRECTION);
- test SSTAT0, SDONE jnz .;
-p_command_dma_loop:
- test SSTAT0, SDONE jnz p_command_ultra2_dma_done;
- test SSTAT1,PHASEMIS jz p_command_dma_loop; /* ie. underrun */
-p_command_ultra2_dma_done:
- test SCSISIGI, REQI jz p_command_ultra2_shutdown;
- test SSTAT1, (PHASEMIS|REQINIT) jz p_command_ultra2_dma_done;
-p_command_ultra2_shutdown:
- and DFCNTRL, ~(HDMAEN|SCSIEN);
- test DFCNTRL, (HDMAEN|SCSIEN) jnz .;
- or SXFRCTL0, CLRSTCNT|CLRCHN;
+ mvi DFCNTRL, (SCSIEN|SDMAEN|DIRECTION|FIFORESET);
+ call copy_to_fifo_6;
+ call copy_to_fifo_6;
+ or DFCNTRL, FIFOFLUSH;
+ }
+p_command_loop:
+ test SSTAT0, SDONE jnz . + 2;
+ test SSTAT1, PHASEMIS jz p_command_loop;
+ /*
+ * Wait for our ACK to go-away on it's own
+ * instead of being killed by SCSIEN getting cleared.
+ */
+ test SCSISIGI, ACKI jnz .;
+ and DFCNTRL, ~(SCSIEN|SDMAEN|HDMAEN);
+ test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz .;
+ if ((ahc->features & AHC_ULTRA2) != 0) {
+ /* Drop any residual from the S/G Preload queue */
+ or SXFRCTL0, CLRSTCNT;
}
jmp ITloop;
@@ -632,21 +1318,26 @@
p_status:
call assert;
- mov SCB_TARGET_STATUS, SCSIDATL;
+ mov SCB_SCSI_STATUS, SCSIDATL;
jmp ITloop;
/*
- * Message out phase. If MSG_OUT is 0x80, build I full indentify message
- * sequence and send it to the target. In addition, if the MK_MESSAGE bit
- * is set in the SCB_CONTROL byte, interrupt the host and allow it to send
- * it's own message.
+ * Message out phase. If MSG_OUT is MSG_IDENTIFYFLAG, build a full
+ * indentify message sequence and send it to the target. The host may
+ * override this behavior by setting the MK_MESSAGE bit in the SCB
+ * control byte. This will cause us to interrupt the host and allow
+ * it to handle the message phase completely on its own. If the bit
+ * associated with this target is set, we will also interrupt the host,
+ * thereby allowing it to send a message on the next selection regardless
+ * of the transaction being sent.
*
* If MSG_OUT is == HOST_MSG, also interrupt the host and take a message.
- * This is done to allow the hsot to send messages outside of an identify
+ * This is done to allow the host to send messages outside of an identify
* sequence while protecting the seqencer from testing the MK_MESSAGE bit
* on an SCB that might not be for the current nexus. (For example, a
* BDR message in responce to a bad reselection would leave us pointed to
* an SCB that doesn't have anything to do with the current target).
+ *
* Otherwise, treat MSG_OUT as a 1 byte message to send (abort, abort tag,
* bus device reset).
*
@@ -655,23 +1346,27 @@
* reason.
*/
p_mesgout_retry:
- or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */
+ or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */
p_mesgout:
mov SINDEX, MSG_OUT;
cmp SINDEX, MSG_IDENTIFYFLAG jne p_mesgout_from_host;
-p_mesgout_identify:
- if ((p->features & AHC_WIDE) != 0) {
- and SINDEX,0xf,SCB_TCL; /* lun */
- } else {
- and SINDEX,0x7,SCB_TCL; /* lun */
+ test SCB_CONTROL,MK_MESSAGE jnz host_message_loop;
+ mov FUNCTION1, SCB_SCSIID;
+ mov A, FUNCTION1;
+ mov SINDEX, TARGET_MSG_REQUEST[0];
+ if ((ahc->features & AHC_TWIN) != 0) {
+ /* Second Channel uses high byte bits */
+ test SCB_SCSIID, TWIN_CHNLB jz . + 2;
+ mov SINDEX, TARGET_MSG_REQUEST[1];
+ } else if ((ahc->features & AHC_WIDE) != 0) {
+ test SCB_SCSIID, 0x80 jz . + 2; /* target > 7 */
+ mov SINDEX, TARGET_MSG_REQUEST[1];
}
- and A,DISCENB,SCB_CONTROL; /* mask off disconnect privledge */
- or SINDEX,A; /* or in disconnect privledge */
- or SINDEX,MSG_IDENTIFYFLAG;
-p_mesgout_mk_message:
- test SCB_CONTROL,MK_MESSAGE jz p_mesgout_tag;
- mov SCSIDATL, SINDEX; /* Send the last byte */
- jmp p_mesgout_from_host + 1;/* Skip HOST_MSG test */
+ test SINDEX, A jnz host_message_loop;
+p_mesgout_identify:
+ or SINDEX, MSG_IDENTIFYFLAG|DISCENB, SCB_LUN;
+ test SCB_CONTROL, DISCENB jnz . + 2;
+ and SINDEX, ~DISCENB;
/*
* Send a tag message if TAG_ENB is set in the SCB control block.
* Use SCB_TAG (the position in the kernel's SCB array) as the tag value.
@@ -686,34 +1381,27 @@
cmp LASTPHASE, P_MESGOUT jne p_mesgout_done;
mov SCB_TAG jmp p_mesgout_onebyte;
/*
- * Interrupt the driver, and allow it to send a message
- * if it asks.
+ * Interrupt the driver, and allow it to handle this message
+ * phase and any required retries.
*/
p_mesgout_from_host:
cmp SINDEX, HOST_MSG jne p_mesgout_onebyte;
- mvi INTSTAT,AWAITING_MSG;
- nop;
- /*
- * Did the host detect a phase change?
- */
- cmp RETURN_1, MSGOUT_PHASEMIS je p_mesgout_done;
+ jmp host_message_loop;
p_mesgout_onebyte:
mvi CLRSINT1, CLRATNO;
mov SCSIDATL, SINDEX;
/*
- * If the next bus phase after ATN drops is a message out, it means
+ * If the next bus phase after ATN drops is message out, it means
* that the target is requesting that the last message(s) be resent.
*/
call phase_lock;
- cmp LASTPHASE, P_MESGOUT je p_mesgout_retry;
+ cmp LASTPHASE, P_MESGOUT je p_mesgout_retry;
p_mesgout_done:
mvi CLRSINT1,CLRATNO; /* Be sure to turn ATNO off */
mov LAST_MSG, MSG_OUT;
- cmp MSG_OUT, MSG_IDENTIFYFLAG jne . + 2;
- and SCB_CONTROL, ~MK_MESSAGE;
mvi MSG_OUT, MSG_NOOP; /* No message left */
jmp ITloop;
@@ -728,113 +1416,137 @@
cmp A,MSG_SAVEDATAPOINTER je mesgin_sdptrs;
cmp ALLZEROS,A je mesgin_complete;
cmp A,MSG_RESTOREPOINTERS je mesgin_rdptrs;
- cmp A,MSG_EXTENDED je mesgin_extended;
- cmp A,MSG_MESSAGE_REJECT je mesgin_reject;
+ cmp A,MSG_IGN_WIDE_RESIDUE je mesgin_ign_wide_residue;
cmp A,MSG_NOOP je mesgin_done;
- cmp A,MSG_IGN_WIDE_RESIDUE je mesgin_wide_residue;
-rej_mesgin:
/*
- * We have no idea what this message in is, so we issue a message reject
- * and hope for the best. In any case, rejection should be a rare
- * occurrence - signal the driver when it happens.
+ * Pushed message loop to allow the kernel to
+ * run it's own message state engine. To avoid an
+ * extra nop instruction after signaling the kernel,
+ * we perform the phase_lock before checking to see
+ * if we should exit the loop and skip the phase_lock
+ * in the ITloop. Performing back to back phase_locks
+ * shouldn't hurt, but why do it twice...
*/
- mvi INTSTAT,SEND_REJECT; /* let driver know */
+host_message_loop:
+ mvi HOST_MSG_LOOP call set_seqint;
+ call phase_lock;
+ cmp RETURN_1, EXIT_MSG_LOOP je ITloop + 1;
+ jmp host_message_loop;
- mvi MSG_MESSAGE_REJECT call mk_mesg;
+mesgin_ign_wide_residue:
+if ((ahc->features & AHC_WIDE) != 0) {
+ test SCSIRATE, WIDEXFER jz mesgin_reject;
+ /* Pull the residue byte */
+ mvi ARG_1 call inb_next;
+ cmp ARG_1, 0x01 jne mesgin_reject;
+ test SCB_RESIDUAL_SGPTR[0], SG_LIST_NULL jz . + 2;
+ test DATA_COUNT_ODD, 0x1 jz mesgin_done;
+ mvi IGN_WIDE_RES call set_seqint;
+ jmp mesgin_done;
+}
+mesgin_reject:
+ mvi MSG_MESSAGE_REJECT call mk_mesg;
mesgin_done:
mov NONE,SCSIDATL; /*dummy read from latch to ACK*/
jmp ITloop;
-
mesgin_complete:
/*
- * We got a "command complete" message, so put the SCB_TAG into the QOUTFIFO,
+ * We received a "command complete" message. Put the SCB_TAG into the QOUTFIFO,
* and trigger a completion interrupt. Before doing so, check to see if there
* is a residual or the status byte is something other than STATUS_GOOD (0).
* In either of these conditions, we upload the SCB back to the host so it can
* process this information. In the case of a non zero status byte, we
* additionally interrupt the kernel driver synchronously, allowing it to
* decide if sense should be retrieved. If the kernel driver wishes to request
- * sense, it will fill the kernel SCB with a request sense command and set
- * RETURN_1 to SEND_SENSE. If RETURN_1 is set to SEND_SENSE we redownload
- * the SCB, and process it as the next command by adding it to the waiting list.
- * If the kernel driver does not wish to request sense, it need only clear
- * RETURN_1, and the command is allowed to complete normally. We don't bother
- * to post to the QOUTFIFO in the error cases since it would require extra
- * work in the kernel driver to ensure that the entry was removed before the
- * command complete code tried processing it.
+ * sense, it will fill the kernel SCB with a request sense command, requeue
+ * it to the QINFIFO and tell us not to post to the QOUTFIFO by setting
+ * RETURN_1 to SEND_SENSE.
+ */
+
+/*
+ * If ATN is raised, we still want to give the target a message.
+ * Perhaps there was a parity error on this last message byte.
+ * Either way, the target should take us to message out phase
+ * and then attempt to complete the command again. We should use a
+ * critical section here to guard against a timeout triggering
+ * for this command and setting ATN while we are still processing
+ * the completion.
+ test SCSISIGI, ATNI jnz mesgin_done;
*/
/*
- * First check for residuals
+ * See if we attempted to deliver a message but the target ingnored us.
*/
- test SCB_RESID_SGCNT,0xff jnz upload_scb;
- test SCB_TARGET_STATUS,0xff jz complete; /* Good Status? */
+ test SCB_CONTROL, MK_MESSAGE jz . + 2;
+ mvi MKMSG_FAILED call set_seqint;
+
+/*
+ * Check for residuals
+ */
+ test SCB_SGPTR, SG_LIST_NULL jnz check_status;/* No xfer */
+ test SCB_SGPTR, SG_FULL_RESID jnz upload_scb;/* Never xfered */
+ test SCB_RESIDUAL_SGPTR, SG_LIST_NULL jz upload_scb;
+check_status:
+ test SCB_SCSI_STATUS,0xff jz complete; /* Good Status? */
upload_scb:
+ or SCB_SGPTR, SG_RESID_VALID;
mvi DMAPARAMS, FIFORESET;
mov SCB_TAG call dma_scb;
-check_status:
- test SCB_TARGET_STATUS,0xff jz complete; /* Just a residual? */
- mvi INTSTAT,BAD_STATUS; /* let driver know */
- nop;
+ test SCB_SCSI_STATUS, 0xff jz complete; /* Just a residual? */
+ mvi BAD_STATUS call set_seqint; /* let driver know */
cmp RETURN_1, SEND_SENSE jne complete;
- /* This SCB becomes the next to execute as it will retrieve sense */
- mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET;
- mov SCB_TAG call dma_scb;
-add_to_waiting_list:
- mov SCB_NEXT,WAITING_SCBH;
- mov WAITING_SCBH, SCBPTR;
- /*
- * Prepare our selection hardware before the busfree so we have a
- * high probability of winning arbitration.
- */
- call start_selection;
+ call add_scb_to_free_list;
jmp await_busfree;
-
complete:
- /* If we are untagged, clear our address up in host ram */
- test SCB_CONTROL, TAG_ENB jnz complete_post;
- mov A, SAVED_TCL;
- mvi UNTAGGEDSCB_OFFSET call post_byte_setup;
- mvi SCB_LIST_NULL call post_byte;
+ mov SCB_TAG call complete_post;
+ jmp await_busfree;
+}
complete_post:
- /* Post the SCB and issue an interrupt */
- if ((p->features & AHC_QUEUE_REGS) != 0) {
+ /* Post the SCBID in SINDEX and issue an interrupt */
+ call add_scb_to_free_list;
+ mov ARG_1, SINDEX;
+ if ((ahc->features & AHC_QUEUE_REGS) != 0) {
mov A, SDSCB_QOFF;
} else {
mov A, QOUTPOS;
}
mvi QOUTFIFO_OFFSET call post_byte_setup;
- mov SCB_TAG call post_byte;
- if ((p->features & AHC_QUEUE_REGS) == 0) {
+ mov ARG_1 call post_byte;
+ if ((ahc->features & AHC_QUEUE_REGS) == 0) {
inc QOUTPOS;
}
- mvi INTSTAT,CMDCMPLT;
-
-add_to_free_list:
- call add_scb_to_free_list;
- jmp await_busfree;
-
-/*
- * Is it an extended message? Copy the message to our message buffer and
- * notify the host. The host will tell us whether to reject this message,
- * respond to it with the message that the host placed in our message buffer,
- * or simply to do nothing.
- */
-mesgin_extended:
- mvi INTSTAT,EXTENDED_MSG; /* let driver know */
- jmp ITloop;
+ mvi INTSTAT,CMDCMPLT ret;
+if ((ahc->flags & AHC_INITIATORROLE) != 0) {
/*
* Is it a disconnect message? Set a flag in the SCB to remind us
- * and await the bus going free.
+ * and await the bus going free. If this is an untagged transaction
+ * store the SCB id for it in our untagged target table for lookup on
+ * a reselction.
*/
mesgin_disconnect:
+ /*
+ * If ATN is raised, we still want to give the target a message.
+ * Perhaps there was a parity error on this last message byte
+ * or we want to abort this command. Either way, the target
+ * should take us to message out phase and then attempt to
+ * disconnect again.
+ * XXX - Wait for more testing.
+ test SCSISIGI, ATNI jnz mesgin_done;
+ */
+
or SCB_CONTROL,DISCONNECTED;
- call add_scb_to_disc_list;
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ call add_scb_to_disc_list;
+ }
+ test SCB_CONTROL, TAG_ENB jnz await_busfree;
+ mov ARG_1, SCB_TAG;
+ mov SAVED_LUN, SCB_LUN;
+ mov SCB_SCSIID call set_busy_target;
jmp await_busfree;
/*
@@ -846,22 +1558,20 @@
*/
mesgin_sdptrs:
test SEQ_FLAGS, DPHASE jz mesgin_done;
+
/*
- * The SCB SGPTR becomes the next one we'll download,
- * and the SCB DATAPTR becomes the current SHADDR.
+ * The SCB_SGPTR becomes the next one we'll download,
+ * and the SCB_DATAPTR becomes the current SHADDR.
* Use the residual number since STCNT is corrupted by
* any message transfer.
*/
- if ((p->features & AHC_CMD_CHAN) != 0) {
- bmov SCB_SGCOUNT, SG_COUNT, 5;
- bmov SCB_DATAPTR, SHADDR, 4;
- bmov SCB_DATACNT, SCB_RESID_DCNT, 3;
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
+ bmov SCB_DATAPTR, SHADDR, 4;
+ bmov SCB_DATACNT, SCB_RESIDUAL_DATACNT, 8;
} else {
- mvi DINDEX, SCB_SGCOUNT;
- mvi SG_COUNT call bcopy_5;
mvi DINDEX, SCB_DATAPTR;
- mvi SHADDR call bcopy_4;
- mvi SCB_RESID_DCNT call bcopy_3;
+ mvi SHADDR call bcopy_4;
+ mvi SCB_RESIDUAL_DATACNT call bcopy_8;
}
jmp mesgin_done;
@@ -880,109 +1590,166 @@
jmp mesgin_done;
/*
+ * Index into our Busy Target table. SINDEX and DINDEX are modified
+ * upon return. SCBPTR may be modified by this action.
+ */
+set_busy_target:
+ shr DINDEX, 4, SINDEX;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ mov SCBPTR, SAVED_LUN;
+ add DINDEX, SCB_64_BTT;
+ } else {
+ add DINDEX, BUSY_TARGETS;
+ }
+ mov DINDIR, ARG_1 ret;
+
+/*
* Identify message? For a reconnecting target, this tells us the lun
* that the reconnection is for - find the correct SCB and switch to it,
* clearing the "disconnected" bit so we don't "find" it by accident later.
*/
mesgin_identify:
-
- if ((p->features & AHC_WIDE) != 0) {
- and A,0x0f; /* lun in lower four bits */
+ /*
+ * Determine whether a target is using tagged or non-tagged
+ * transactions by first looking at the transaction stored in
+ * the busy target array. If there is no untagged transaction
+ * for this target or the transaction is for a different lun, then
+ * this must be an untagged transaction.
+ */
+ shr SINDEX, 4, SAVED_SCSIID;
+ and SAVED_LUN, MSG_IDENTIFY_LUNMASK, A;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ add SINDEX, SCB_64_BTT;
+ mov SCBPTR, SAVED_LUN;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ add NONE, -SCB_64_BTT, SINDEX;
+ jc . + 2;
+ mvi INTSTAT, OUT_OF_RANGE;
+ nop;
+ add NONE, -(SCB_64_BTT + 16), SINDEX;
+ jnc . + 2;
+ mvi INTSTAT, OUT_OF_RANGE;
+ nop;
+ }
} else {
- and A,0x07; /* lun in lower three bits */
+ add SINDEX, BUSY_TARGETS;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ add NONE, -BUSY_TARGETS, SINDEX;
+ jc . + 2;
+ mvi INTSTAT, OUT_OF_RANGE;
+ nop;
+ add NONE, -(BUSY_TARGETS + 16), SINDEX;
+ jnc . + 2;
+ mvi INTSTAT, OUT_OF_RANGE;
+ nop;
+ }
}
- or SAVED_TCL,A; /* SAVED_TCL should be complete now */
-
- mvi ARG_2, SCB_LIST_NULL; /* SCBID of prev SCB in disc List */
- call get_untagged_SCBID;
+ mov ARG_1, SINDIR;
cmp ARG_1, SCB_LIST_NULL je snoop_tag;
- if ((p->flags & AHC_PAGESCBS) != 0) {
- test SEQ_FLAGS, SCBPTR_VALID jz use_retrieveSCB;
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ mov ARG_1 call findSCB;
+ } else {
+ mov SCBPTR, ARG_1;
}
- /*
- * If the SCB was found in the disconnected list (as is
- * always the case in non-paging scenarios), SCBPTR is already
- * set to the correct SCB. So, simply setup the SCB and get
- * on with things.
- */
- mov SCBPTR call rem_scb_from_disc_list;
- jmp setup_SCB;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ jmp setup_SCB_id_lun_okay;
+ } else {
+ /*
+ * We only allow one untagged command per-target
+ * at a time. So, if the lun doesn't match, look
+ * for a tag message.
+ */
+ mov A, SCB_LUN;
+ cmp SAVED_LUN, A je setup_SCB_id_lun_okay;
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ /*
+ * findSCB removes the SCB from the
+ * disconnected list, so we must replace
+ * it there should this SCB be for another
+ * lun.
+ */
+ call cleanup_scb;
+ }
+ }
+
/*
* Here we "snoop" the bus looking for a SIMPLE QUEUE TAG message.
* If we get one, we use the tag returned to find the proper
- * SCB. With SCB paging, this requires using search for both tagged
- * and non-tagged transactions since the SCB may exist in any slot.
- * If we're not using SCB paging, we can use the tag as the direct
- * index to the SCB.
+ * SCB. With SCB paging, we must search for non-tagged
+ * transactions since the SCB may exist in any slot. If we're not
+ * using SCB paging, we can use the tag as the direct index to the
+ * SCB.
*/
snoop_tag:
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x80;
+ }
mov NONE,SCSIDATL; /* ACK Identify MSG */
-snoop_tag_loop:
call phase_lock;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x1;
+ }
cmp LASTPHASE, P_MESGIN jne not_found;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x2;
+ }
cmp SCSIBUSL,MSG_SIMPLE_Q_TAG jne not_found;
get_tag:
- mvi ARG_1 call inb_next; /* tag value */
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ mvi ARG_1 call inb_next; /* tag value */
+ mov ARG_1 call findSCB;
+ } else {
+ mvi ARG_1 call inb_next; /* tag value */
+ mov SCBPTR, ARG_1;
+ }
-use_retrieveSCB:
- call retrieveSCB;
+/*
+ * Ensure that the SCB the tag points to is for
+ * an SCB transaction to the reconnecting target.
+ */
setup_SCB:
- mov A, SAVED_TCL;
- cmp SCB_TCL, A jne not_found_cleanup_scb;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x4;
+ }
+ mov A, SCB_SCSIID;
+ cmp SAVED_SCSIID, A jne not_found_cleanup_scb;
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x8;
+ }
+setup_SCB_id_okay:
+ mov A, SCB_LUN;
+ cmp SAVED_LUN, A jne not_found_cleanup_scb;
+setup_SCB_id_lun_okay:
+ if ((ahc->flags & AHC_SEQUENCER_DEBUG) != 0) {
+ or SEQ_FLAGS, 0x10;
+ }
test SCB_CONTROL,DISCONNECTED jz not_found_cleanup_scb;
and SCB_CONTROL,~DISCONNECTED;
- or SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */
+ test SCB_CONTROL, TAG_ENB jnz setup_SCB_tagged;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ mov A, SCBPTR;
+ }
+ mvi ARG_1, SCB_LIST_NULL;
+ mov SAVED_SCSIID call set_busy_target;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ mov SCBPTR, A;
+ }
+setup_SCB_tagged:
+ mvi SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */
+ call set_transfer_settings;
/* See if the host wants to send a message upon reconnection */
test SCB_CONTROL, MK_MESSAGE jz mesgin_done;
- and SCB_CONTROL, ~MK_MESSAGE;
mvi HOST_MSG call mk_mesg;
jmp mesgin_done;
not_found_cleanup_scb:
- test SCB_CONTROL, DISCONNECTED jz . + 3;
- call add_scb_to_disc_list;
- jmp not_found;
- call add_scb_to_free_list;
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ call cleanup_scb;
+ }
not_found:
- mvi INTSTAT, NO_MATCH;
- mvi MSG_BUS_DEV_RESET call mk_mesg;
- jmp mesgin_done;
-
-/*
- * Message reject? Let the kernel driver handle this. If we have an
- * outstanding WDTR or SDTR negotiation, assume that it's a response from
- * the target selecting 8bit or asynchronous transfer, otherwise just ignore
- * it since we have no clue what it pertains to.
- */
-mesgin_reject:
- mvi INTSTAT, REJECT_MSG;
- jmp mesgin_done;
-
-/*
- * Wide Residue. We handle the simple cases, but pass of the one hard case
- * to the kernel (when the residue byte happened to cause us to advance our
- * sg element array, so we know have to back that advance out).
- */
-mesgin_wide_residue:
- mvi ARG_1 call inb_next; /* ACK the wide_residue and get */
- /* the size byte */
-/*
- * In order for this to be reliable, we have to do all sorts of horrible
- * magic in terms of resetting the datafifo and reloading the shadow layer
- * with the correct new values (so that a subsequent save data pointers
- * message will do the right thing). We let the kernel do that work.
- */
- mvi INTSTAT, WIDE_RESIDUE;
+ mvi NO_MATCH call set_seqint;
jmp mesgin_done;
-
-/*
- * [ ADD MORE MESSAGE HANDLING HERE ]
- */
-/*
- * Locking the driver out, build a one-byte message passed in SINDEX
- * if there is no active message already. SINDEX is returned intact.
- */
mk_mesg:
or SCSISIGO,ATNO,LASTPHASE;/* turn on ATNO */
mov MSG_OUT,SINDEX ret;
@@ -1002,7 +1769,9 @@
* and that REQ is already set when inb_first is called. inb_{first,next}
* use the same calling convention as inb.
*/
-
+inb_next_wait_perr:
+ mvi PERR_DETECTED call set_seqint;
+ jmp inb_next_wait;
inb_next:
mov NONE,SCSIDATL; /*dummy read from latch to ACK*/
inb_next_wait:
@@ -1012,7 +1781,8 @@
* before continuing.
*/
test SSTAT1, REQINIT jz inb_next_wait;
- test SSTAT1, SCSIPERR jnz .;
+ test SSTAT1, SCSIPERR jnz inb_next_wait_perr;
+inb_next_check_phase:
and LASTPHASE, PHASE_MASK, SCSISIGI;
cmp LASTPHASE, P_MESGIN jne mesgin_phasemis;
inb_first:
@@ -1020,71 +1790,48 @@
mov DINDIR,SCSIBUSL ret; /*read byte directly from bus*/
inb_last:
mov NONE,SCSIDATL ret; /*dummy read from latch to ACK*/
+}
-mesgin_phasemis:
+if ((ahc->flags & AHC_TARGETROLE) != 0) {
/*
- * We expected to receive another byte, but the target changed phase
+ * Change to a new phase. If we are changing the state of the I/O signal,
+ * from out to in, wait an additional data release delay before continuing.
*/
- mvi INTSTAT, MSGIN_PHASEMIS;
- jmp ITloop;
-
-/*
- * DMA data transfer. HADDR and HCNT must be loaded first, and
- * SINDEX should contain the value to load DFCNTRL with - 0x3d for
- * host->scsi, or 0x39 for scsi->host. The SCSI channel is cleared
- * during initialization.
- */
-if ((p->features & AHC_ULTRA2) == 0) {
-dma:
- mov DFCNTRL,SINDEX;
-dma_loop:
- test SSTAT0,DMADONE jnz dma_dmadone;
- test SSTAT1,PHASEMIS jz dma_loop; /* ie. underrun */
-dma_phasemis:
- test SSTAT0,SDONE jnz dma_checkfifo;
- mov SINDEX,ALLZEROS; /* Notify caller of phasemiss */
+change_phase:
+ /* Wait for preceeding I/O session to complete. */
+ test SCSISIGI, ACKI jnz .;
+
+ /* Change the phase */
+ and DINDEX, IOI, SCSISIGI;
+ mov SCSISIGO, SINDEX;
+ and A, IOI, SINDEX;
-/*
- * We will be "done" DMAing when the transfer count goes to zero, or
- * the target changes the phase (in light of this, it makes sense that
- * the DMA circuitry doesn't ACK when PHASEMIS is active). If we are
- * doing a SCSI->Host transfer, the data FIFO should be flushed auto-
- * magically on STCNT=0 or a phase change, so just wait for FIFO empty
- * status.
- */
-dma_checkfifo:
- test DFCNTRL,DIRECTION jnz dma_fifoempty;
-dma_fifoflush:
- test DFSTATUS,FIFOEMP jz dma_fifoflush;
+ /*
+ * If the data direction has changed, from
+ * out (initiator driving) to in (target driving),
+ * we must wait at least a data release delay plus
+ * the normal bus settle delay. [SCSI III SPI 10.11.0]
+ */
+ cmp DINDEX, A je change_phase_wait;
+ test SINDEX, IOI jz change_phase_wait;
+ call change_phase_wait;
+change_phase_wait:
+ nop;
+ nop;
+ nop;
+ nop ret;
-dma_fifoempty:
- /* Don't clobber an inprogress host data transfer */
- test DFSTATUS, MREQPEND jnz dma_fifoempty;
/*
- * Now shut the DMA enables off and make sure that the DMA enables are
- * actually off first lest we get an ILLSADDR.
+ * Send a byte to an initiator in Automatic PIO mode.
*/
-dma_dmadone:
- cmp LASTPHASE, P_COMMAND je dma_await_nreq;
- test SCSIRATE, 0x0f jnz dma_shutdown;
-dma_await_nreq:
- test SCSISIGI, REQI jz dma_shutdown;
- test SSTAT1, (PHASEMIS|REQINIT) jz dma_await_nreq;
-dma_shutdown:
- and DFCNTRL, ~(SCSIEN|SDMAEN|HDMAEN);
-dma_halt:
- /*
- * Some revisions of the aic7880 have a problem where, if the
- * data fifo is full, but the PCI input latch is not empty,
- * HDMAEN cannot be cleared. The fix used here is to attempt
- * to drain the data fifo until there is space for the input
- * latch to drain and HDMAEN de-asserts.
- */
- mov NONE, DFDAT;
- test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz dma_halt;
+target_outb:
+ or SXFRCTL0, SPIOEN;
+ test SSTAT0, SPIORDY jz .;
+ mov SCSIDATL, SINDEX;
+ test SSTAT0, SPIORDY jz .;
+ and SXFRCTL0, ~SPIOEN ret;
}
-return:
- ret;
+
/*
* Assert that if we've been reselected, then we've seen an IDENTIFY
@@ -1093,138 +1840,69 @@
assert:
test SEQ_FLAGS,IDENTIFY_SEEN jnz return; /* seen IDENTIFY? */
- mvi INTSTAT,NO_IDENT ret; /* no - tell the kernel */
+ mvi NO_IDENT jmp set_seqint; /* no - tell the kernel */
/*
- * Locate a disconnected SCB either by SAVED_TCL (ARG_1 is SCB_LIST_NULL)
- * or by the SCBID ARG_1. The search begins at the SCB index passed in
- * via SINDEX which is an SCB that must be on the disconnected list. If
- * the SCB cannot be found, SINDEX will be SCB_LIST_NULL, otherwise, SCBPTR
- * is set to the proper SCB.
+ * Locate a disconnected SCB by SCBID. Upon return, SCBPTR and SINDEX will
+ * be set to the position of the SCB. If the SCB cannot be found locally,
+ * it will be paged in from host memory. RETURN_2 stores the address of the
+ * preceding SCB in the disconnected list which can be used to speed up
+ * removal of the found SCB from the disconnected list.
*/
+if ((ahc->flags & AHC_PAGESCBS) != 0) {
+BEGIN_CRITICAL
findSCB:
- mov SCBPTR,SINDEX; /* Initialize SCBPTR */
- cmp ARG_1, SCB_LIST_NULL jne findSCB_by_SCBID;
- mov A, SAVED_TCL;
- mvi SCB_TCL jmp findSCB_loop; /* &SCB_TCL -> SINDEX */
-findSCB_by_SCBID:
- mov A, ARG_1; /* Tag passed in ARG_1 */
- mvi SCB_TAG jmp findSCB_loop; /* &SCB_TAG -> SINDEX */
+ mov A, SINDEX; /* Tag passed in SINDEX */
+ cmp DISCONNECTED_SCBH, SCB_LIST_NULL je findSCB_notFound;
+ mov SCBPTR, DISCONNECTED_SCBH; /* Initialize SCBPTR */
+ mvi ARG_2, SCB_LIST_NULL; /* Head of list */
+ jmp findSCB_loop;
findSCB_next:
- mov ARG_2, SCBPTR;
- cmp SCB_NEXT, SCB_LIST_NULL je notFound;
+ cmp SCB_NEXT, SCB_LIST_NULL je findSCB_notFound;
+ mov ARG_2, SCBPTR;
mov SCBPTR,SCB_NEXT;
- dec SINDEX; /* Last comparison moved us too far */
findSCB_loop:
- cmp SINDIR, A jne findSCB_next;
- mov SINDEX, SCBPTR ret;
-notFound:
- mvi SINDEX, SCB_LIST_NULL ret;
-
-/*
- * Retrieve an SCB by SCBID first searching the disconnected list falling
- * back to DMA'ing the SCB down from the host. This routine assumes that
- * ARG_1 is the SCBID of interrest and that SINDEX is the position in the
- * disconnected list to start the search from. If SINDEX is SCB_LIST_NULL,
- * we go directly to the host for the SCB.
- */
-retrieveSCB:
- test SEQ_FLAGS, SCBPTR_VALID jz retrieve_from_host;
- mov SCBPTR call findSCB; /* Continue the search */
- cmp SINDEX, SCB_LIST_NULL je retrieve_from_host;
-
-/*
- * This routine expects SINDEX to contain the index of the SCB to be
- * removed, SCBPTR to be pointing to that SCB, and ARG_2 to be the
- * SCBID of the SCB just previous to this one in the list or SCB_LIST_NULL
- * if it is at the head.
- */
+ cmp SCB_TAG, A jne findSCB_next;
rem_scb_from_disc_list:
-/* Remove this SCB from the disconnection list */
- cmp ARG_2, SCB_LIST_NULL je rHead;
+ cmp ARG_2, SCB_LIST_NULL je rHead;
mov DINDEX, SCB_NEXT;
+ mov SINDEX, SCBPTR;
mov SCBPTR, ARG_2;
mov SCB_NEXT, DINDEX;
mov SCBPTR, SINDEX ret;
rHead:
mov DISCONNECTED_SCBH,SCB_NEXT ret;
-
-retrieve_from_host:
-/*
- * We didn't find it. Pull an SCB and DMA down the one we want.
- * We should never get here in the non-paging case.
- */
- mov ALLZEROS call get_free_or_disc_scb;
+END_CRITICAL
+findSCB_notFound:
+ /*
+ * We didn't find it. Page in the SCB.
+ */
+ mov ARG_1, A; /* Save tag */
+ mov ALLZEROS call get_free_or_disc_scb;
mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET;
- /* Jump instead of call as we want to return anyway */
mov ARG_1 jmp dma_scb;
-
-/*
- * Determine whether a target is using tagged or non-tagged transactions
- * by first looking for a matching transaction based on the TCL and if
- * that fails, looking up this device in the host's untagged SCB array.
- * The TCL to search for is assumed to be in SAVED_TCL. The value is
- * returned in ARG_1 (SCB_LIST_NULL for tagged, SCBID for non-tagged).
- * The SCBPTR_VALID bit is set in SEQ_FLAGS if we found the information
- * in an SCB instead of having to go to the host.
- */
-get_untagged_SCBID:
- cmp DISCONNECTED_SCBH, SCB_LIST_NULL je get_SCBID_from_host;
- mvi ARG_1, SCB_LIST_NULL;
- mov DISCONNECTED_SCBH call findSCB;
- cmp SINDEX, SCB_LIST_NULL je get_SCBID_from_host;
- or SEQ_FLAGS, SCBPTR_VALID;/* Was in disconnected list */
- test SCB_CONTROL, TAG_ENB jnz . + 2;
- mov ARG_1, SCB_TAG ret;
- mvi ARG_1, SCB_LIST_NULL ret;
-
-/*
- * Fetch a byte from host memory given an index of (A + (256 * SINDEX))
- * and a base address of SCBID_ADDR. The byte is returned in RETURN_2.
- */
-fetch_byte:
- mov ARG_2, SINDEX;
- if ((p->features & AHC_CMD_CHAN) != 0) {
- mvi DINDEX, CCHADDR;
- mvi SCBID_ADDR call set_1byte_addr;
- mvi CCHCNT, 1;
- mvi CCSGCTL, CCSGEN|CCSGRESET;
- test CCSGCTL, CCSGDONE jz .;
- mvi CCSGCTL, CCSGRESET;
- bmov RETURN_2, CCSGRAM, 1 ret;
- } else {
- mvi DINDEX, HADDR;
- mvi SCBID_ADDR call set_1byte_addr;
- mvi HCNT[0], 1;
- clr HCNT[1];
- clr HCNT[2];
- mvi DFCNTRL, HDMAEN|DIRECTION|FIFORESET;
- call dma_finish;
- mov RETURN_2, DFDAT ret;
- }
+}
/*
* Prepare the hardware to post a byte to host memory given an
- * index of (A + (256 * SINDEX)) and a base address of SCBID_ADDR.
+ * index of (A + (256 * SINDEX)) and a base address of SHARED_DATA_ADDR.
*/
post_byte_setup:
mov ARG_2, SINDEX;
- if ((p->features & AHC_CMD_CHAN) != 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
mvi DINDEX, CCHADDR;
- mvi SCBID_ADDR call set_1byte_addr;
+ mvi SHARED_DATA_ADDR call set_1byte_addr;
mvi CCHCNT, 1;
mvi CCSCBCTL, CCSCBRESET ret;
} else {
mvi DINDEX, HADDR;
- mvi SCBID_ADDR call set_1byte_addr;
- mvi HCNT[0], 1;
- clr HCNT[1];
- clr HCNT[2];
+ mvi SHARED_DATA_ADDR call set_1byte_addr;
+ mvi 1 call set_hcnt;
mvi DFCNTRL, FIFORESET ret;
}
post_byte:
- if ((p->features & AHC_CMD_CHAN) != 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
bmov CCSCBRAM, SINDEX, 1;
or CCSCBCTL, CCSCBEN|CCSCBRESET;
test CCSCBCTL, CCSCBDONE jz .;
@@ -1235,23 +1913,34 @@
jmp dma_finish;
}
-get_SCBID_from_host:
- mov A, SAVED_TCL;
- mvi UNTAGGEDSCB_OFFSET call fetch_byte;
- mov RETURN_1, RETURN_2 ret;
-
+phase_lock_perr:
+ mvi PERR_DETECTED call set_seqint;
phase_lock:
+ /*
+ * If there is a parity error, wait for the kernel to
+ * see the interrupt and prepare our message response
+ * before continuing.
+ */
test SSTAT1, REQINIT jz phase_lock;
- test SSTAT1, SCSIPERR jnz phase_lock;
+ test SSTAT1, SCSIPERR jnz phase_lock_perr;
+phase_lock_latch_phase:
and SCSISIGO, PHASE_MASK, SCSISIGI;
and LASTPHASE, PHASE_MASK, SCSISIGI ret;
-if ((p->features & AHC_CMD_CHAN) == 0) {
+if ((ahc->features & AHC_CMD_CHAN) == 0) {
+set_hcnt:
+ mov HCNT[0], SINDEX;
+clear_hcnt:
+ clr HCNT[1];
+ clr HCNT[2] ret;
+
set_stcnt_from_hcnt:
mov STCNT[0], HCNT[0];
mov STCNT[1], HCNT[1];
mov STCNT[2], HCNT[2] ret;
+bcopy_8:
+ mov DINDIR, SINDIR;
bcopy_7:
mov DINDIR, SINDIR;
mov DINDIR, SINDIR;
@@ -1265,6 +1954,7 @@
mov DINDIR, SINDIR ret;
}
+if ((ahc->flags & AHC_TARGETROLE) != 0) {
/*
* Setup addr assuming that A is an index into
* an array of 32byte objects, SINDEX contains
@@ -1275,16 +1965,30 @@
set_32byte_addr:
shr ARG_2, 3, A;
shl A, 5;
+ jmp set_1byte_addr;
+}
+
+/*
+ * Setup addr assuming that A is an index into
+ * an array of 64byte objects, SINDEX contains
+ * the base address of that array, and DINDEX
+ * contains the base address of the location
+ * to store the indexed address.
+ */
+set_64byte_addr:
+ shr ARG_2, 2, A;
+ shl A, 6;
+
/*
- * Setup addr assuming that A + (ARG_1 * 256) is an
+ * Setup addr assuming that A + (ARG_2 * 256) is an
* index into an array of 1byte objects, SINDEX contains
* the base address of that array, and DINDEX contains
* the base address of the location to store the computed
* address.
*/
set_1byte_addr:
- add DINDIR, A, SINDIR;
- mov A, ARG_2;
+ add DINDIR, A, SINDIR;
+ mov A, ARG_2;
adc DINDIR, A, SINDIR;
clr A;
adc DINDIR, A, SINDIR;
@@ -1296,21 +2000,32 @@
*/
dma_scb:
mov A, SINDEX;
- if ((p->features & AHC_CMD_CHAN) != 0) {
+ if ((ahc->features & AHC_CMD_CHAN) != 0) {
mvi DINDEX, CCHADDR;
- mvi HSCB_ADDR call set_32byte_addr;
+ mvi HSCB_ADDR call set_64byte_addr;
mov CCSCBPTR, SCBPTR;
- mvi CCHCNT, 32;
test DMAPARAMS, DIRECTION jz dma_scb_tohost;
+ if ((ahc->flags & AHC_SCB_BTT) != 0) {
+ mvi CCHCNT, SCB_DOWNLOAD_SIZE_64;
+ } else {
+ mvi CCHCNT, SCB_DOWNLOAD_SIZE;
+ }
mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBDIR|CCSCBRESET;
cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN|CCSCBDIR jne .;
jmp dma_scb_finish;
dma_scb_tohost:
- if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) {
+ mvi CCHCNT, SCB_UPLOAD_SIZE;
+ if ((ahc->features & AHC_ULTRA2) == 0) {
mvi CCSCBCTL, CCSCBRESET;
- bmov CCSCBRAM, SCB_CONTROL, 32;
+ bmov CCSCBRAM, SCB_BASE, SCB_UPLOAD_SIZE;
or CCSCBCTL, CCSCBEN|CCSCBRESET;
test CCSCBCTL, CCSCBDONE jz .;
+ } else if ((ahc->bugs & AHC_SCBCHAN_UPLOAD_BUG) != 0) {
+ mvi CCSCBCTL, CCARREN|CCSCBRESET;
+ cmp CCSCBCTL, ARRDONE|CCARREN jne .;
+ mvi CCHCNT, SCB_UPLOAD_SIZE;
+ mvi CCSCBCTL, CCSCBEN|CCSCBRESET;
+ cmp CCSCBCTL, CCSCBDONE|CCSCBEN jne .;
} else {
mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBRESET;
cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN jne .;
@@ -1321,85 +2036,161 @@
ret;
} else {
mvi DINDEX, HADDR;
- mvi HSCB_ADDR call set_32byte_addr;
- mvi HCNT[0], 32;
- clr HCNT[1];
- clr HCNT[2];
+ mvi HSCB_ADDR call set_64byte_addr;
+ mvi SCB_DOWNLOAD_SIZE call set_hcnt;
mov DFCNTRL, DMAPARAMS;
test DMAPARAMS, DIRECTION jnz dma_scb_fromhost;
/* Fill it with the SCB data */
copy_scb_tofifo:
- mvi SINDEX, SCB_CONTROL;
- add A, 32, SINDEX;
+ mvi SINDEX, SCB_BASE;
+ add A, SCB_DOWNLOAD_SIZE, SINDEX;
copy_scb_tofifo_loop:
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
- mov DFDAT,SINDIR;
+ call copy_to_fifo_8;
cmp SINDEX, A jne copy_scb_tofifo_loop;
or DFCNTRL, HDMAEN|FIFOFLUSH;
+ jmp dma_finish;
dma_scb_fromhost:
- call dma_finish;
+ mvi DINDEX, SCB_BASE;
+ if ((ahc->bugs & AHC_PCI_2_1_RETRY_BUG) != 0) {
+ /*
+ * The PCI module will only issue a PCI
+ * retry if the data FIFO is empty. If the
+ * host disconnects in the middle of a
+ * transfer, we must empty the fifo of all
+ * available data to force the chip to
+ * continue the transfer. This does not
+ * happen for SCSI transfers as the SCSI module
+ * will drain the FIFO as data is made available.
+ * When the hang occurs, we know that a multiple
+ * of 8 bytes are in the FIFO because the PCI
+ * module has an 8 byte input latch that only
+ * dumps to the FIFO when HCNT == 0 or the
+ * latch is full.
+ */
+ clr A;
+ /* Wait for some data to arrive. */
+dma_scb_hang_fifo:
+ test DFSTATUS, FIFOEMP jnz dma_scb_hang_fifo;
+dma_scb_hang_wait:
+ test DFSTATUS, MREQPEND jnz dma_scb_hang_wait;
+ test DFSTATUS, HDONE jnz dma_scb_hang_dma_done;
+ test DFSTATUS, HDONE jnz dma_scb_hang_dma_done;
+ test DFSTATUS, HDONE jnz dma_scb_hang_dma_done;
+ /*
+ * The PCI module no longer intends to perform
+ * a PCI transaction and HDONE has not come true.
+ * We are hung. Drain the fifo.
+ */
+dma_scb_hang_empty_fifo:
+ /*
+ * Skip lines not yet transfered into the FIFO.
+ */
+ add SINDEX, 7, HCNT;
+ shr SINDEX, 3;
+
+ /*
+ * Skip lines already copied out of the FIFO.
+ */
+ add A, A, SINDEX;
+
+ call dma_scb_hang_dma_drain_fifo;
+
+ /*
+ * Set the lines transferred to all but
+ * those yet to reach the FIFO.
+ */
+ not SINDEX;
+ add A, 5, SINDEX;
+ jmp dma_scb_hang_fifo;
+dma_scb_hang_dma_done:
+ and DFCNTRL, ~HDMAEN;
+ test DFCNTRL, HDMAEN jnz .;
+dma_scb_hang_dma_drain_fifo:
+ add SEQADDR0, A;
+ } else {
+ call dma_finish;
+ }
/* If we were putting the SCB, we are done */
- test DMAPARAMS, DIRECTION jz return;
- mvi SCB_CONTROL call dfdat_in_7;
- call dfdat_in_7_continued;
- call dfdat_in_7_continued;
- jmp dfdat_in_7_continued;
+ call dfdat_in_8;
+ call dfdat_in_8;
+ call dfdat_in_8;
+dfdat_in_8:
+ mov DINDIR,DFDAT;
dfdat_in_7:
- mov DINDEX,SINDEX;
-dfdat_in_7_continued:
mov DINDIR,DFDAT;
mov DINDIR,DFDAT;
mov DINDIR,DFDAT;
mov DINDIR,DFDAT;
mov DINDIR,DFDAT;
+dfdat_in_2:
mov DINDIR,DFDAT;
mov DINDIR,DFDAT ret;
}
+copy_to_fifo_8:
+ mov DFDAT,SINDIR;
+ mov DFDAT,SINDIR;
+copy_to_fifo_6:
+ mov DFDAT,SINDIR;
+copy_to_fifo_5:
+ mov DFDAT,SINDIR;
+copy_to_fifo_4:
+ mov DFDAT,SINDIR;
+ mov DFDAT,SINDIR;
+ mov DFDAT,SINDIR;
+ mov DFDAT,SINDIR ret;
/*
* Wait for DMA from host memory to data FIFO to complete, then disable
* DMA and wait for it to acknowledge that it's off.
*/
-if ((p->features & AHC_CMD_CHAN) == 0) {
dma_finish:
test DFSTATUS,HDONE jz dma_finish;
/* Turn off DMA */
and DFCNTRL, ~HDMAEN;
test DFCNTRL, HDMAEN jnz .;
ret;
-}
+/*
+ * Restore an SCB that failed to match an incoming reselection
+ * to the correct/safe state. If the SCB is for a disconnected
+ * transaction, it must be returned to the disconnected list.
+ * If it is not in the disconnected state, it must be free.
+ */
+cleanup_scb:
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+ test SCB_CONTROL,DISCONNECTED jnz add_scb_to_disc_list;
+ }
add_scb_to_free_list:
- if ((p->flags & AHC_PAGESCBS) != 0) {
+ if ((ahc->flags & AHC_PAGESCBS) != 0) {
+BEGIN_CRITICAL
mov SCB_NEXT, FREE_SCBH;
- mov FREE_SCBH, SCBPTR;
+ mvi SCB_TAG, SCB_LIST_NULL;
+ mov FREE_SCBH, SCBPTR ret;
+END_CRITICAL
+ } else {
+ mvi SCB_TAG, SCB_LIST_NULL ret;
}
- mvi SCB_TAG, SCB_LIST_NULL ret;
-if ((p->flags & AHC_PAGESCBS) != 0) {
+if ((ahc->flags & AHC_PAGESCBS) != 0) {
get_free_or_disc_scb:
+BEGIN_CRITICAL
cmp FREE_SCBH, SCB_LIST_NULL jne dequeue_free_scb;
cmp DISCONNECTED_SCBH, SCB_LIST_NULL jne dequeue_disc_scb;
return_error:
+ mvi NO_FREE_SCB call set_seqint;
mvi SINDEX, SCB_LIST_NULL ret;
dequeue_disc_scb:
mov SCBPTR, DISCONNECTED_SCBH;
-dma_up_scb:
+ mov DISCONNECTED_SCBH, SCB_NEXT;
+END_CRITICAL
mvi DMAPARAMS, FIFORESET;
- mov SCB_TAG call dma_scb;
-unlink_disc_scb:
- mov DISCONNECTED_SCBH, SCB_NEXT ret;
+ mov SCB_TAG jmp dma_scb;
+BEGIN_CRITICAL
dequeue_free_scb:
mov SCBPTR, FREE_SCBH;
mov FREE_SCBH, SCB_NEXT ret;
-}
+END_CRITICAL
add_scb_to_disc_list:
/*
@@ -1407,5 +2198,13 @@
* candidates for paging out an SCB if one is needed for a new command.
* Modifying the disconnected list is a critical(pause dissabled) section.
*/
+BEGIN_CRITICAL
mov SCB_NEXT, DISCONNECTED_SCBH;
mov DISCONNECTED_SCBH, SCBPTR ret;
+END_CRITICAL
+}
+set_seqint:
+ mov INTSTAT, SINDEX;
+ nop;
+return:
+ ret;
FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)