/*+M*********************************************************************** *Adaptec 274x/284x/294x device driver for Linux and FreeBSD. * *Copyright (c) 1994 John Aycock * The University of Calgary Department of Computer Science. * All rights reserved. * *FreeBSD, Twin, Wide, 2 command per target support, tagged queuing, *SCB paging and other optimizations: *Copyright (c) 1994, 1995, 1996, 1997 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. *2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. *3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of Calgary * Department of Computer Science and its contributors. *4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * *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.46.2.21 1997/09/27 19:46:55 gibbs Exp $ * *-M************************************************************************/ #include #include /* * 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 psuedo-next pointer and to thread a list * of SCBs that are awaiting selection. Since 0-0xfe are valid SCB indexes, * SCB_LIST_NULL is 0xff which is out of range. An entry is also added to * this list everytime a request sense occurs or after completing a non-tagged * command for which a second SCB has been queued. The sequencer will * automatically consume the entries. */ /* * We assume that the kernel driver may reset us at any time, even in the * middle of a DMA, so clear DFCNTRL too. */ reset: clr SCSISIGO; /* De-assert BSY */ /* Always allow reselection */ mvi SCSISEQ, ENRSELI|ENAUTOATNP; call clear_target_state; poll_for_work: test SSTAT0,SELDO jnz select; test SSTAT0,SELDI jnz reselect; test SCSISEQ, ENSELO jnz poll_for_work; .if ( TWIN_CHANNEL ) /* * 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 jnz select; test SSTAT0,SELDI jnz reselect; test SCSISEQ, ENSELO jnz poll_for_work; xor SBLKCTL,SELBUSB; /* Toggle back */ .endif cmp WAITING_SCBH,SCB_LIST_NULL jne start_waiting; test_queue: /* Has the driver posted any work for us? */ test QINCNT,QCNTMASK jz poll_for_work; /* * 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 ( SCB_PAGING ) mov ALLZEROS call get_free_or_disc_scb; cmp SINDEX, SCB_LIST_NULL je poll_for_work; .endif dequeue_scb: mov CUR_SCBID,QINFIFO; .if !( SCB_PAGING ) /* In the non-paging case, the SCBID == hardware SCB index */ mov SCBPTR, CUR_SCBID; .endif dma_queued_scb: /* * DMA the SCB from host ram into the current SCB location. */ mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; mov CUR_SCBID call dma_scb; /* * See if there is not already an active SCB for this target. This code * locks out on a per target basis instead of target/lun. Although this * is not ideal for devices that have multiple luns active at the same * time, it is faster than looping through all SCB's looking for active * commands. We also don't have enough spare SCB space for us to store the * SCBID of the currently busy transaction for each target/lun making it * impossible to link up the SCBs. */ test_busy: test SCB_CONTROL, TAG_ENB|ABORT_SCB jnz start_scb; mvi SEQCTL, PAUSEDIS|FASTMODE; mov SAVED_SCBPTR, SCBPTR; mov SCB_TCL call index_untagged_scb; mov ARG_1, SINDIR; /* * ARG_1 should * now have the SCB ID of * any active, non-tagged, * command for this target. */ cmp ARG_1, SCB_LIST_NULL je make_busy; .if ( SCB_PAGING ) /* * Put this SCB back onto the free list. It * may be necessary to satisfy the search for * the active SCB. */ mov SCBPTR, SAVED_SCBPTR; call add_scb_to_free_list; /* Find the active SCB */ mov ALLZEROS call findSCB; /* * If we couldn't find it, tell the kernel. This should * never happen. */ cmp SINDEX, SCB_LIST_NULL jne paged_busy_link; mvi INTSTAT, NO_MATCH_BUSY; paged_busy_link: /* Link us in */ mov SCB_LINKED_NEXT, CUR_SCBID; /* Put it back on the disconnected list */ call add_scb_to_disc_list; mvi SEQCTL, FASTMODE; jmp poll_for_work; .else simple_busy_link: mov SCBPTR, ARG_1; mov SCB_LINKED_NEXT, CUR_SCBID; mvi SEQCTL, FASTMODE; jmp poll_for_work; .endif make_busy: mov DINDIR, CUR_SCBID; mov SCBPTR, SAVED_SCBPTR; mvi SEQCTL, FASTMODE; 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 * We don't have to "test_busy" because only transactions that * have passed that test can be in the WAITING_SCB list. */ mov SCBPTR, WAITING_SCBH; call start_selection; jmp poll_for_work; start_selection: .if ( TWIN_CHANNEL ) and SINDEX,~SELBUSB,SBLKCTL;/* Clear the channel select bit */ and A,SELBUSB,SCB_TCL; /* Get new channel bit */ or SINDEX,A; mov SBLKCTL,SINDEX; /* select channel */ .endif initialize_scsiid: and A, TID, SCB_TCL; /* Get target ID */ and SCSIID, OID; /* Clear old target */ or SCSIID, A; mvi SCSISEQ, ENSELO|ENAUTOATNO|ENRSELI|ENAUTOATNP ret; /* * 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. */ reselect: clr MSG_LEN; /* Don't have anything in the mesg buffer */ mvi CLRSINT0, CLRSELDI; /* XXX test for and handle ONE BIT condition */ and SAVED_TCL, SELID_MASK, SELID; or SEQ_FLAGS,RESELECTED; jmp select2; /* * 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: /* 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; /* * As soon as we get a successful selection, the target should go * into the message out phase since we have ATN asserted. Prepare * the message to send. * * Messages are stored in scratch RAM starting with a length byte * followed by the message itself. */ mk_identify: and MSG_OUT,0x7,SCB_TCL; /* lun */ and A,DISCENB,SCB_CONTROL; /* mask off disconnect privledge */ or MSG_OUT,A; /* or in disconnect privledge */ or MSG_OUT,MSG_IDENTIFYFLAG; mvi MSG_LEN, 1; /* * 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. */ mk_tag: test SCB_CONTROL,TAG_ENB jz mk_message; and MSG_OUT[1],TAG_ENB|SCB_TAG_TYPE,SCB_CONTROL; mov MSG_OUT[2],SCB_TAG; add MSG_LEN,2; /* update message length */ /* * Interrupt the driver, and allow it to tweak the message buffer * if it asks. */ mk_message: test SCB_CONTROL,MK_MESSAGE jz select2; mvi INTSTAT,AWAITING_MSG; select2: mvi CLRSINT1,CLRBUSFREE; or SIMODE1, ENBUSFREE; /* * We aren't expecting a * bus free, so interrupt * the kernel driver if it * happens. */ /* * Initialize Ultra mode setting and clear the SCSI channel. */ or SXFRCTL0, CLRSTCNT|SPIOEN|CLRCHN; .if ( ULTRA ) 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; .endif /* * Initialize SCSIRATE with the appropriate value for this target. * The SCSIRATE settings for each target are stored in an array * based at TARG_SCRATCH. */ ndx_dtr: shr A,4,SAVED_TCL; test SBLKCTL,SELBUSB jz ndx_dtr_2; or SAVED_TCL, SELBUSB; /* Add the channel bit while we're here */ or A,0x08; /* Channel B entries add 8 */ ndx_dtr_2: add SINDEX,TARG_SCRATCH,A; mov SCSIRATE,SINDIR; /* * Main loop for information transfer phases. If BSY is false, then * we have a bus free condition, expected or not. Otherwise, wait * for the target to assert REQ before checking MSG, C/D and I/O * for the bus phase. * */ ITloop: test SSTAT1,REQINIT jz ITloop; test SSTAT1, SCSIPERR jnz ITloop; and A,PHASE_MASK,SCSISIGI; mov LASTPHASE,A; mov SCSISIGO,A; cmp ALLZEROS,A je p_dataout; cmp A,P_DATAIN je p_datain; 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 */ test SSTAT1,REQINIT|BUSFREE jz .; test SSTAT1, BUSFREE jnz poll_for_work; mvi INTSTAT, BAD_PHASE; clear_target_state: clr DFCNTRL; clr SCSIRATE; /* * We don't know the target we will * connect to, so default to narrow * transfers to avoid parity problems. */ and SXFRCTL0, ~FAST20; mvi LASTPHASE, P_BUSFREE; /* clear target specific flags */ and SEQ_FLAGS,~(RESELECTED|IDENTIFY_SEEN|TAGGED_SCB|DPHASE) ret; p_dataout: mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET; jmp data_phase_init; /* * If we re-enter the data phase after going through another phase, the * STCNT may have been cleared, so restore it from the residual field. */ data_phase_reinit: mvi DINDEX, STCNT; mvi SCB_RESID_DCNT call bcopy_3; jmp data_phase_loop; p_datain: mvi DMAPARAMS, WIDEODD|SCSIEN|SDMAEN|HDMAEN|FIFORESET; data_phase_init: call assert; /* * Ensure entering a data * phase is okay - seen identify, etc. */ test SEQ_FLAGS, DPHASE jnz data_phase_reinit; /* * 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. */ mvi DINDEX, HADDR; mvi SCB_DATAPTR call bcopy_7; call set_stcnt_from_hcnt; mov SG_COUNT,SCB_SGCOUNT; mvi DINDEX, SG_NEXT; mvi SCB_SGPTR call bcopy_4; 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; mvi HCNT[0], 0xff; mvi HCNT[1], 0xff; mvi HCNT[2], 0xff; call set_stcnt_from_hcnt; data_phase_inbounds: /* If we are the last SG block, ensure wideodd is off. */ cmp SG_COUNT,0x01 jne data_phase_wideodd; and DMAPARAMS, ~WIDEODD; data_phase_wideodd: mov DMAPARAMS call dma; /* 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 if needed */ sg_advance: dec SG_COUNT; /* one less segment to go */ test SG_COUNT, 0xff jz data_phase_finish; /* Are we done? */ clr A; /* add sizeof(struct scatter) */ add SG_NEXT[0],SG_SIZEOF; adc SG_NEXT[1],A; /* * Load a struct scatter and set up the data address and length. * If the working value of the SG count is nonzero, then * we need to load a new set of values. * * This, like all DMA's, assumes little-endian host data storage. */ sg_load: 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 HADDR call dfdat_in_7; /* Load STCNT as well. It is a mirror of HCNT */ call set_stcnt_from_hcnt; test SSTAT1,PHASEMIS jz data_phase_loop; data_phase_finish: /* * After a DMA finishes, save the SG and STCNT residuals back into the SCB * We use STCNT instead of HCNT, since it's a reflection of how many bytes * were transferred on the SCSI (as opposed to the host) bus. */ 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; /* We have seen a data phase */ or SEQ_FLAGS, DPHASE; jmp ITloop; data_phase_overrun: /* * Turn off BITBUCKET mode and notify the host */ and SXFRCTL1, ~BITBUCKET; mvi INTSTAT,DATA_OVERRUN; jmp ITloop; /* * Command phase. Set up the DMA registers and let 'er rip. */ p_command: call assert; /* * Load HADDR and HCNT. */ mvi DINDEX, HADDR; mvi SCB_CMDPTR call bcopy_5; clr HCNT[1]; clr HCNT[2]; call set_stcnt_from_hcnt; mvi (SCSIEN|SDMAEN|HDMAEN|DIRECTION|FIFORESET) call dma; 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 there is not an active message, but the target * took us into this phase anyway, build a no-op message and send it. */ p_mesgout: test MSG_LEN, 0xff jnz p_mesgout_start; mvi MSG_NOOP call mk_mesg; /* build NOP message */ p_mesgout_start: /* * Set up automatic PIO transfer from MSG_OUT. Bit 3 in * SXFRCTL0 (SPIOEN) is already on. */ mvi SINDEX,MSG_OUT; mov DINDEX,MSG_LEN; /* * When target asks for a byte, drop ATN if it's the last one in * the message. Otherwise, keep going until the message is exhausted. * ATN must be dropped *at least* 90ns before we ack the last byte, so * the code is aranged to execute two instructions before the byte is * transferred to give a good margin of safety * * Keep an eye out for a phase change, in case the target issues * a MESSAGE REJECT. */ p_mesgout_loop: test SSTAT1, REQINIT jz p_mesgout_loop; test SSTAT1, SCSIPERR jnz p_mesgout_loop; and LASTPHASE, PHASE_MASK, SCSISIGI; cmp LASTPHASE, P_MESGOUT jne p_mesgout_done; p_mesgout_testretry: test DINDEX,0xff jnz p_mesgout_dropatn; or SCSISIGO,ATNO,LASTPHASE;/* turn on ATN for the retry */ jmp p_mesgout_start; /* * 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. */ p_mesgout_dropatn: cmp DINDEX,1 jne p_mesgout_outb; /* last byte? */ mvi CLRSINT1,CLRATNO; /* drop ATN */ p_mesgout_outb: dec DINDEX; mov SCSIDATL,SINDIR; jmp p_mesgout_loop; p_mesgout_done: mvi CLRSINT1,CLRATNO; /* Be sure to turn ATNO off */ clr MSG_LEN; /* no active msg */ 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; 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 NO_ERROR (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 status_ok; /* Good Status? */ upload_scb: mvi DMAPARAMS, FIFORESET; mov SCB_TAG call dma_scb; check_status: test SCB_TARGET_STATUS,0xff jz status_ok; /* Just a residual? */ mvi INTSTAT,BAD_STATUS; /* let driver know */ cmp RETURN_1, SEND_SENSE jne status_ok; /* This SCB becomes the next to execute as it will retrieve sense */ mov SCB_LINKED_NEXT, SCB_TAG; jmp dma_next_scb; status_ok: /* First, mark this target as free. */ test SCB_CONTROL,TAG_ENB jnz complete; /* * Tagged commands * don't busy the * target. */ mov SAVED_SCBPTR, SCBPTR; mov SAVED_LINKPTR, SCB_LINKED_NEXT; mov SCB_TCL call index_untagged_scb; mov DINDIR, SAVED_LINKPTR; mov SCBPTR, SAVED_SCBPTR; complete: /* Post the SCB and issue an interrupt */ .if ( SCB_PAGING ) /* * Spin loop until there is space * in the QOUTFIFO. */ cmp CMDOUTCNT, FIFODEPTH je .; inc CMDOUTCNT; .endif mov QOUTFIFO,SCB_TAG; mvi INTSTAT,CMDCMPLT; test SCB_CONTROL, ABORT_SCB jz dma_next_scb; mvi INTSTAT, ABORT_CMDCMPLT; dma_next_scb: cmp SCB_LINKED_NEXT, SCB_LIST_NULL je add_to_free_list; .if !( SCB_PAGING ) /* Only DMA on top of ourselves if we are the SCB to download */ mov A, SCB_LINKED_NEXT; cmp SCB_TAG, A je dma_next_scb2; call add_scb_to_free_list; mov SCBPTR, A; jmp add_to_waiting_list; .endif dma_next_scb2: mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; mov SCB_LINKED_NEXT 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; 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 MSGIN_EXT_LEN call inb_next; mov A, MSGIN_EXT_LEN; mesgin_extended_loop: mov DINDEX call inb_next; dec A; cmp DINDEX, MSGIN_EXT_BYTES+3 jne mesgin_extended_loop_test; dec DINDEX; /* dump by repeatedly filling the last byte */ mesgin_extended_loop_test: test A, 0xFF jnz mesgin_extended_loop; mesgin_extended_intr: mvi INTSTAT,EXTENDED_MSG; /* let driver know */ cmp RETURN_1,SEND_REJ je rej_mesgin; cmp RETURN_1,SEND_MSG jne mesgin_done; /* The kernel has setup a message to be sent */ or SCSISIGO,ATNO,LASTPHASE; /* turn on ATNO */ jmp mesgin_done; /* * 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; .if ( SCB_PAGING ) call add_scb_to_disc_list; .endif 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; mov SCB_SGCOUNT,SG_COUNT; /* The SCB SGPTR becomes the next one we'll download */ mvi DINDEX, SCB_SGPTR; mvi SG_NEXT call bcopy_4; /* The SCB DATAPTR0 becomes the current SHADDR */ mvi DINDEX, SCB_DATAPTR; mvi SHADDR call bcopy_4; /* * Use the residual number since STCNT is corrupted by any message transfer. */ 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: test A,0x78 jnz rej_mesgin; /*!DiscPriv|!LUNTAR|!Reserved*/ and A,0x07; /* lun in lower three bits */ or SAVED_TCL,A; /* SAVED_TCL should be complete now */ mov SAVED_TCL call index_untagged_scb; mov ARG_1, SINDIR; .if ( SCB_PAGING ) cmp ARG_1,SCB_LIST_NULL jne use_findSCB; .else cmp ARG_1,SCB_LIST_NULL je snoop_tag; /* Directly index the SCB */ mov SCBPTR,ARG_1; test SCB_CONTROL,DISCONNECTED jz not_found; jmp setup_SCB; .endif /* * 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 findSCB 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: test SSTAT1,REQINIT jz snoop_tag_loop; test SSTAT1, SCSIPERR jnz snoop_tag_loop; and LASTPHASE, PHASE_MASK, SCSISIGI; cmp LASTPHASE, P_MESGIN jne not_found; cmp SCSIBUSL,MSG_SIMPLE_Q_TAG jne not_found; get_tag: or SEQ_FLAGS, TAGGED_SCB; mvi ARG_1 call inb_next; /* tag value */ /* * See if the tag is in range. The tag is < SCBCOUNT if we add * the complement of SCBCOUNT to the incomming tag and there is * no carry. */ add SINDEX,COMP_SCBCOUNT,ARG_1; jc not_found; .if ! ( SCB_PAGING ) index_by_tag: mov SCBPTR,ARG_1; mov A, SAVED_TCL; cmp SCB_TCL,A jne not_found; test SCB_CONTROL,TAG_ENB jz not_found; test SCB_CONTROL,DISCONNECTED jz not_found; .else /* * Ensure that the SCB the tag points to is for an SCB transaction * to the reconnecting target. */ use_findSCB: mov ALLZEROS call findSCB; /* Have to search */ cmp SINDEX, SCB_LIST_NULL je not_found; .endif setup_SCB: and SCB_CONTROL,~DISCONNECTED; or SEQ_FLAGS,IDENTIFY_SEEN; /* make note of IDENTIFY */ jmp mesgin_done; 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; /* * [ 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: mvi SEQCTL, PAUSEDIS|FASTMODE; test MSG_LEN,0xff jz mk_mesg1; /* Should always succeed */ /* * Hmmm. For some reason the mesg buffer is in use. * Tell the driver. It should look at SINDEX to find * out what we wanted to use the buffer for and resolve * the conflict. */ mvi SEQCTL,FASTMODE; mvi INTSTAT,MSG_BUFFER_BUSY; mk_mesg1: or SCSISIGO,ATNO,LASTPHASE;/* turn on ATNO */ mvi MSG_LEN,1; /* length = 1 */ mov MSG_OUT,SINDEX; /* 1-byte message */ mvi SEQCTL,FASTMODE 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 inb_next_wait; 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. */ 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: and DFCNTRL, ~(SCSIEN|SDMAEN|HDMAEN); dma_halt: /* * Some revisions of the aic7880 have a problem where, if the * data fifo is full, but the PCI input latch is not empty, * HDMAEN cannot be cleared. The fix used here is to attempt * to drain the data fifo until there is space for the input * latch to drain and HDMAEN de-asserts. */ mov NONE, DFDAT; test DFCNTRL, HDMAEN jnz dma_halt; return: ret; /* * Assert that if we've been reselected, then we've seen an IDENTIFY * message. */ assert: test SEQ_FLAGS,RESELECTED jz return; /* reselected? */ test SEQ_FLAGS,IDENTIFY_SEEN jnz return; /* seen IDENTIFY? */ mvi INTSTAT,NO_IDENT ret; /* no - tell the kernel */ .if ( SCB_PAGING ) /* * Locate a disconnected SCB either by SAVED_TCL (ARG_1 is SCB_LIST_NULL) * or by the SCBIDn ARG_1. The search begins at the SCB index passed in * via SINDEX. If the SCB cannot be found, SINDEX will be SCB_LIST_NULL, * otherwise, SCBPTR is set to the proper SCB. */ findSCB: mov SCBPTR,SINDEX; /* switch to next SCB */ mov A, ARG_1; /* Tag passed in ARG_1 */ cmp SCB_TAG,A jne findSCB_loop; test SCB_CONTROL,DISCONNECTED jnz foundSCB;/*should be disconnected*/ findSCB_loop: inc SINDEX; cmp SINDEX,SCBCOUNT jne findSCB; /* * We didn't find it. If we're paging, pull an SCB and DMA down the * one we want. If we aren't paging or the SCB we dma down has the * abort flag set, return not found. */ mov ALLZEROS call get_free_or_disc_scb; mvi DMAPARAMS, HDMAEN|DIRECTION|FIFORESET; mov ARG_1 call dma_scb; test SCB_RESID_SGCNT, 0xff jz . + 2; or SCB_CONTROL, MUST_DMAUP_SCB; test SCB_CONTROL, ABORT_SCB jz return; find_error: mvi SINDEX, SCB_LIST_NULL ret; foundSCB: test SCB_CONTROL, ABORT_SCB jnz find_error; rem_scb_from_disc_list: /* Remove this SCB from the disconnection list */ cmp SCB_NEXT,SCB_LIST_NULL je unlink_prev; mov SAVED_LINKPTR, SCB_PREV; mov SCBPTR, SCB_NEXT; mov SCB_PREV, SAVED_LINKPTR; mov SCBPTR, SINDEX; unlink_prev: cmp SCB_PREV,SCB_LIST_NULL je rHead;/* At the head of the list */ mov SAVED_LINKPTR, SCB_NEXT; mov SCBPTR, SCB_PREV; mov SCB_NEXT, SAVED_LINKPTR; mov SCBPTR, SINDEX ret; rHead: mov DISCONNECTED_SCBH,SCB_NEXT ret; .else ret; .endif 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; dma_scb: /* * SCB index is in SINDEX. Determine the physical address in * the host where this SCB is located and load HADDR with it. */ shr DINDEX, 3, SINDEX; shl A, 5, SINDEX; add HADDR[0], A, HSCB_ADDR[0]; mov A, DINDEX; adc HADDR[1], A, HSCB_ADDR[1]; clr A; adc HADDR[2], A, HSCB_ADDR[2]; adc HADDR[3], A, HSCB_ADDR[3]; /* Setup Count */ mvi HCNT[0], 28; 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, 28, 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; cmp SINDEX, A jne copy_scb_tofifo_loop; or DFCNTRL, HDMAEN|FIFOFLUSH; dma_scb_fromhost: call dma_finish; /* If we were putting the SCB, we are done */ test DMAPARAMS, DIRECTION jz return; mvi SCB_CONTROL call dfdat_in_7; call dfdat_in_7_continued; call dfdat_in_7_continued; jmp dfdat_in_7_continued; dfdat_in_7: mov DINDEX,SINDEX; dfdat_in_7_continued: mov DINDIR,DFDAT; mov DINDIR,DFDAT; mov DINDIR,DFDAT; mov DINDIR,DFDAT; mov DINDIR,DFDAT; 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. */ dma_finish: test DFSTATUS,HDONE jz dma_finish; /* Turn off DMA */ and DFCNTRL, ~HDMAEN; test DFCNTRL, HDMAEN jnz .; ret; index_untagged_scb: mov DINDEX, SINDEX; shr DINDEX, 4; and DINDEX, 0x03; /* Bottom two bits of tid */ add DINDEX, SCB_BUSYTARGETS; shr A, 6, SINDEX; /* Target ID divided by 4 */ test SINDEX, SELBUSB jz index_untagged_scb2; add A, 2; /* Add 2 positions */ index_untagged_scb2: mov SCBPTR, A; /* * Select the SCB with this * target's information. */ mov SINDEX, DINDEX ret; add_scb_to_free_list: mov SCB_NEXT, FREE_SCBH; mvi SCB_TAG, SCB_LIST_NULL; mov FREE_SCBH, SCBPTR ret; .if ( SCB_PAGING ) 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; /* * If we have a residual, then we are in the middle of some I/O * and we have to send this SCB back up to the kernel so that the * saved data pointers and residual information isn't lost. */ test SCB_CONTROL, MUST_DMAUP_SCB jz . + 3; and SCB_CONTROL, ~MUST_DMAUP_SCB; jmp dma_up_scb; test SCB_RESID_SGCNT,0xff jnz dma_up_scb; cmp SCB_LINKED_NEXT, SCB_LIST_NULL je unlink_disc_scb; dma_up_scb: mvi DMAPARAMS, FIFORESET; mov SCB_TAG call dma_scb; unlink_disc_scb: /* jmp instead of call since we want to return anyway */ mov SCBPTR jmp rem_scb_from_disc_list; 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. */ mvi SCB_PREV, SCB_LIST_NULL; mov SCB_NEXT, DISCONNECTED_SCBH; mov DISCONNECTED_SCBH, SCBPTR; cmp SCB_NEXT,SCB_LIST_NULL je return; mov SCBPTR,SCB_NEXT; mov SCB_PREV,DISCONNECTED_SCBH; mov SCBPTR,DISCONNECTED_SCBH ret; .endif