diff options
Diffstat (limited to 'drivers/scsi/aic7xxx_old/aic7xxx.seq')
-rw-r--r-- | drivers/scsi/aic7xxx_old/aic7xxx.seq | 1539 |
1 files changed, 1539 insertions, 0 deletions
diff --git a/drivers/scsi/aic7xxx_old/aic7xxx.seq b/drivers/scsi/aic7xxx_old/aic7xxx.seq new file mode 100644 index 000000000000..f6fc4b75b5a5 --- /dev/null +++ b/drivers/scsi/aic7xxx_old/aic7xxx.seq @@ -0,0 +1,1539 @@ +/* + * Adaptec 274x/284x/294x device driver firmware for Linux and FreeBSD. + * + * Copyright (c) 1994-1999 Justin Gibbs. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * 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. + * 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 General 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * 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 $ + */ + +#include "aic7xxx.reg" +#include "scsi_message.h" + +/* + * A few words on the waiting SCB list: + * After starting the selection hardware, we check for reconnecting targets + * as well as for our selection to complete just in case the reselection wins + * bus arbitration. The problem with this is that we must keep track of the + * SCB that we've already pulled from the QINFIFO and started the selection + * on just in case the reselection wins so that we can retry the selection at + * 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 + * 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 + * command for which a second SCB has been queued. The sequencer will + * 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; +poll_for_work: + and SXFRCTL0, ~SPIOEN; + if ((p->features & AHC_QUEUE_REGS) == 0) { + mov A, QINPOS; + } +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. + */ + 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 */ + } + cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting; +test_queue: + /* Has the driver posted any work for us? */ + if ((p->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; + cmp KERNEL_QINPOS, A je poll_for_work_loop; + inc QINPOS; + and SEQCTL, ~PAUSEDIS; + } + +/* + * 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) { + 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) { + /* In the non-paging case, the SCBID == hardware SCB index */ + mov SCBPTR, RETURN_2; + } +dma_queued_scb: +/* + * 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: + /* + * Place us on the waiting list in case our selection + * doesn't win during bus arbitration. + */ + mov SCB_NEXT,WAITING_SCBH; + mov WAITING_SCBH, SCBPTR; +start_waiting: + /* + * Pull the first entry off of the waiting SCB list. + */ + mov SCBPTR, WAITING_SCBH; + call start_selection; + jmp poll_for_work; + +start_selection: + if ((p->features & AHC_TWIN) != 0) { + and SINDEX,~SELBUSB,SBLKCTL;/* Clear channel select bit */ + and A,SELBUSB,SCB_TCL; /* Get new channel bit */ + or SINDEX,A; + 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; + +/* + * Initialize Ultra mode setting and clear the SCSI channel. + * SINDEX should contain any additional bit's the client wants + * set in SXFRCTL0. + */ +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, 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; + } + + add SINDEX,TARG_SCSIRATE,A; + mov SCSIRATE,SINDIR ret; + + +selection: + test SSTAT0,SELDO jnz select_out; +/* + * 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: + 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; + +/* + * After the selection, remove this SCB from the "waiting SCB" + * list. This is achieved by simply moving our "next" pointer into + * WAITING_SCBH. Our next pointer will be set to null the next time this + * SCB is used, so don't bother with it now. + */ +select_out: + /* Turn off the selection hardware */ + mvi SCSISEQ, ENRSELI|ENAUTOATNP; /* + * ATN on parity errors + * for "in" phases + */ + 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. + */ + 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. + */ +ITloop: + call phase_lock; + + mov A, LASTPHASE; + + test A, ~P_DATAIN jz p_data; + cmp A,P_COMMAND je p_command; + cmp A,P_MESGOUT je p_mesgout; + cmp A,P_STATUS je p_status; + cmp A,P_MESGIN je p_mesgin; + + mvi INTSTAT,BAD_PHASE; /* unknown phase - signal driver */ + jmp ITloop; /* Try reading the bus again. */ + +await_busfree: + and SIMODE1, ~ENBUSFREE; + call clear_target_state; + mov NONE, SCSIDATL; /* Ack the last byte */ + and SXFRCTL0, ~SPIOEN; + test SSTAT1,REQINIT|BUSFREE jz .; + test SSTAT1, BUSFREE jnz poll_for_work; + mvi INTSTAT, BAD_PHASE; + +clear_target_state: + /* + * We assume that the kernel driver may reset us + * at any time, even in the middle of a DMA, so + * clear DFCNTRL too. + */ + clr DFCNTRL; + + /* + * 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; + } else { + clr SCSIRATE; + and SXFRCTL0, ~(FAST20); + } + mvi LASTPHASE, P_BUSFREE; + /* clear target specific flags */ + clr SEQ_FLAGS ret; + + +data_phase_reinit: +/* + * 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. + * On Ultra2, we have to put it into the HCNT field because we have to + * drop the data down into the shadow layer via the preload ability. + */ + if ((p->features & AHC_ULTRA2) != 0) { + 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) { + mvi DINDEX, STCNT; + mvi SCB_RESID_DCNT call bcopy_3; + } + jmp data_phase_loop; +p_data: + if ((p->features & AHC_ULTRA2) != 0) { + mvi DMAPARAMS, PRELOADEN|SCSIEN|HDMAEN; + } else { + mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET; + } + test LASTPHASE, IOI jnz . + 2; + or DMAPARAMS, DIRECTION; + call assert; /* + * Ensure entering a data + * phase is okay - seen identify, etc. + */ + if ((p->features & AHC_CMD_CHAN) != 0) { + mvi CCSGADDR, CCSGADDR_MAX; + } + + test SEQ_FLAGS, DPHASE jnz data_phase_reinit; + or SEQ_FLAGS, DPHASE; /* we've seen a data phase */ + /* + * 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. + */ + if ((p->features & AHC_CMD_CHAN) != 0) { + bmov HADDR, SCB_DATAPTR, 7; + bmov SG_COUNT, SCB_SGCOUNT, 5; + if ((p->features & AHC_ULTRA2) == 0) { + bmov STCNT, HCNT, 3; + } + } 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; + } +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. + */ + or SXFRCTL1,BITBUCKET; + and DMAPARAMS, ~(HDMAEN|SDMAEN); + if ((p->features & AHC_ULTRA2) != 0) { + bmov HCNT, ALLONES, 3; + } + if ((p->chip & AHC_CHIPID_MASK) == AHC_AIC7895) { + bmov STCNT, ALLONES, 3; + } + if ((p->features & AHC_CMD_CHAN) == 0) { + mvi STCNT[0], 0xFF; + mvi STCNT[1], 0xFF; + mvi STCNT[2], 0xFF; + } + +data_phase_inbounds: +/* If we are the last SG block, tell the hardware. */ + if ((p->features & AHC_ULTRA2) != 0) { + shl A, 2, SG_COUNT; + cmp SG_COUNT,0x01 jne data_phase_wideodd; + or A, LAST_SEG; + } else { + cmp SG_COUNT,0x01 jne data_phase_wideodd; + and DMAPARAMS, ~WIDEODD; + } +data_phase_wideodd: + if ((p->features & AHC_ULTRA2) != 0) { + mov SG_CACHEPTR, A; + mov DFCNTRL, DMAPARAMS; /* start the operation */ + test SXFRCTL1, BITBUCKET jnz data_phase_overrun; +u2_preload_wait: + test SSTAT1, PHASEMIS jnz u2_phasemis; + test DFSTATUS, PRELOAD_AVAIL jz u2_preload_wait; + } else { + mov DMAPARAMS call dma; +data_phase_dma_done: +/* Go tell the host about any overruns */ + test SXFRCTL1,BITBUCKET jnz data_phase_overrun; + +/* Exit if we had an underrun. dma clears SINDEX in this case. */ + test SINDEX,0xff jz data_phase_finish; + } +/* + * Advance the scatter-gather pointers + */ +sg_advance: + if ((p->features & AHC_ULTRA2) != 0) { + cmp SG_COUNT, 0x01 je u2_data_phase_finish; + } else { + dec SG_COUNT; + test SG_COUNT, 0xff jz data_phase_finish; + } + + if ((p->features & AHC_CMD_CHAN) != 0) { + + /* + * Do we have any prefetch left??? + */ + cmp CCSGADDR, CCSGADDR_MAX jne prefetch_avail; + + /* + * Fetch MIN(CCSGADDR_MAX, (SG_COUNT * 8)) bytes. + */ + 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; +prefetch_avail: + bmov HADDR, CCSGRAM, 8; + if ((p->features & AHC_ULTRA2) == 0) { + bmov STCNT, HCNT, 3; + } else { + dec SG_COUNT; + } + } else { + mvi DINDEX, HADDR; + mvi SG_NEXT call bcopy_4; + + mvi HCNT[0],SG_SIZEOF; + clr HCNT[1]; + clr HCNT[2]; + + or DFCNTRL, HDMAEN|DIRECTION|FIFORESET; + + 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 DINDEX, HADDR; + call dfdat_in_7; + call set_stcnt_from_hcnt; + } +/* Advance the SG pointer */ + clr A; /* add sizeof(struct scatter) */ + add SG_NEXT[0],SG_SIZEOF; + adc SG_NEXT[1],A; + + if ((p->features & AHC_ULTRA2) != 0) { + jmp data_phase_loop; + } else { + test SSTAT1, REQINIT jz .; + test SSTAT1,PHASEMIS jz data_phase_loop; + } + + +/* + * We've loaded all of our segments into the preload layer. Now, we simply + * have to wait for it to finish or for us to get a phasemis. And, since + * we'll get a phasemis if we do finish, all we really need to do is wait + * for a phasemis then check if we did actually complete all the segments. + */ + if ((p->features & AHC_ULTRA2) != 0) { +u2_data_phase_finish: + test SSTAT1, PHASEMIS jnz u2_phasemis; + test SG_CACHEPTR, LAST_SEG_DONE jz u2_data_phase_finish; + clr SG_COUNT; + test SSTAT1, REQINIT jz .; + test SSTAT1, PHASEMIS jz data_phase_loop; +u2_phasemis: + call ultra2_dmafinish; + test SG_CACHEPTR, LAST_SEG_DONE jnz data_phase_finish; + test SSTAT2, SHVALID jnz u2_fixup_residual; + mvi INTSTAT, SEQ_SG_FIXUP; + jmp data_phase_finish; +u2_fixup_residual: + shr ARG_1, 2, SG_CACHEPTR; +u2_phasemis_loop: + and A, 0x3f, SG_COUNT; + cmp ARG_1, A je data_phase_finish; +/* + * Subtract SG_SIZEOF from the SG_NEXT pointer and add 1 to the SG_COUNT + */ + clr A; + add SG_NEXT[0], -SG_SIZEOF; + adc SG_NEXT[1], 0xff; + inc SG_COUNT; + jmp u2_phasemis_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_CMD_CHAN) != 0) { + bmov SCB_RESID_DCNT, STCNT, 3; + mov SCB_RESID_SGCNT, SG_COUNT; + if ((p->features & AHC_ULTRA2) != 0) { + or SXFRCTL0, CLRSTCNT|CLRCHN; + } + } 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; + } + + jmp ITloop; + +data_phase_overrun: +/* + * Turn off BITBUCKET mode and notify the host + */ + if ((p->features & AHC_ULTRA2) != 0) { +/* + * Wait for the target to quit transferring data on the SCSI bus + */ + test SSTAT1, PHASEMIS jz .; + call ultra2_dmafinish; + } + and SXFRCTL1, ~BITBUCKET; + mvi INTSTAT,DATA_OVERRUN; + jmp ITloop; + + + + +/* + * Actually turn off the DMA hardware, save our current position into the + * proper residual variables, wait for the next REQ signal, then jump to + * the ITloop. Jumping to the ITloop ensures that if we happen to get + * brought into the data phase again (or are still in it after our last + * segment) that we will properly signal an overrun to the kernel. + */ + if ((p->features & AHC_ULTRA2) != 0) { +ultra2_dmafinish: + test DFCNTRL, DIRECTION jnz ultra2_dmahalt; + and DFCNTRL, ~SCSIEN; + test DFCNTRL, SCSIEN jnz .; + if ((p->bugs & AHC_BUG_AUTOFLUSH) != 0) { + or DFCNTRL, FIFOFLUSH; + } +ultra2_dmafifoflush: + if ((p->bugs & AHC_BUG_AUTOFLUSH) != 0) { + /* + * hardware bug alert! This needless set of jumps + * works around a glitch in the silicon. When the + * PCI DMA fifo goes empty, but there is still SCSI + * data to be flushed into the PCI DMA fifo (and from + * there on into main memory), the FIFOEMP bit will + * come on between the time when the PCI DMA buffer + * went empty and the next bit of data is copied from + * the SCSI fifo into the PCI fifo. It should only + * come on when both FIFOs (meaning the entire FIFO + * chain) are emtpy. Since it can take up to 4 cycles + * for new data to be copied from the SCSI fifo into + * the PCI fifo, testing for FIFOEMP status for 4 + * extra times gives the needed time for any + * remaining SCSI fifo data to be put in the PCI fifo + * before we declare it *truly* empty. + */ + 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: + and DFCNTRL, ~(HDMAEN|SCSIEN); + test DFCNTRL, (HDMAEN|SCSIEN) jnz .; + ret; + } + +/* + * 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; + bmov HCNT[1], ALLZEROS, 2; + if ((p->features & AHC_ULTRA2) == 0) { + bmov STCNT, HCNT, 3; + } + } else { + mvi DINDEX, HADDR; + mvi SCB_CMDPTR call bcopy_5; + clr HCNT[1]; + clr HCNT[2]; + call set_stcnt_from_hcnt; + } + + if ((p->features & AHC_ULTRA2) == 0) { + mvi (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET) call dma; + } 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; + } + jmp ITloop; + +/* + * Status phase. Wait for the data byte to appear, then read it + * and store it into the SCB. + */ +p_status: + call assert; + + mov SCB_TARGET_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. + * + * 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 + * 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). + * + * When there are no messages to send, MSG_OUT should be set to MSG_NOOP, + * in case the target decides to put us in this phase for some strange + * reason. + */ +p_mesgout_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 */ + } + 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 */ +/* + * 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. + */ +p_mesgout_tag: + test SCB_CONTROL,TAG_ENB jz p_mesgout_onebyte; + mov SCSIDATL, SINDEX; /* Send the identify message */ + call phase_lock; + cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; + and SCSIDATL,TAG_ENB|SCB_TAG_TYPE,SCB_CONTROL; + call phase_lock; + 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. + */ +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; + +p_mesgout_onebyte: + mvi CLRSINT1, CLRATNO; + mov SCSIDATL, SINDEX; + +/* + * If the next bus phase after ATN drops is a 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; + +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; + +/* + * Message in phase. Bytes are read using Automatic PIO mode. + */ +p_mesgin: + mvi ACCUM call inb_first; /* read the 1st message byte */ + + test A,MSG_IDENTIFYFLAG jnz mesgin_identify; + cmp A,MSG_DISCONNECT je mesgin_disconnect; + 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_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. + */ + mvi INTSTAT,SEND_REJECT; /* let driver know */ + + 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, + * 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. + */ + +/* + * First check for residuals + */ + test SCB_RESID_SGCNT,0xff jnz upload_scb; + test SCB_TARGET_STATUS,0xff jz complete; /* Good Status? */ +upload_scb: + 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; + 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; + 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; + +complete_post: + /* Post the SCB and issue an interrupt */ + if ((p->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) { + 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; + +/* + * Is it a disconnect message? Set a flag in the SCB to remind us + * and await the bus going free. + */ +mesgin_disconnect: + or SCB_CONTROL,DISCONNECTED; + call add_scb_to_disc_list; + jmp await_busfree; + +/* + * Save data pointers message: + * Copying RAM values back to SCB, for Save Data Pointers message, but + * only if we've actually been into a data phase to change them. This + * protects against bogus data in scratch ram and the residual counts + * since they are only initialized when we go into data_in or data_out. + */ +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. + * 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; + } 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; + } + jmp mesgin_done; + +/* + * Restore pointers message? Data pointers are recopied from the + * SCB anytime we enter a data phase for the first time, so all + * we need to do is clear the DPHASE flag and let the data phase + * code do the rest. + */ +mesgin_rdptrs: + and SEQ_FLAGS, ~DPHASE; /* + * We'll reload them + * the next time through + * the dataphase. + */ + jmp mesgin_done; + +/* + * 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 */ + } else { + and A,0x07; /* lun in lower three bits */ + } + 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; + cmp ARG_1, SCB_LIST_NULL je snoop_tag; + if ((p->flags & AHC_PAGESCBS) != 0) { + test SEQ_FLAGS, SCBPTR_VALID jz use_retrieveSCB; + } + /* + * 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; +/* + * 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. + */ +snoop_tag: + mov NONE,SCSIDATL; /* ACK Identify MSG */ +snoop_tag_loop: + call phase_lock; + cmp LASTPHASE, P_MESGIN jne not_found; + cmp SCSIBUSL,MSG_SIMPLE_Q_TAG jne not_found; +get_tag: + mvi ARG_1 call inb_next; /* tag value */ + +use_retrieveSCB: + call retrieveSCB; +setup_SCB: + mov A, SAVED_TCL; + cmp SCB_TCL, A jne not_found_cleanup_scb; + test SCB_CONTROL,DISCONNECTED jz not_found_cleanup_scb; + and SCB_CONTROL,~DISCONNECTED; + or SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */ + /* 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; +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; + 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; + +/* + * Functions to read data in Automatic PIO mode. + * + * According to Adaptec's documentation, an ACK is not sent on input from + * the target until SCSIDATL is read from. So we wait until SCSIDATL is + * latched (the usual way), then read the data byte directly off the bus + * using SCSIBUSL. When we have pulled the ATN line, or we just want to + * acknowledge the byte, then we do a dummy read from SCISDATL. The SCSI + * spec guarantees that the target will hold the data byte on the bus until + * we send our ACK. + * + * The assumption here is that these are called in a particular sequence, + * and that REQ is already set when inb_first is called. inb_{first,next} + * use the same calling convention as inb. + */ + +inb_next: + mov NONE,SCSIDATL; /*dummy read from latch to ACK*/ +inb_next_wait: + /* + * 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 inb_next_wait; + test SSTAT1, SCSIPERR jnz .; + and LASTPHASE, PHASE_MASK, SCSISIGI; + cmp LASTPHASE, P_MESGIN jne mesgin_phasemis; +inb_first: + mov DINDEX,SINDEX; + mov DINDIR,SCSIBUSL ret; /*read byte directly from bus*/ +inb_last: + mov NONE,SCSIDATL ret; /*dummy read from latch to ACK*/ + + +mesgin_phasemis: +/* + * We expected to receive another byte, but the target changed phase + */ + 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 */ + +/* + * 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; +/* + * Now shut the DMA enables off and make sure that the DMA enables are + * actually off first lest we get an ILLSADDR. + */ +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. + */ + if ((p->bugs & AHC_BUG_PCI_2_1_RETRY) != 0) { + mov NONE, DFDAT; + } + test DFCNTRL, (SCSIEN|SDMAEN|HDMAEN) jnz dma_halt; +} +return: + ret; + +/* + * Assert that if we've been reselected, then we've seen an IDENTIFY + * message. + */ +assert: + test SEQ_FLAGS,IDENTIFY_SEEN jnz return; /* seen IDENTIFY? */ + + mvi INTSTAT,NO_IDENT ret; /* 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. + */ +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 */ +findSCB_next: + mov ARG_2, SCBPTR; + cmp SCB_NEXT, SCB_LIST_NULL je notFound; + 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. + */ +rem_scb_from_disc_list: +/* Remove this SCB from the disconnection list */ + cmp ARG_2, SCB_LIST_NULL je rHead; + mov DINDEX, SCB_NEXT; + 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; + 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. + */ +post_byte_setup: + 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 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 DFCNTRL, FIFORESET ret; + } + +post_byte: + if ((p->features & AHC_CMD_CHAN) != 0) { + bmov CCSCBRAM, SINDEX, 1; + or CCSCBCTL, CCSCBEN|CCSCBRESET; + test CCSCBCTL, CCSCBDONE jz .; + clr CCSCBCTL ret; + } else { + mov DFDAT, SINDEX; + or DFCNTRL, HDMAEN|FIFOFLUSH; + 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: + test SSTAT1, REQINIT jz phase_lock; + test SSTAT1, SCSIPERR jnz phase_lock; + and SCSISIGO, PHASE_MASK, SCSISIGI; + and LASTPHASE, PHASE_MASK, SCSISIGI ret; + +if ((p->features & AHC_CMD_CHAN) == 0) { +set_stcnt_from_hcnt: + mov STCNT[0], HCNT[0]; + mov STCNT[1], HCNT[1]; + mov STCNT[2], HCNT[2] ret; + +bcopy_7: + mov DINDIR, SINDIR; + mov DINDIR, SINDIR; +bcopy_5: + mov DINDIR, SINDIR; +bcopy_4: + mov DINDIR, SINDIR; +bcopy_3: + mov DINDIR, SINDIR; + mov DINDIR, SINDIR; + mov DINDIR, SINDIR ret; +} + +/* + * Setup addr assuming that A is an index into + * an array of 32byte objects, SINDEX contains + * the base address of that array, and DINDEX + * contains the base address of the location + * to store the indexed address. + */ +set_32byte_addr: + shr ARG_2, 3, A; + shl A, 5; +/* + * Setup addr assuming that A + (ARG_1 * 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; + adc DINDIR, A, SINDIR; + clr A; + adc DINDIR, A, SINDIR; + adc DINDIR, A, SINDIR ret; + +/* + * Either post or fetch and SCB from host memory based on the + * DIRECTION bit in DMAPARAMS. The host SCB index is in SINDEX. + */ +dma_scb: + mov A, SINDEX; + if ((p->features & AHC_CMD_CHAN) != 0) { + mvi DINDEX, CCHADDR; + mvi HSCB_ADDR call set_32byte_addr; + mov CCSCBPTR, SCBPTR; + mvi CCHCNT, 32; + test DMAPARAMS, DIRECTION jz dma_scb_tohost; + mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBDIR|CCSCBRESET; + cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN|CCSCBDIR jne .; + jmp dma_scb_finish; +dma_scb_tohost: + if ((p->features & AHC_ULTRA2) == 0) { + mvi CCSCBCTL, CCSCBRESET; + bmov CCSCBRAM, SCB_CONTROL, 32; + or CCSCBCTL, CCSCBEN|CCSCBRESET; + test CCSCBCTL, CCSCBDONE jz .; + } + if ((p->features & AHC_ULTRA2) != 0) { + if ((p->bugs & AHC_BUG_SCBCHAN_UPLOAD) != 0) { + mvi CCSCBCTL, CCARREN|CCSCBRESET; + cmp CCSCBCTL, ARRDONE|CCARREN jne .; + mvi CCHCNT, 32; + mvi CCSCBCTL, CCSCBEN|CCSCBRESET; + cmp CCSCBCTL, CCSCBDONE|CCSCBEN jne .; + } else { + mvi CCSCBCTL, CCARREN|CCSCBEN|CCSCBRESET; + cmp CCSCBCTL, CCSCBDONE|ARRDONE|CCARREN|CCSCBEN jne .; + } + } +dma_scb_finish: + clr CCSCBCTL; + test CCSCBCTL, CCARREN|CCSCBEN jnz .; + ret; + } + if ((p->features & AHC_CMD_CHAN) == 0) { + mvi DINDEX, HADDR; + mvi HSCB_ADDR call set_32byte_addr; + mvi HCNT[0], 32; + clr HCNT[1]; + clr HCNT[2]; + 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; +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; + cmp SINDEX, A jne copy_scb_tofifo_loop; + or DFCNTRL, HDMAEN|FIFOFLUSH; + jmp dma_finish; +dma_scb_fromhost: + mvi DINDEX, SCB_CONTROL; + if ((p->bugs & AHC_BUG_PCI_2_1_RETRY) != 0) { + /* + * Set the A to -24. It it hits 0, then we let + * our code fall through to dfdat_in_8 to complete + * the last of the copy. + * + * Also, things happen 8 bytes at a time in this + * case, so we may need to drain the fifo at most + * 3 times to keep things flowing + */ + mvi A, -24; +dma_scb_hang_fifo: + /* Wait for the first bit of data to hit the fifo */ + test DFSTATUS, FIFOEMP jnz .; +dma_scb_hang_wait: + /* OK, now they've started to transfer into the fifo, + * so wait for them to stop trying to transfer any + * more data. + */ + test DFSTATUS, MREQPEND jnz .; + /* + * OK, they started, then they stopped, now see if they + * managed to complete the job before stopping. Try + * it multiple times to give the chip a few cycles to + * set the flag if it did complete. + */ + 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; + /* + * Too bad, the chip didn't complete the DMA, but there + * aren't any more memory requests pending, so that + * means it stopped part way through and hung. That's + * our bug, so now we drain what data there is in the + * fifo in order to get things going again. + */ +dma_scb_hang_empty_fifo: + call dfdat_in_8; + add A, 8; + add SINDEX, A, HCNT; + /* + * If there are another 8 bytes of data waiting in the + * fifo, then the carry bit will be set as a result + * of the above add command (unless A is non-negative, + * in which case the carry bit won't be set). + */ + jc dma_scb_hang_empty_fifo; + /* + * We've emptied the fifo now, but we wouldn't have got + * here if the memory transfer hadn't stopped part way + * through, so go back up to the beginning of the + * loop and start over. When it succeeds in getting + * all the data down, HDONE will be set and we'll + * jump to the code just below here. + */ + jmp dma_scb_hang_fifo; +dma_scb_hang_dma_done: + and DFCNTRL, ~HDMAEN; + test DFCNTRL, HDMAEN jnz .; + call dfdat_in_8; + add A, 8; + cmp A, 8 jne . - 2; + ret; + } else { + call dma_finish; + call dfdat_in_8; + call dfdat_in_8; + call dfdat_in_8; + } +dfdat_in_8: + mov DINDIR,DFDAT; +dfdat_in_7: + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT; + mov DINDIR,DFDAT 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; +} + +add_scb_to_free_list: + if ((p->flags & AHC_PAGESCBS) != 0) { + mov SCB_NEXT, FREE_SCBH; + mov FREE_SCBH, SCBPTR; + } + mvi SCB_TAG, SCB_LIST_NULL ret; + +if ((p->flags & AHC_PAGESCBS) != 0) { +get_free_or_disc_scb: + cmp FREE_SCBH, SCB_LIST_NULL jne dequeue_free_scb; + cmp DISCONNECTED_SCBH, SCB_LIST_NULL jne dequeue_disc_scb; +return_error: + mvi SINDEX, SCB_LIST_NULL ret; +dequeue_disc_scb: + mov SCBPTR, DISCONNECTED_SCBH; +dma_up_scb: + mvi DMAPARAMS, FIFORESET; + mov SCB_TAG call dma_scb; +unlink_disc_scb: + mov DISCONNECTED_SCBH, SCB_NEXT ret; +dequeue_free_scb: + mov SCBPTR, FREE_SCBH; + mov FREE_SCBH, SCB_NEXT ret; +} + +add_scb_to_disc_list: +/* + * Link this SCB into the DISCONNECTED list. This list holds the + * candidates for paging out an SCB if one is needed for a new command. + * Modifying the disconnected list is a critical(pause dissabled) section. + */ + mov SCB_NEXT, DISCONNECTED_SCBH; + mov DISCONNECTED_SCBH, SCBPTR ret; |