| /* |
| * 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 every time 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 empty. 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 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 response 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 privilege */ |
| or SINDEX,A; /* or in disconnect privilege */ |
| 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 interest 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; |