root/drivers/scsi/aic7xxx.seq

/* [previous][next][first][last][top][bottom][index][help] */
/*+M*************************************************************************
 * Adaptec 274x/284x/294x device driver for Linux and FreeBSD.
 *
 * Copyright (c) 1994 John Aycock
 *   The University of Calgary Department of Computer Science.
 *
 *Modifications/enhancements:
 *  Copyright (c) 1994, 1995, 1996 Justin Gibbs. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * FreeBSD, Twin, Wide, 2 command per target support, tagged queuing and other 
 * optimizations provided by Justin T. Gibbs (gibbs@FreeBSD.org)
 *-M*************************************************************************/

VERSION AIC7XXX_SEQ_VER "$Id: aic7xxx.seq,v 2.8 1996/02/10 06:23:39 deang Exp $"

#ifdef linux
#include "aic7xxx_reg.h"
#else
#include "../../dev/aic7xxx/aic7xxx_reg.h"
#endif

/*
 * We can't just use ACCUM in the sequencer code because it
 * must be treated specially by the assembler, and it currently
 * looks for the symbol 'A'.  This is the only register defined in
 * the assembler's symbol space.
 */
A = ACCUM

/* 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 offsets, 
 * SCB_LIST_NULL is 0xff which is out of range.  The kernel driver must
 * add an entry to this list every time a request sense occurs.  The sequencer
 * will automatically consume the entries.
 */

/*
 * Initialize any state valid during the idle loop here.  This code is
 * executed on startup and after every bus free.
 */
start:
        mvi     SCSISEQ,ENRSELI         /* Always allow reselection */
poll_for_work:
        /*
         * Are we a twin channel device?
         * For fairness, we check the other bus first,
         * since we just finished a transaction on the
         * current channel.
         */
        test    FLAGS,TWIN_BUS  jz start2
        xor     SBLKCTL,SELBUSB                 /* Toggle to the other bus */
        test    SSTAT0,SELDI    jnz reselect
        xor     SBLKCTL,SELBUSB                 /* Toggle to the original bus */
start2:
        test    SSTAT0,SELDI    jnz reselect
        cmp     WAITING_SCBH,SCB_LIST_NULL jne start_waiting
        test    QINCNT,0xff     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.  Set the SCB
 * pointer from the FIFO so we see the right bank of SCB 
 * registers.
 */
        mov     SCBPTR,QINFIFO

/*
 * 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.  It may be beneficial to make findscb a more general procedure
 * to see if the added cost of the search is negligible.  This code also 
 * assumes that the kernel driver will clear the active flags on board 
 * initialization, board reset, and a target SELTO.  Tagged commands
 * don't set the active bits since you can queue more than one command
 * at a time.  We do, however, look to see if there are any non-tagged
 * I/Os in progress, and requeue the command if there are.  Tagged and
 * non-tagged commands cannot be mixed to a single target.
 */

test_busy:
        mov     FUNCTION1,SCB_TCL
        mov     A,FUNCTION1
        test    SCB_TCL,0x88    jz test_a       /* Id < 8 && A channel */

        test    ACTIVE_B,A      jnz requeue
        test    SCB_CONTROL,TAG_ENB     jnz start_scb
        /* Mark the current target as busy */
        or      ACTIVE_B,A
        jmp     start_scb

/* Place the currently active SCB back on the queue for later processing */
requeue:
        mov     QINFIFO, SCBPTR
        jmp     poll_for_work

/*
 * Pull the first entry off of the waiting for selection list
 * We don't have to "test_busy" because only transactions that
 * have passed that test can be in the waiting_scb list.
 */
start_waiting:
        mov     SCBPTR,WAITING_SCBH
        jmp     start_scb2

test_a:
        test    ACTIVE_A,A jnz requeue
        test    SCB_CONTROL,TAG_ENB jnz start_scb
        /* Mark the current target as busy */
        or      ACTIVE_A,A

start_scb:
        mov     SCB_NEXT_WAITING,WAITING_SCBH
        mov     WAITING_SCBH, SCBPTR
start_scb2:
        and     SINDEX,0xf7,SBLKCTL     /* Clear the channel select bit */
        and     A,0x08,SCB_TCL          /* Get new channel bit */
        or      SINDEX,A
        mov     SBLKCTL,SINDEX          /* select channel */
        mov     SCB_TCL call initialize_scsiid

/*
 * Enable selection phase as an initiator, and do automatic ATN
 * after the selection.  We do this now so that we can overlap the
 * rest of our work to set up this target with the arbitration and
 * selection bus phases.
 */
start_selection:
        mvi     SCSISEQ,0x58            /* ENSELO|ENAUTOATNO|ENRSELI */

/*
 * 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.
 */
        test    SCB_CMDLEN,0xff jnz mk_identify /* 0 Length Command? */

/*
 * The kernel has sent us an SCB with no command attached.  This implies
 * that the kernel wants to send a message of some sort to this target,
 * so we interrupt the driver, allow it to fill the message buffer, and
 * then go back into the arbitration loop
 */
        mvi     INTSTAT,AWAITING_MSG
        jmp     wait_for_selection

mk_identify:
        and     A,DISCENB,SCB_CONTROL   /* mask off disconnect privilege */

        and     MSG0,0x7,SCB_TCL        /* lun */
        or      MSG0,A                  /* or in disconnect privilege */
        or      MSG0,MSG_IDENTIFY
        mvi     MSG_LEN, 1

        test    SCB_CONTROL,0xb0 jz  !message   /* WDTR, SDTR or TAG?? */
/*
 * Tag Message if Tag enabled in SCB control block.  Use SCBPTR as the tag
 * value
 */

mk_tag:
        mvi     DINDEX, MSG1
        test    SCB_CONTROL,TAG_ENB jz mk_tag_done
        and     A,0x23,SCB_CONTROL
        mov     DINDIR,A
        mov     DINDIR,SCBPTR

        add     MSG_LEN,COMP_MSG0,DINDEX        /* update message length */

mk_tag_done:

        test    SCB_CONTROL,0x90 jz !message    /* NEEDWDTR|NEEDSDTR */
        mov     DINDEX  call mk_dtr     /* build DTR message if needed */

!message:
wait_for_selection:
        test    SSTAT0,SELDO    jnz select 
        test    SSTAT0,SELDI    jz wait_for_selection

/*
 * 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 */
        mov     SELID           call initialize_scsiid
        and     FLAGS,0x03      /* clear target specific flags */
        or      FLAGS,RESELECTED
        jmp     select2

/*
 * After the selection, remove this SCB from the "waiting for selection"
 * 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:
        and     FLAGS,0x03              /* Clear target flags */
        mov     WAITING_SCBH,SCB_NEXT_WAITING
select2:
/*
 * Set CLRCHN here before the target has entered a data transfer mode -
 * with synchronous SCSI, if you do it later, you blow away some
 * data in the SCSI FIFO that the target has already sent to you.
 */
        or      SXFRCTL0,CLRCHN
/*
 * Initialize SCSIRATE with the appropriate value for this target.
 */
        call    ndx_dtr
        mov     SCSIRATE,SINDIR

        mvi     SCSISEQ,ENAUTOATNP              /*
                                                 * ATN on parity errors
                                                 * for "in" phases
                                                 */
        mvi     CLRSINT1,CLRBUSFREE
        mvi     CLRSINT0,0x60                   /* CLRSELDI|CLRSELDO */

/*
 * 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,BUSFREE  jnz p_busfree
        test    SSTAT1,REQINIT  jz ITloop

/*
 * If we've had a parity error, let the driver know before
 * we overwrite LASTPHASE.
 */
        test    SSTAT1, SCSIPERR jz parity_okay
        or      CLRSINT1, CLRSCSIPERR
        mvi     INTSTAT, PARITY_ERROR

parity_okay:
        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 */

p_dataout:
        mvi     DMAPARAMS,0x7d                  /*
                                                 * 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:
        mov     STCNT0,SCB_RESID_DCNT0
        mov     STCNT1,SCB_RESID_DCNT1
        mov     STCNT2,SCB_RESID_DCNT2
        jmp     data_phase_loop

p_datain:
        mvi     DMAPARAMS,0x79          /*
                                         * WIDEODD|SCSIEN|SDMAEN|HDMAEN|
                                         * !DIRECTION|FIFORESET
                                         */
data_phase_init:
        call    assert

        test    FLAGS, DPHASE   jnz data_phase_reinit
        call    sg_scb2ram
        or      FLAGS, DPHASE           /* We have seen a data phase */

data_phase_loop:
/* If we are the last SG block, don't set wideodd. */
        cmp     SG_COUNT,0x01 jne data_phase_wideodd
        and     DMAPARAMS, 0xbf         /* Turn off WIDEODD */
data_phase_wideodd:
        mov     DMAPARAMS  call dma

/* Exit if we had an underrun */
        test    SSTAT0,SDONE    jz data_phase_finish /* underrun STCNT != 0 */

/*
 * 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_NEXT0,SG_SIZEOF,SG_NEXT0
        adc     SG_NEXT1,A,SG_NEXT1

/*
 * 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 a little-endian host data storage.
 */
sg_load:
        clr     HCNT2
        clr     HCNT1
        mvi     HCNT0,SG_SIZEOF

        mov     HADDR0,SG_NEXT0
        mov     HADDR1,SG_NEXT1
        mov     HADDR2,SG_NEXT2
        mov     HADDR3,SG_NEXT3

        or      DFCNTRL,0xd                     /* HDMAEN|DIRECTION|FIFORESET */

/*
 * 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 preserving WIDEODD */
        and     DFCNTRL,WIDEODD
dma_finish2:
        test    DFCNTRL,HDMAENACK jnz dma_finish2

/*
 * Copy data from FIFO into SCB data pointer and data count.  This assumes
 * that the struct scatterlist has this structure (this and sizeof(struct
 * scatterlist) == 12 are asserted in aic7xxx.c):
 *
 *      struct scatterlist {
 *              char *address;          four bytes, little-endian order
 *              ...                     four bytes, ignored
 *              unsigned short length;  two bytes, little-endian order
 *      }
 *
 *
 * Not in FreeBSD.  the scatter list entry is only 8 bytes.
 * 
 * struct ahc_dma_seg {
 *       physaddr addr;                  four bytes, little-endian order
 *       long    len;                    four bytes, little endian order
 * };
 */

/*
 * For Linux, we must throw away four bytes since there is a 32bit gap
 * in the middle of a struct scatterlist
 */
#ifdef linux
        mov     HADDR0,DFDAT
        mov     HADDR1,DFDAT
        mov     HADDR2,DFDAT
        mov     HADDR3,DFDAT
        mov     NONE,DFDAT
        mov     NONE,DFDAT
        mov     NONE,DFDAT
        mov     NONE,DFDAT
        mov     HCNT0,DFDAT
        mov     HCNT1,DFDAT
        mov     HCNT2,DFDAT
#else
/*
 * For FreeBSD, just copy it wholesale
 */
        mov     HADDR0,DFDAT
        mov     HADDR1,DFDAT
        mov     HADDR2,DFDAT
        mov     HADDR3,DFDAT
        mov     HCNT0,DFDAT
        mov     HCNT1,DFDAT
        mov     HCNT2,DFDAT
#endif

/* Load STCNT as well.  It is a mirror of HCNT */
        mov     STCNT0,HCNT0
        mov     STCNT1,HCNT1
        mov     STCNT2,HCNT2
        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_DCNT0,STCNT0
        mov     SCB_RESID_DCNT1,STCNT1
        mov     SCB_RESID_DCNT2,STCNT2
        mov     SCB_RESID_SGCNT, SG_COUNT
        jmp     ITloop

/*
 * Command phase.  Set up the DMA registers and let 'er rip - the
 * two bytes after the SCB SCSI_cmd_length are zeroed by the driver,
 * so we can copy those three bytes directly into HCNT.
 */
p_command:
        call    assert

/*
 * Load HADDR and HCNT.  We can do this in one bcopy since they are neighbors
 */
        mov     HADDR0, SCB_CMDPTR0
        mov     HADDR1, SCB_CMDPTR1
        mov     HADDR2, SCB_CMDPTR2
        mov     HADDR3, SCB_CMDPTR3
        mov     HCNT0, SCB_CMDLEN
        clr     HCNT1
        clr     HCNT2

        mov     STCNT0, HCNT0
        mov     STCNT1, HCNT1
        mov     STCNT2, HCNT2

        mvi     0x3d            call dma        # SCSIEN|SDMAEN|HDMAEN|
                                                #   DIRECTION|FIFORESET
        jmp     ITloop

/*
 * Status phase.  Wait for the data byte to appear, then read it
 * and store it into the SCB.
 */
p_status:
        mvi     SCB_TARGET_STATUS       call inb_first
        jmp     mesgin_done

/*
 * Message out phase.  If there is no 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_NOP         call mk_mesg    /* build NOP message */

p_mesgout_start:
/*
 * Set up automatic PIO transfer from MSG0.  Bit 3 in
 * SXFRCTL0 (SPIOEN) is already on.
 */
        mvi     SINDEX,MSG0
        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.
 *
 * Keep an eye out for a phase change, in case the target issues
 * a MESSAGE REJECT.
 */
p_mesgout_loop:
        test    SSTAT1,PHASEMIS jnz p_mesgout_phasemis
        test    SSTAT0,SPIORDY  jz p_mesgout_loop
        cmp     DINDEX,1        jne p_mesgout_outb      /* last byte? */
        mvi     CLRSINT1,CLRATNO                        /* drop ATN */
p_mesgout_outb:
        dec     DINDEX
        or      CLRSINT0, CLRSPIORDY
        mov     SCSIDATL,SINDIR
        
p_mesgout4:
        test    DINDEX,0xff     jnz p_mesgout_loop

/*
 * 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_snoop:
        test    SSTAT1,BUSFREE  jnz p_mesgout_done
        test    SSTAT1,REQINIT  jz p_mesgout_snoop

        test    SSTAT1,PHASEMIS jnz p_mesgout_done

        or      SCSISIGO,ATNO                   /* turn on ATNO */

        jmp     ITloop

p_mesgout_phasemis:
        mvi     CLRSINT1,CLRATNO        /* Be sure turn ATNO off */
p_mesgout_done:
        clr     MSG_LEN                 /* no active msg */
        jmp     ITloop

/*
 * Message in phase.  Bytes are read using Automatic PIO mode.
 */
p_mesgin:
        mvi     A               call inb_first  /* read the 1st message byte */
        mov     REJBYTE,A                       /* save it for the driver */

        test    A,MSG_IDENTIFY          jnz mesgin_identify
        cmp     A,MSG_DISCONNECT        je mesgin_disconnect
        cmp     A,MSG_SDPTRS            je mesgin_sdptrs
        cmp     ALLZEROS,A              je mesgin_complete
        cmp     A,MSG_RDPTRS            je mesgin_rdptrs
        cmp     A,MSG_EXTENDED          je mesgin_extended
        cmp     A,MSG_REJECT            je mesgin_reject

rej_mesgin:
/*
 * We have no idea what this message in is, and there's no way
 * to pass it up to the kernel, so we issue a message reject and
 * hope for the best.  Since we're now using manual PIO mode to
 * read in the message, there should no longer be a race condition
 * present when we assert ATN.  In any case, rejection should be a
 * rare occurrence - signal the driver when it happens.
 */
        or      SCSISIGO,ATNO                   /* turn on ATNO */
        mvi     INTSTAT,SEND_REJECT             /* let driver know */

        mvi     MSG_REJECT      call mk_mesg

mesgin_done:
        call    inb_last                        /*ack & turn auto PIO back on*/
        jmp     ITloop


mesgin_complete:
/*
 * We got a "command complete" message, so put the SCB pointer
 * into QUEUEOUT, and trigger a completion interrupt.
 * Check status for non zero return and interrupt driver if needed
 * This allows the driver to interpret errors only when they occur
 * instead of always uploading the scb.  If the status is SCSI_CHECK,
 * the driver will download a new scb requesting sense to replace
 * the old one, modify the "waiting for selection" SCB list and set 
 * RETURN_1 to 0x80.  If RETURN_1 is set to 0x80 the sequencer immediately
 * jumps to main loop where it will run down the waiting SCB list.
 * If the kernel driver does not wish to request sense, it need
 * only clear RETURN_1, and the command is allowed to complete.  We don't 
 * bother to post to the QOUTFIFO in the error case 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    jz check_status
/*
 * If we have a residual count, interrupt and tell the host.  Other
 * alternatives are to pause the sequencer on all command completes (yuck),
 * dma the resid directly to the host (slick, we may have space to do it now)
 * or have the sequencer pause itself when it encounters a non-zero resid 
 * (unnecessary pause just to flag the command -yuck-, but takes one instruction
 * and since it shouldn't happen that often is good enough for our purposes).  
 */
resid:
        mvi     INTSTAT,RESIDUAL

check_status:
        test    SCB_TARGET_STATUS,0xff  jz status_ok    /* Good Status? */
        mvi     INTSTAT,BAD_STATUS                      /* let driver know */
        cmp     RETURN_1, SEND_SENSE    jne status_ok
        jmp     mesgin_done

status_ok:
/* First, mark this target as free. */
        test    SCB_CONTROL,TAG_ENB jnz test_immediate  /*
                                                         * Tagged commands
                                                         * don't busy the
                                                         * target.
                                                         */
        mov     FUNCTION1,SCB_TCL
        mov     A,FUNCTION1
        test    SCB_TCL,0x88 jz clear_a
        xor     ACTIVE_B,A
        jmp     test_immediate

clear_a:
        xor     ACTIVE_A,A

test_immediate:
        test    SCB_CMDLEN,0xff jnz complete  /* Immediate message complete */
/*
 * Pause the sequencer until the driver gets around to handling the command
 * complete.  This is so that any action that might require careful timing
 * with the completion of this command can occur.
 */
        mvi     INTSTAT,IMMEDDONE
        jmp     start
complete:
        mov     QOUTFIFO,SCBPTR
        mvi     INTSTAT,CMDCMPLT
        jmp     mesgin_done


/*
 * Is it an extended message?  We only support the synchronous and wide data
 * transfer request messages, which will probably be in response to
 * WDTR or SDTR message outs from us.  If it's not SDTR or WDTR, reject it -
 * apparently this can be done after any message in byte, according
 * to the SCSI-2 spec.
 */
mesgin_extended:
        mvi     ARG_1           call inb_next   /* extended message length */
        mvi     A               call inb_next   /* extended message code */

        cmp     A,MSG_SDTR      je p_mesginSDTR
        cmp     A,MSG_WDTR      je p_mesginWDTR
        jmp     rej_mesgin

p_mesginWDTR:
        cmp     ARG_1,2         jne rej_mesgin  /* extended mesg length=2 */
        mvi     ARG_1           call inb_next   /* Width of bus */
        mvi     INTSTAT,WDTR_MSG                /* let driver know */
        test    RETURN_1,0xff jz mesgin_done    /* Do we need to send WDTR? */
        cmp     RETURN_1,SEND_REJ je rej_mesgin /*
                                                 * Bus width was too large 
                                                 * Reject it.
                                                 */

/* We didn't initiate the wide negotiation, so we must respond to the request */
        and     RETURN_1,0x7f                   /* Clear the SEND_WDTR Flag */
        mvi     DINDEX,MSG0
        mvi     MSG0    call mk_wdtr            /* build WDTR message */
        or      SCSISIGO,ATNO                   /* turn on ATNO */
        jmp     mesgin_done

p_mesginSDTR:
        cmp     ARG_1,3         jne rej_mesgin  /* extended mesg length=3 */
        mvi     ARG_1           call inb_next   /* xfer period */
        mvi     A               call inb_next   /* REQ/ACK offset */
        mvi     INTSTAT,SDTR_MSG                /* call driver to convert */

        test    RETURN_1,0xff   jz mesgin_done  /* Do we need to mk_sdtr/rej */
        cmp     RETURN_1,SEND_REJ je rej_mesgin /*
                                                 * Requested SDTR too small
                                                 * Reject it.
                                                 */
        mvi     DINDEX, MSG0
        mvi     MSG0     call mk_sdtr
        or      SCSISIGO,ATNO                   /* 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
        jmp     mesgin_done

/*
 * Save data pointers message?  Copy working values into the SCB,
 * usually in preparation for a disconnect.
 */
mesgin_sdptrs:
        call    sg_ram2scb
        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     FLAGS,0xfb                      /*
                                                 * !DPHASE we'll reload them
                                                 * the next time through
                                                 */
        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,SELID          
        and     SAVED_TCL,0xf7
        and     A,SELBUSB,SBLKCTL       /* B Channel?? */
        or      SAVED_TCL,A
        call    inb_last                /* ACK */

/*
 * Here we "snoop" the bus looking for a SIMPLE QUEUE TAG message.
 * If we get one, we use the tag returned to switch to the proper
 * SCB.  Otherwise, we just use the findSCB method.
 */
snoop_tag_loop:
        test    SSTAT1,BUSFREE  jnz use_findSCB
        test    SSTAT1,REQINIT  jz snoop_tag_loop
        test    SSTAT1,PHASEMIS jnz use_findSCB
        mvi     A               call inb_first
        cmp     A,MSG_SIMPLE_TAG je get_tag
use_findSCB:
        mov     ALLZEROS        call findSCB      /* Have to search */
setup_SCB:
        and     SCB_CONTROL,0xfb          /* clear disconnect bit in SCB */
        or      FLAGS,IDENTIFY_SEEN       /* make note of IDENTIFY */
        jmp     ITloop
get_tag:
        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 incoming tag and there is
 * no carry.
 */
        mov     A,COMP_SCBCOUNT 
        add     SINDEX,A,ARG_1
        jc      abort_tag

/*
 * Ensure that the SCB the tag points to is for a SCB transaction
 * to the reconnecting target.
 */
        mov     SCBPTR,ARG_1
        mov     A,SAVED_TCL
        cmp     SCB_TCL,A               jne abort_tag
        test    SCB_CONTROL,TAG_ENB     jz  abort_tag
        call    inb_last                        /* Ack Successful tag */
        jmp     setup_SCB
abort_tag:
        or      SCSISIGO,ATNO                   /* turn on ATNO */
        mvi     INTSTAT,ABORT_TAG               /* let driver know */
        mvi     0xd             call mk_mesg    /* ABORT TAG message */
        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 ]
 */

/*
 * Bus free phase.  It might be useful to interrupt the device
 * driver if we aren't expecting this.  For now, make sure that
 * ATN isn't being asserted and look for a new command.
 */
p_busfree:
        mvi     CLRSINT1,CLRATNO

/*
 * if this is an immediate command, perform a pseudo command complete to
 * notify the driver.
 */
        test    SCB_CMDLEN,0xff jz status_ok
        jmp     start

#if 0
/*
 * Instead of a generic bcopy routine that requires an argument, we unroll
 * the cases that are actually used, and call them explicitly.  This
 * not only reduces the overhead of doing a bcopy, but ends up saving space 
 * in the program since you don't have to put the argument into the accumulator
 * before the call.  Both functions expect DINDEX to contain the destination 
 * address and SINDEX to contain the source address.
 */
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
#endif
        
/*
 * 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,0x50                     /* 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,0x10                     /* !PAUSEDIS|FASTMODE */
        mvi     INTSTAT,MSG_BUFFER_BUSY

mk_mesg1:
        mvi     MSG_LEN,1               /* length = 1 */
        mov     MSG0,SINDEX             /* 1-byte message */
        mvi     SEQCTL,0x10     ret     /* !PAUSEDIS|FASTMODE */

/*
 * 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:
        or      CLRSINT0, CLRSPIORDY
        mov     NONE,SCSIDATL                   /*dummy read from latch to ACK*/
inb_next_wait:
        test    SSTAT1,PHASEMIS jnz mesgin_phasemis
        test    SSTAT0,SPIORDY  jz inb_next_wait /* wait for next byte */
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
dma1:
        test    SSTAT0,DMADONE  jnz dma3
        test    SSTAT1,PHASEMIS jz dma1         /* ie. underrun */

/*
 * 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.
 */
dma3:
        test    SINDEX,DIRECTION        jnz dma5
dma4:
        test    DFSTATUS,FIFOEMP        jz dma4

/*
 * Now shut the DMA enables off and make sure that the DMA enables are 
 * actually off first lest we get an ILLSADDR.
 */
dma5:
        /* disable DMA, but maintain WIDEODD */
        and     DFCNTRL,WIDEODD
dma6:
        test    DFCNTRL,0x38    jnz dma6  /* SCSIENACK|SDMAENACK|HDMAENACK */

        ret

/*
 * Common SCSI initialization for selection and reselection.  Expects
 * the target SCSI ID to be in the upper four bits of SINDEX, and A's
 * contents are stomped on return.
 */
initialize_scsiid:
        and     SINDEX,0xf0             /* Get target ID */
        and     A,0x0f,SCSIID
        or      SINDEX,A
        mov     SCSIID,SINDEX ret

/*
 * Assert that if we've been reselected, then we've seen an IDENTIFY
 * message.
 */
assert:
        test    FLAGS,RESELECTED        jz return       /* reselected? */
        test    FLAGS,IDENTIFY_SEEN     jnz return      /* seen IDENTIFY? */

        mvi     INTSTAT,NO_IDENT        ret     /* no - cause a kernel panic */

/*
 * Locate the SCB matching the target ID/channel/lun in SAVED_TCL and switch 
 * the SCB to it.  Have the kernel print a warning message if it can't be 
 * found, and generate an ABORT message to the target.  SINDEX should be
 * cleared on call.
 */
findSCB:
        mov     A,SAVED_TCL
        mov     SCBPTR,SINDEX                   /* switch to new SCB */
        cmp     SCB_TCL,A       jne findSCB1 /* target ID/channel/lun match? */
        test    SCB_CONTROL,DISCONNECTED jz findSCB1 /*should be disconnected*/
        ret

findSCB1:
        inc     SINDEX
        mov     A,SCBCOUNT
        cmp     SINDEX,A        jne findSCB

        mvi     INTSTAT,NO_MATCH                /* not found - signal kernel */
        mvi     MSG_ABORT       call mk_mesg    /* ABORT message */

        or      SCSISIGO,ATNO   ret             /* assert ATNO */

/*
 * Make a working copy of the scatter-gather parameters from the SCB.
 */
sg_scb2ram:
        mov     HADDR0, SCB_DATAPTR0
        mov     HADDR1, SCB_DATAPTR1
        mov     HADDR2, SCB_DATAPTR2
        mov     HADDR3, SCB_DATAPTR3
        mov     HCNT0, SCB_DATACNT0
        mov     HCNT1, SCB_DATACNT1
        mov     HCNT2, SCB_DATACNT2

        mov     STCNT0, HCNT0
        mov     STCNT1, HCNT1
        mov     STCNT2, HCNT2

        mov     SG_COUNT,SCB_SGCOUNT

        mov     SG_NEXT0, SCB_SGPTR0
        mov     SG_NEXT1, SCB_SGPTR1
        mov     SG_NEXT2, SCB_SGPTR2
        mov     SG_NEXT3, SCB_SGPTR3 ret

/*
 * 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.
 */
sg_ram2scb:
        test    FLAGS, DPHASE   jz return
        mov     SCB_SGCOUNT,SG_COUNT

        mov     SCB_SGPTR0,SG_NEXT0
        mov     SCB_SGPTR1,SG_NEXT1
        mov     SCB_SGPTR2,SG_NEXT2
        mov     SCB_SGPTR3,SG_NEXT3
        
        mov     SCB_DATAPTR0,SHADDR0
        mov     SCB_DATAPTR1,SHADDR1
        mov     SCB_DATAPTR2,SHADDR2
        mov     SCB_DATAPTR3,SHADDR3

/*
 * Use the residual number since STCNT is corrupted by any message transfer
 */
        mov     SCB_DATACNT0,SCB_RESID_DCNT0
        mov     SCB_DATACNT1,SCB_RESID_DCNT1
        mov     SCB_DATACNT2,SCB_RESID_DCNT2 ret

/*
 * Add the array base TARG_SCRATCH to the target offset (the target address
 * is in SCSIID), and return the result in SINDEX.  The accumulator
 * contains the 3->8 decoding of the target ID on return.
 */
ndx_dtr:
        shr     A,SCSIID,4
        test    SBLKCTL,SELBUSB jz ndx_dtr_2
        or      A,0x08          /* Channel B entries add 8 */
ndx_dtr_2:
        add     SINDEX,TARG_SCRATCH,A ret

/*
 * If we need to negotiate transfer parameters, build the WDTR or SDTR message
 * starting at the address passed in SINDEX.  DINDEX is modified on return.
 * The SCSI-II spec requires that Wide negotiation occur first and you can
 * only negotiate one or the other at a time otherwise in the event of a message
 * reject, you wouldn't be able to tell which message was the culprit.
 */
mk_dtr:
        test    SCB_CONTROL,NEEDWDTR jnz  mk_wdtr_16bit
        or      FLAGS, MAXOFFSET        /* Force an offset of 15 or 8 if WIDE */

mk_sdtr:
        mvi     DINDIR,1                /* extended message */
        mvi     DINDIR,3                /* extended message length = 3 */
        mvi     DINDIR,1                /* SDTR code */
        call    sdtr_to_rate
        mov     DINDIR,RETURN_1         /* REQ/ACK transfer period */
        test    FLAGS, MAXOFFSET jnz mk_sdtr_max_offset
        and     DINDIR,0x0f,SINDIR      /* Sync Offset */

mk_sdtr_done:
        add     MSG_LEN,COMP_MSG0,DINDEX ret    /* update message length */

mk_sdtr_max_offset:
/*
 * We're initiating sync negotiation, so request the max offset we can (15 or 8)
 */
        xor     FLAGS, MAXOFFSET

        /* Talking to a WIDE device? */
        test    SCSIRATE, WIDEXFER      jnz wmax_offset 
        mvi     DINDIR, MAX_OFFSET_8BIT
        jmp     mk_sdtr_done

wmax_offset:
        mvi     DINDIR, MAX_OFFSET_16BIT
        jmp     mk_sdtr_done

mk_wdtr_16bit:
        mvi     ARG_1,BUS_16_BIT
mk_wdtr:
        mvi     DINDIR,1                /* extended message */
        mvi     DINDIR,2                /* extended message length = 2 */
        mvi     DINDIR,3                /* WDTR code */
        mov     DINDIR,ARG_1            /* bus width */

        add     MSG_LEN,COMP_MSG0,DINDEX ret    /* update message length */
        
sdtr_to_rate:
        call    ndx_dtr                 /* index scratch space for target */
        shr     A,SINDIR,0x4
        dec     SINDEX                  /* Preserve SINDEX */
        and     A,0x7
        clr     RETURN_1
sdtr_to_rate_loop:
        test    A,0x0f  jz sdtr_to_rate_done
        add     RETURN_1,0x19
        dec     A       
        jmp     sdtr_to_rate_loop
sdtr_to_rate_done:
        shr     RETURN_1,0x2
        add     RETURN_1,0x19
        test    SXFRCTL0,ULTRAEN jz return
        shr     RETURN_1,0x1
return:
        ret

/* [previous][next][first][last][top][bottom][index][help] */