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 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.0 1995/08/02 05:28:42 deang Exp $"

SCBMASK         = 0x1f

SCSISEQ         = 0x00
ENRSELI         = 0x10
SXFRCTL0        = 0x01
SXFRCTL1        = 0x02
SCSISIGI        = 0x03
SCSISIGO        = 0x03
SCSIRATE        = 0x04
SCSIID          = 0x05
SCSIDATL        = 0x06
STCNT           = 0x08
STCNT+0         = 0x08
STCNT+1         = 0x09
STCNT+2         = 0x0a
CLRSINT0        = 0x0b
SSTAT0          = 0x0b
SELDO           = 0x40
SELDI           = 0x20
CLRSINT1        = 0x0c
SSTAT1          = 0x0c
SIMODE1         = 0x11
SCSIBUSL        = 0x12
SHADDR          = 0x14
SELID           = 0x19
SBLKCTL         = 0x1f
SEQCTL          = 0x60
A               = 0x64                          # == ACCUM
SINDEX          = 0x65
DINDEX          = 0x66
ALLZEROS        = 0x6a
NONE            = 0x6a
SINDIR          = 0x6c
DINDIR          = 0x6d
FUNCTION1       = 0x6e
HADDR           = 0x88
HADDR+1         = 0x89
HADDR+2         = 0x8a
HADDR+3         = 0x8b
HCNT            = 0x8c
HCNT+0          = 0x8c
HCNT+1          = 0x8d
HCNT+2          = 0x8e
SCBPTR          = 0x90
INTSTAT         = 0x91
DFCNTRL         = 0x93
DFSTATUS        = 0x94
DFDAT           = 0x99
QINFIFO         = 0x9b
QINCNT          = 0x9c
QOUTFIFO        = 0x9d

SCSICONF_A      = 0x5a
SCSICONF_B      = 0x5b

#  The two reserved bytes at SCBARRAY+1[23] are expected to be set to
#  zero, and the reserved bit in SCBARRAY+0 is used as an internal flag
#  to indicate whether or not to reload scatter-gather parameters after
#  a disconnect.  We also use bits 6 & 7 to indicate whether or not to
#  initiate SDTR or WDTR repectively when starting this command.
#
SCBARRAY+0      = 0xa0

DISCONNECTED    = 0x04
NEEDDMA         = 0x08
SG_LOAD         = 0x10
TAG_ENB         = 0x20
NEEDSDTR        = 0x40
NEEDWDTR        = 0x80

SCBARRAY+1      = 0xa1
SCBARRAY+2      = 0xa2
SCBARRAY+3      = 0xa3
SCBARRAY+4      = 0xa4
SCBARRAY+5      = 0xa5
SCBARRAY+6      = 0xa6
SCBARRAY+7      = 0xa7
SCBARRAY+8      = 0xa8
SCBARRAY+9      = 0xa9
SCBARRAY+10     = 0xaa
SCBARRAY+11     = 0xab
SCBARRAY+12     = 0xac
SCBARRAY+13     = 0xad
SCBARRAY+14     = 0xae
SCBARRAY+15     = 0xaf
SCBARRAY+16     = 0xb0
SCBARRAY+17     = 0xb1
SCBARRAY+18     = 0xb2
SCBARRAY+19     = 0xb3
SCBARRAY+20     = 0xb4
SCBARRAY+21     = 0xb5
SCBARRAY+22     = 0xb6
SCBARRAY+23     = 0xb7
SCBARRAY+24     = 0xb8
SCBARRAY+25     = 0xb9
SCBARRAY+26     = 0xba
SCBARRAY+27     = 0xbb
SCBARRAY+28     = 0xbc
SCBARRAY+29     = 0xbd
SCBARRAY+30     = 0xbe

BAD_PHASE       = 0x01                          # unknown scsi bus phase
CMDCMPLT        = 0x02                          # Command Complete
SEND_REJECT     = 0x11                          # sending a message reject
NO_IDENT        = 0x21                          # no IDENTIFY after reconnect
NO_MATCH        = 0x31                          # no cmd match for reconnect
MSG_SDTR        = 0x41                          # SDTR message recieved
MSG_WDTR        = 0x51                          # WDTR message recieved
MSG_REJECT      = 0x61                          # Reject message recieved
BAD_STATUS      = 0x71                          # Bad status from target
RESIDUAL        = 0x81                          # Residual byte count != 0
ABORT_TAG       = 0x91                          # Sent an ABORT_TAG message
AWAITING_MSG    = 0xa1                          # Kernel requested to specify
                                                # a message to this target
                                                # (command was null), so tell
                                                # it that it can fill the
                                                # message buffer.


#  The host adapter card (at least the BIOS) uses 20-2f for SCSI
#  device information, 32-33 and 5a-5f as well. As it turns out, the
#  BIOS trashes 20-2f, writing the synchronous negotiation results
#  on top of the BIOS values, so we re-use those for our per-target
#  scratchspace (actually a value that can be copied directly into
#  SCSIRATE).  The kernel driver will enable synchronous negotiation
#  for all targets that have a value other than 0 in the lower four
#  bits of the target scratch space.  This should work irregardless of
#  whether the bios has been installed. NEEDWDTR and NEEDSDTR are the top
#  two bits of the SCB control byte.  The kernel driver will set these
#  when a WDTR or SDTR message should be sent to the target the SCB's 
#  command references.
#
#  REJBYTE contains the first byte of a MESSAGE IN message, so the driver 
#  can report an intelligible error if a message is rejected.
#
#  FLAGS's high bit is true if we are currently handling a reselect;
#  its next-highest bit is true ONLY IF we've seen an IDENTIFY message
#  from the reselecting target.  If we haven't had IDENTIFY, then we have
#  no idea what the lun is, and we can't select the right SCB register
#  bank, so force a kernel panic if the target attempts a data in/out or
#  command phase instead of corrupting something.  FLAGS also contains
#  configuration bits so that we can optimize for TWIN and WIDE controllers
#  as well as the MAX_OFFSET bit which we set when we want to negotiate for
#  maximum sync offset irregardless of what the per target scratch space says.
#
#  Note that SG_NEXT occupies four bytes.
#
SYNCNEG         = 0x20

REJBYTE         = 0x31
DISC_DSB_A      = 0x32
DISC_DSB_B      = 0x33

MSG_LEN         = 0x34
MSG_START+0     = 0x35
MSG_START+1     = 0x36
MSG_START+2     = 0x37
MSG_START+3     = 0x38
MSG_START+4     = 0x39
MSG_START+5     = 0x3a
-MSG_START+0    = 0xcb                          # 2's complement of MSG_START+0

ARG_1           = 0x4a                          # sdtr conversion args & return
BUS_16_BIT      = 0x01
RETURN_1        = 0x4a

SIGSTATE        = 0x4b                          # value written to SCSISIGO

# Linux users should use 0xc (12) for SG_SIZEOF
#SG_SIZEOF      = 0x8                           # sizeof(struct ahc_dma)
SG_SIZEOF       = 0xc                           # sizeof(struct scatterlist)
# if AIC7XXX_USE_SG
SCB_SIZEOF      = 0x13                          # sizeof SCB to DMA (19 bytes)
# else
#SCB_SIZEOF     = 0x1a                          # sizeof SCB without SG
# endif

SG_NOLOAD       = 0x4c                          # load SG pointer/length?
SG_COUNT        = 0x4d                          # working value of SG count
SG_NEXT         = 0x4e                          # working value of SG pointer
SG_NEXT+0       = 0x4e
SG_NEXT+1       = 0x4f
SG_NEXT+2       = 0x50
SG_NEXT+3       = 0x51

SCBCOUNT        = 0x52                          # the actual number of SCBs
FLAGS           = 0x53                          # Device configuration flags
TWIN_BUS        = 0x01
WIDE_BUS        = 0x02
MAX_OFFSET      = 0x08
ACTIVE_MSG      = 0x20
IDENTIFY_SEEN   = 0x40
RESELECTED      = 0x80

MAX_OFFSET_8BIT = 0x0f
MAX_OFFSET_WIDE = 0x08

ACTIVE_A        = 0x54
ACTIVE_B        = 0x55
SAVED_TCL       = 0x56                          # Temporary storage for the 
                                                # target/channel/lun of a
                                                # reconnecting target

# After starting the selection hardware, we return to the "poll_for_work"
# loop so that we can 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 31 of the SCB as a psuedo-next pointer and to thread a list
# of SCBs that are awaiting selection.  Since 0 is a valid SCB offset, 
# SCB_LIST_NULL is 0x10 which is out of range.  The kernel driver must
# add an entry to this list everytime a request sense occurs.  The sequencer
# will automatically consume the entries.

WAITING_SCBH    = 0x57                          # head of list of SCBs awaiting
                                                # selection
WAITING_SCBT    = 0x58                          # tail of list of SCBs awaiting
                                                # selection
SCB_LIST_NULL   = 0x10


#  Poll QINCNT for work - the lower bits contain
#  the number of entries in the Queue In FIFO.
#
start:
        test    WAITING_SCBH,SCB_LIST_NULL jz start_waiting
poll_for_work:
        test    FLAGS,TWIN_BUS  jz start2       # Are we a twin channel device?
# For fairness, we check the other bus first, since we just finished a 
# transaction on the current channel.
        xor     SBLKCTL,0x08                    # Toggle to the other bus
        test    SSTAT0,SELDI    jnz reselect
        test    SSTAT0,SELDO    jnz select
        xor     SBLKCTL,0x08                    # Toggle to the original bus
start2:
        test    SSTAT0,SELDI    jnz reselect
        test    SSTAT0,SELDO    jnz select
        test    WAITING_SCBH,SCB_LIST_NULL jz start_waiting
        test    QINCNT,SCBMASK  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, then set SCSI options and set the initiator and
# target SCSI IDs.
#
        mov     SCBPTR,QINFIFO

# If the control byte of this SCB has the NEEDDMA flag set, we have
# yet to DMA it from host memory

test    SCBARRAY+0,NEEDDMA      jz test_busy    
        clr     HCNT+2
        clr     HCNT+1
        mvi     HCNT+0,SCB_SIZEOF

        mvi     DINDEX,HADDR      
        mvi     SCBARRAY+26     call bcopy_4
        
        mvi     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.
#
        call    dma_finish

# Copy the SCB from the FIFO to  the SCBARRAY

        mvi     DINDEX, SCBARRAY+0
        call    bcopy_3_dfdat 
        call    bcopy_4_dfdat
        call    bcopy_4_dfdat
        call    bcopy_4_dfdat   
        call    bcopy_4_dfdat
# ifndef AIC7XXX_USE_SG
#       call    bcopy_3_dfdat
#       call    bcopy_4_dfdat
# endif

# 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 benificial 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's SELTO.

test_busy:
        test    SCBARRAY+0,0x20 jnz start_scb
        and     FUNCTION1,0x70,SCBARRAY+1
        mov     A,FUNCTION1
        test    SCBARRAY+1,0x88 jz test_a       # Id < 8 && A channel

        test    ACTIVE_B,A      jnz requeue
        or      ACTIVE_B,A      # Mark the current target as busy
        jmp     start_scb

# Place the currently active 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
start_waiting:
        mov     SCBPTR,WAITING_SCBH
        jmp     start_scb

test_a:
        test    ACTIVE_A,A      jnz requeue
        or      ACTIVE_A,A      # Mark the current target as busy

start_scb:
        and     SINDEX,0xf7,SBLKCTL  #Clear the channel select bit
        and     A,0x08,SCBARRAY+1    #Get new channel bit
        or      SINDEX,A             
        mov     SBLKCTL,SINDEX  # select channel
        mov     SCBARRAY+1      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:
        or      SCSISEQ,0x48                    # ENSELO|ENAUTOATNO
        mov     WAITING_SCBH, SCBPTR
        clr     SG_NOLOAD
        and     FLAGS,0x3f      # !RESELECTING

#  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, locking out the device driver.  If the device
#  driver hasn't beaten us with an ABORT or RESET message, then tack
#  on an SDTR negotiation if required.
#
#  Messages are stored in scratch RAM starting with a flag byte (high bit
#  set means active message), one length byte, and then the message itself.
#

        test    SCBARRAY+11,0xff jnz 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     poll_for_work

identify:
        mov     SCBARRAY+1      call disconnect # disconnect ok?

        and     SINDEX,0x7,SCBARRAY+1           # lun
        or      SINDEX,A                        # return value from disconnect
        or      SINDEX,0x80     call mk_mesg    # IDENTIFY message

        mov     A,SINDEX
        test    SCBARRAY+0,0xe0 jz  !message    # WDTR, SDTR or TAG??
        cmp     MSG_START+0,A   jne !message    # did driver beat us?

# Tag Message if Tag enabled in SCB control block.  Use SCBPTR as the tag
# value

mk_tag:
        mvi     DINDEX, MSG_START+1
        test    SCBARRAY+0,TAG_ENB jz mk_tag_done
        and     A,0x23,SCBARRAY+0
        mov     DINDIR,A
        mov     DINDIR,SCBPTR

        add     MSG_LEN,-MSG_START+0,DINDEX     # update message length

mk_tag_done:

        mov     DINDEX  call mk_dtr     # build DTR message if needed

!message:
        jmp     poll_for_work

#  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:
        mov     SELID           call initialize_scsiid
        and     FLAGS,0x3f                      # reselected, no IDENTIFY       
        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 and setting our next pointer to null so that the next
# time this SCB is used, we don't get confused.
#
select:
        or      SCBARRAY+0,NEEDDMA
        mov     WAITING_SCBH,SCBARRAY+30
        mvi     SCBARRAY+30,SCB_LIST_NULL
select2:
        call    initialize_for_target
        mvi     SCSISEQ,ENRSELI
        mvi     CLRSINT0,0x60                   # CLRSELDI|CLRSELDO
        mvi     CLRSINT1,0x8                    # CLRBUSFREE

#  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.
#
#  We can't simply look at the values of SCSISIGI here (if we want
#  to do synchronous data transfer), because the target won't assert
#  REQ if it's already sent us some data that we haven't acknowledged
#  yet.
#
ITloop:
        test    SSTAT1,0x8      jnz p_busfree   # BUSFREE
        test    SSTAT1,0x1      jz ITloop       # REQINIT

        and     A,0xe0,SCSISIGI                 # CDI|IOI|MSGI

        cmp     ALLZEROS,A      je p_dataout
        cmp     A,0x40          je p_datain
        cmp     A,0x80          je p_command
        cmp     A,0xc0          je p_status
        cmp     A,0xa0          je p_mesgout
        cmp     A,0xe0          je p_mesgin

        mvi     INTSTAT,BAD_PHASE               # unknown - signal driver

p_dataout:
        mvi     0               call scsisig    # !CDO|!IOO|!MSGO
        call    assert
        call    sg_load

        mvi     DINDEX,HADDR
        mvi     SCBARRAY+19     call bcopy_4

#       mvi     DINDEX,HCNT     # implicit since HCNT is next to HADDR
        mvi     SCBARRAY+23     call bcopy_3

        mvi     DINDEX,STCNT
        mvi     SCBARRAY+23     call bcopy_3

# If we are the last SG block, don't set wideodd.
        test    SCBARRAY+18,0xff jnz p_dataout_wideodd
        mvi     0x3d            call dma        # SCSIEN|SDMAEN|HDMAEN|
                                                #   DIRECTION|FIFORESET
        jmp     p_dataout_rest

p_dataout_wideodd:
        mvi     0xbd            call dma        # WIDEODD|SCSIEN|SDMAEN|HDMAEN|
                                                #   DIRECTION|FIFORESET

p_dataout_rest:
#  After a DMA finishes, save the final transfer pointer and count
#  back into the SCB, in case a device disconnects in the middle of
#  a transfer.  Use SHADDR and STCNT instead of HADDR and HCNT, since
#  it's a reflection of how many bytes were transferred on the SCSI
#  (as opposed to the host) bus.
#
        mvi     DINDEX,SCBARRAY+23
        mvi     STCNT           call bcopy_3

        mvi     DINDEX,SCBARRAY+19
        mvi     SHADDR          call bcopy_4

        call    sg_advance
        mov     SCBARRAY+18,SG_COUNT            # residual S/G count

        jmp     ITloop

p_datain:
        mvi     0x40            call scsisig    # !CDO|IOO|!MSGO
        call    assert
        call    sg_load

        mvi     DINDEX,HADDR
        mvi     SCBARRAY+19     call bcopy_4

#       mvi     DINDEX,HCNT     # implicit since HCNT is next to HADDR
        mvi     SCBARRAY+23     call bcopy_3

        mvi     DINDEX,STCNT
        mvi     SCBARRAY+23     call bcopy_3

# If we are the last SG block, don't set wideodd.
        test    SCBARRAY+18,0xff jnz p_datain_wideodd
        mvi     0x39            call dma        # SCSIEN|SDMAEN|HDMAEN|
                                                #   !DIRECTION|FIFORESET
        jmp     p_datain_rest
p_datain_wideodd:
        mvi     0xb9            call dma        # WIDEODD|SCSIEN|SDMAEN|HDMAEN|
                                                #   !DIRECTION|FIFORESET
p_datain_rest:
        mvi     DINDEX,SCBARRAY+23
        mvi     STCNT           call bcopy_3

        mvi     DINDEX,SCBARRAY+19
        mvi     SHADDR          call bcopy_4

        call    sg_advance
        mov     SCBARRAY+18,SG_COUNT            # residual S/G 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:
        mvi     0x80            call scsisig    # CDO|!IOO|!MSGO
        call    assert

        mvi     DINDEX,HADDR
        mvi     SCBARRAY+7      call bcopy_4

#       mvi     DINDEX,HCNT     # implicit since HCNT is next to HADDR
        mvi     SCBARRAY+11     call bcopy_3

        mvi     DINDEX,STCNT
        mvi     SCBARRAY+11     call bcopy_3

        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     0xc0            call scsisig    # CDO|IOO|!MSGO

        mvi     SCBARRAY+14     call inb_first
        jmp     p_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:
        mvi     0xa0            call scsisig    # CDO|!IOO|MSGO
        mvi     0x8             call mk_mesg    # build NOP message

        clr     STCNT+2
        clr     STCNT+1

#  Set up automatic PIO transfer from MSG_START.  Bit 3 in
#  SXFRCTL0 (SPIOEN) is already on.
#
        mvi     SINDEX,MSG_START+0
        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.
#  (We can't use outb for this since it wants the input in SINDEX.)
#
#  Keep an eye out for a phase change, in case the target issues
#  a MESSAGE REJECT.
#
p_mesgout2:
        test    SSTAT0,0x2      jz p_mesgout2   # SPIORDY
        test    SSTAT1,0x10     jnz p_mesgout6  # PHASEMIS

        cmp     DINDEX,1        jne p_mesgout3  # last byte?
        mvi     CLRSINT1,0x40                   # CLRATNO - drop ATN

#  Write a byte to the SCSI bus.  The AIC-7770 refuses to automatically
#  send ACKs in automatic PIO or DMA mode unless you make sure that the
#  "expected" bus phase in SCSISIGO matches the actual bus phase.  This
#  behaviour is completely undocumented and caused me several days of
#  grief.
#
#  After plugging in different drives to test with and using a longer
#  SCSI cable, I found that I/O in Automatic PIO mode ceased to function,
#  especially when transferring >1 byte.  It seems to be much more stable
#  if STCNT is set to one before the transfer, and SDONE (in SSTAT0) is
#  polled for transfer completion - for both output _and_ input.  The
#  only theory I have is that SPIORDY doesn't drop right away when SCSIDATL
#  is accessed (like the documentation says it does), and that on a longer
#  cable run, the sequencer code was fast enough to loop back and see
#  an SPIORDY that hadn't dropped yet.
#
p_mesgout3:
        mvi     STCNT+0, 0x01   
        mov     SCSIDATL,SINDIR

p_mesgout4:
        test    SSTAT0,0x4      jz p_mesgout4   # SDONE
        dec     DINDEX
        test    DINDEX,0xff     jnz p_mesgout2

#  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_mesgout5:
        test    SSTAT1,0x8      jnz p_mesgout6  # BUSFREE
        test    SSTAT1,0x1      jz p_mesgout5   # REQINIT

        and     A,0xe0,SCSISIGI                 # CDI|IOI|MSGI
        cmp     A,0xa0          jne p_mesgout6
        mvi     0x10            call scsisig    # ATNO - re-assert ATN

        jmp     ITloop

p_mesgout6:
        mvi     CLRSINT1,0x40                   # CLRATNO - in case of PHASEMIS
        and     FLAGS,0xdf                      # no active msg
        jmp     ITloop

#  Message in phase.  Bytes are read using Automatic PIO mode, but not
#  using inb.  This alleviates a race condition, namely that if ATN had
#  to be asserted under Automatic PIO mode, it had to beat the SCSI
#  circuitry sending an ACK to the target.  This showed up under heavy
#  loads and really confused things, since ABORT commands wouldn't be
#  seen by the drive after an IDENTIFY message in until it had changed
#  to a data I/O phase.
#
p_mesgin:
        mvi     0xe0            call scsisig    # CDO|IOO|MSGO
        mvi     A               call inb_first  # read the 1st message byte
        mvi     REJBYTE,A                       # save it for the driver

        cmp     ALLZEROS,A      jne p_mesgin1

#  We got a "command complete" message, so put the SCB pointer
#  into the Queue Out, 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 imediately
#  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    SCBARRAY+15,0xff        jnz resid
        test    SCBARRAY+16,0xff        jnz resid
        test    SCBARRAY+17,0xff        jnz resid

check_status:
        test    SCBARRAY+14,0xff        jz status_ok    # 0 Status?
        mvi     INTSTAT,BAD_STATUS                      # let driver know
        test    RETURN_1, 0x80  jz status_ok
        jmp     p_mesgin_done

status_ok:
#  First, mark this target as free.
        test    SCBARRAY+0,0x20 jnz complete            # Tagged command
        and     FUNCTION1,0x70,SCBARRAY+1
        mov     A,FUNCTION1
        test    SCBARRAY+1,0x88 jz clear_a
        xor     ACTIVE_B,A
        jmp     complete

clear_a:
        xor     ACTIVE_A,A

complete:
        mov     QOUTFIFO,SCBPTR
        mvi     INTSTAT,CMDCMPLT
        test    SCBARRAY+11,0xff jz start       # Immediate message complete
        jmp     p_mesgin_done

# 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, but a ton of instructions), or
# have the sequencer pause itself when it encounters a non-zero resid 
# (unecessary pause just to flag the command -- yuck, but takes few instructions
# and since it shouldn't happen that often is good enough for our purposes).  

resid:
        mvi     INTSTAT,RESIDUAL
        jmp     check_status

#  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.
#
p_mesgin1:
        cmp     A,1             jne p_mesgin2   # extended message code?
        
        mvi     ARG_1           call inb_next   # extended message length
        mvi     A               call inb_next   # extended message code

        cmp     A,1             je p_mesginSDTR # Syncronous negotiation message
        cmp     A,3             je p_mesginWDTR # Wide negotiation message
        jmp     p_mesginN

p_mesginWDTR:
        cmp     ARG_1,2         jne p_mesginN   # extended mesg length = 2
        mvi     A               call inb_next   # Width of bus
        mvi     INTSTAT,MSG_WDTR                # let driver know
        test    RETURN_1,0x80   jz p_mesgin_done# Do we need to send WDTR?

# We didn't initiate the wide negotiation, so we must respond to the request
        and     RETURN_1,0x7f                   # Clear the SEND_WDTR Flag
        or      FLAGS,ACTIVE_MSG
        mvi     DINDEX,MSG_START+0
        mvi     MSG_START+0     call mk_wdtr    # build WDTR message    
        or      SINDEX,0x10,SIGSTATE            # turn on ATNO
        call    scsisig
        jmp     p_mesgin_done

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

        test    RETURN_1,0xc0   jz p_mesgin_done# Do we need to mk_sdtr or rej?
        test    RETURN_1,0x40   jnz p_mesginN   # Requested SDTR too small - rej
        or      FLAGS,ACTIVE_MSG
        mvi     DINDEX, MSG_START+0
        mvi     MSG_START+0     call mk_sdtr
        or      SINDEX,0x10,SIGSTATE            # turn on ATNO
        call    scsisig
        jmp     p_mesgin_done

#  Is it a disconnect message?  Set a flag in the SCB to remind us
#  and await the bus going free.
#
p_mesgin2:
        cmp     A,4             jne p_mesgin3   # disconnect code?

        or      SCBARRAY+0,0x4                  # set "disconnected" bit
        jmp     p_mesgin_done

#  Save data pointers message?  Copy working values into the SCB,
#  usually in preparation for a disconnect.
#
p_mesgin3:
        cmp     A,2             jne p_mesgin4   # save data pointers code?

        call    sg_ram2scb
        jmp     p_mesgin_done

#  Restore pointers message?  Data pointers are recopied from the
#  SCB anyway at the start of any DMA operation, so the only thing
#  to copy is the scatter-gather values.
#
p_mesgin4:
        cmp     A,3             jne p_mesgin5   # restore pointers code?

        call    sg_scb2ram
        jmp     p_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.
#
p_mesgin5:
        test    A,0x80          jz p_mesgin6    # identify message?

        test    A,0x78          jnz p_mesginN   # !DiscPriv|!LUNTAR|!Reserved

        and     A,0x07                          # lun in lower three bits
        or      SAVED_TCL,A,SELID          
        and     SAVED_TCL,0xf7
        and     A,0x08,SBLKCTL                  # B Channel??
        or      SAVED_TCL,A
        call    inb_last                        # ACK
        mov     ALLZEROS        call findSCB    
setup_SCB:
        and     SCBARRAY+0,0xfb                 # clear disconnect bit in SCB
        or      FLAGS,IDENTIFY_SEEN             # make note of IDENTIFY

        call    sg_scb2ram                      # implied restore pointers
                                                #   required on reselect
        jmp     ITloop
get_tag:
        mvi     A               call inb_first
        cmp     A,0x20          jne return      # Simple Tag message?
        mvi     A               call inb_next
        call                    inb_last
        test    A,0xf0          jnz abort_tag   # Tag in range?
        mov     SCBPTR,A
        mov     A,SAVED_TCL
        cmp     SCBARRAY+1,A            jne abort_tag
        test    SCBARRAY+0,TAG_ENB      jz  abort_tag
        ret
abort_tag:
        or      SINDEX,0x10,SIGSTATE            # turn on ATNO
        call    scsisig
        mvi     INTSTAT,ABORT_TAG               # let driver know
        mvi     0xd             call mk_mesg    # ABORT TAG message
        ret

#  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.
#
p_mesgin6:
        cmp     A,7             jne p_mesgin7   # message reject code?

        mvi     INTSTAT, MSG_REJECT
        jmp     p_mesgin_done

#  [ ADD MORE MESSAGE HANDLING HERE ]
#
p_mesgin7:

#  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.
#
p_mesginN:
        or      SINDEX,0x10,SIGSTATE            # turn on ATNO
        call    scsisig
        mvi     INTSTAT,SEND_REJECT             # let driver know

        mvi     0x7             call mk_mesg    # MESSAGE REJECT message

p_mesgin_done:
        call    inb_last                        # ack & turn auto PIO back on
        jmp     ITloop


#  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,0x40                   # CLRATNO
        clr     SIGSTATE

#  if this is an immediate command, perform a psuedo command complete to
#  notify the driver.
        test    SCBARRAY+11,0xff        jz status_ok
        jmp     start

#  Instead of a generic bcopy routine that requires an argument, we unroll
#  the two cases that are actually used, and call them explicitly.  This
#  not only reduces the overhead of doing a bcopy by 2/3rds, 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_3:
        mov     DINDIR,SINDIR
        mov     DINDIR,SINDIR
        mov     DINDIR,SINDIR   ret

bcopy_4:
        mov     DINDIR,SINDIR
        mov     DINDIR,SINDIR
        mov     DINDIR,SINDIR
        mov     DINDIR,SINDIR   ret
        
bcopy_3_dfdat:
        mov     DINDIR,DFDAT
        mov     DINDIR,DFDAT
        mov     DINDIR,DFDAT    ret

bcopy_4_dfdat:
        mov     DINDIR,DFDAT
        mov     DINDIR,DFDAT
        mov     DINDIR,DFDAT
        mov     DINDIR,DFDAT    ret

#  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    FLAGS,ACTIVE_MSG jnz mk_mesg1   # active message?

        or      FLAGS,ACTIVE_MSG                # if not, there is now
        mvi     MSG_LEN,1                       # length = 1
        mov     MSG_START+0,SINDEX              # 1-byte message

mk_mesg1:
        mvi     SEQCTL,0x10     ret             # !PAUSEDIS|FASTMODE

#  Carefully read data in Automatic PIO mode.  I first tried this using
#  Manual PIO mode, but it gave me continual underrun errors, probably
#  indicating that I did something wrong, but I feel more secure leaving
#  Automatic PIO on all the time.
#
#  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_first:
        clr     STCNT+2
        clr     STCNT+1
        mov     DINDEX,SINDEX
        mov     DINDIR,SCSIBUSL ret             # read byte directly from bus

inb_next:
        mov     DINDEX,SINDEX                   # save SINDEX

        mvi     STCNT+0,1                       # xfer one byte
        mov     NONE,SCSIDATL                   # dummy read from latch to ACK
inb_next1:
        test    SSTAT0,0x4      jz inb_next1    # SDONE
inb_next2:
        test    SSTAT0,0x2      jz inb_next2    # SPIORDY - wait for next byte
        mov     DINDIR,SCSIBUSL ret             # read byte directly from bus

inb_last:
        mvi     STCNT+0,1                       # ACK with dummy read
        mov     NONE,SCSIDATL
inb_last1:
        test    SSTAT0,0x4      jz inb_last1    # wait for completion
        ret

#  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:
dma2:
        test    SSTAT0,0x1      jnz dma3        # DMADONE
        test    SSTAT1,0x10     jz dma1         # PHASEMIS, 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,0x4      jnz dma5        # DIRECTION
dma4:
        test    DFSTATUS,0x1    jz dma4         # !FIFOEMP

#  Now shut the DMA enables off, and copy STCNT (ie. the underrun
#  amount, if any) to the SCB registers; SG_COUNT will get copied to
#  the SCB's residual S/G count field after sg_advance is called.  Make
#  sure that the DMA enables are actually off first lest we get an ILLSADDR.
#
dma5:
        clr     DFCNTRL                         # disable DMA
dma6:
        test    DFCNTRL,0x38    jnz dma6        # SCSIENACK|SDMAENACK|HDMAENACK

        mvi     DINDEX,SCBARRAY+15
        mvi     STCNT           call bcopy_3

        ret

dma_finish:
        test    DFSTATUS,0x8    jz dma_finish   # HDONE

        clr     DFCNTRL                         # disable DMA
dma_finish2:
        test    DFCNTRL,0x8     jnz dma_finish2 # 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

initialize_for_target:
#  Turn on Automatic PIO mode now, before we expect to see a REQ
#  from the target.  It shouldn't hurt anything to leave it on.  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.
#
        clr     SIGSTATE 

        mvi     SXFRCTL0,0x8a                   # DFON|SPIOEN|CLRCHN

#  Initialize scatter-gather pointers by setting up the working copy
#  in scratch RAM.
#
        call    sg_scb2ram

#  Initialize SCSIRATE with the appropriate value for this target.
#
        call    ndx_dtr
        mov     SCSIRATE,SINDIR 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

#  Find out if disconnection is ok from the information the BIOS has left
#  us.  The tcl from SCBARRAY+1 should be in SINDEX; A will
#  contain either 0x40 (disconnection ok) or 0x00 (disconnection not ok)
#  on exit.
#
#  To allow for wide or twin busses, we check the upper bit of the target ID
#  and the channel ID and look at the appropriate disconnect register. 
#
disconnect:
        and     FUNCTION1,0x70,SINDEX           # strip off extra just in case
        mov     A,FUNCTION1
        test    SINDEX, 0x88    jz disconnect_a

        test    DISC_DSB_B,A    jz disconnect1  # bit nonzero if DISabled
        clr     A               ret

disconnect_a:
        test    DISC_DSB_A,A    jz disconnect1  # bit nonzero if DISabled
        clr     A               ret

disconnect1:
        mvi     A,0x40          ret

#  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     SCBARRAY+1,A    jne findSCB1    # target ID/channel/lun match?
        test    SCBARRAY+0,0x4  jz findSCB1     # should be disconnected
        test    SCBARRAY+0,TAG_ENB jnz get_tag
        ret

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

        mvi     INTSTAT,NO_MATCH                # not found - signal kernel
        mvi     0x6             call mk_mesg    # ABORT message

        or      SINDEX,0x10,SIGSTATE            # assert ATNO
        call    scsisig
        ret

#  Make a working copy of the scatter-gather parameters in the SCB.
#
sg_scb2ram:
        mov     SG_COUNT,SCBARRAY+2

        mvi     DINDEX,SG_NEXT
        mvi     SCBARRAY+3      call bcopy_4

        mvi     SG_NOLOAD,0x80
        test    SCBARRAY+0,0x10 jnz return      # don't reload s/g?
        clr     SG_NOLOAD        ret

#  Copying RAM values back to SCB, for Save Data Pointers message.
#
sg_ram2scb:
        mov     SCBARRAY+2,SG_COUNT

        mvi     DINDEX,SCBARRAY+3
        mvi     SG_NEXT         call bcopy_4

        and     SCBARRAY+0,0xef,SCBARRAY+0
        test    SG_NOLOAD,0x80  jz return       # reload s/g?
        or      SCBARRAY+0,SG_LOAD       ret

#  Load a struct scatter if needed 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 the above DMA, assumes a little-endian host data storage.
#
sg_load:
        test    SG_COUNT,0xff   jz return       # SG being used?
        test    SG_NOLOAD,0x80  jnz return      # don't reload s/g?

        clr     HCNT+2
        clr     HCNT+1
        mvi     HCNT+0,SG_SIZEOF

        mvi     DINDEX,HADDR
        mvi     SG_NEXT         call bcopy_4

        mvi     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.
#

        call    dma_finish

#  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 */   
# };
#

        mvi     DINDEX, SCBARRAY+19
        call    bcopy_4_dfdat

# For Linux, we must throw away four bytes since there is a 32bit gap
# in the middle of a struct scatterlist
        mov     NONE,DFDAT
        mov     NONE,DFDAT
        mov     NONE,DFDAT
        mov     NONE,DFDAT

        call    bcopy_3_dfdat           #Only support 24 bit length.
        ret

#  Advance the scatter-gather pointers only IF NEEDED.  If SG is enabled,
#  and the SCSI transfer count is zero (note that this should be called
#  right after a DMA finishes), then move the working copies of the SG
#  pointer/length along.  If the SCSI transfer count is not zero, then
#  presumably the target is disconnecting - do not reload the SG values
#  next time.
#
sg_advance:
        test    SG_COUNT,0xff   jz return       # s/g enabled?

        test    STCNT+0,0xff    jnz sg_advance1 # SCSI transfer count nonzero?
        test    STCNT+1,0xff    jnz sg_advance1
        test    STCNT+2,0xff    jnz sg_advance1

        clr     SG_NOLOAD                       # reload s/g next time
        dec     SG_COUNT                        # one less segment to go

        clr     A                               # add sizeof(struct scatter)
        add     SG_NEXT+0,SG_SIZEOF,SG_NEXT+0
        adc     SG_NEXT+1,A,SG_NEXT+1
        adc     SG_NEXT+2,A,SG_NEXT+2
        adc     SG_NEXT+3,A,SG_NEXT+3   ret

sg_advance1:
        mvi     SG_NOLOAD,0x80  ret             # don't reload s/g next time

#  Add the array base SYNCNEG 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,0x08    jz ndx_dtr_2
        or      A,0x08          # Channel B entries add 8
ndx_dtr_2:
        add     SINDEX,SYNCNEG,A

        and     FUNCTION1,0x70,SCSIID           # 3-bit target address decode
        mov     A,FUNCTION1     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 negotiat 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 culpret.
#
mk_dtr:
        test    SCBARRAY+0,0xc0 jz return       # NEEDWDTR|NEEDSDTR
        test    SCBARRAY+0,NEEDWDTR jnz  mk_wdtr_16bit
        or      FLAGS, MAX_OFFSET       # 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, MAX_OFFSET jnz mk_sdtr_max_offset
        and     DINDIR,0x0f,SINDIR              # Sync Offset

mk_sdtr_done:
        add     MSG_LEN,-MSG_START+0,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, MAX_OFFSET
        test    SCSIRATE, 0x80  jnz wmax_offset # Talking to a WIDE device?
        mvi     DINDIR, MAX_OFFSET_8BIT
        jmp     mk_sdtr_done

wmax_offset:
        mvi     DINDIR, MAX_OFFSET_WIDE
        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,-MSG_START+0,DINDEX ret # update message length
        
#  Set SCSI bus control signal state.  This also saves the last-written
#  value into a location where the higher-level driver can read it - if
#  it has to send an ABORT or RESET message, then it needs to know this
#  so it can assert ATN without upsetting SCSISIGO.  The new value is
#  expected in SINDEX.  Change the actual state last to avoid contention
#  from the driver.
#
scsisig:
        mov     SIGSTATE,SINDEX
        mov     SCSISIGO,SINDEX ret

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,0x18
        dec     A       
        jmp     sdtr_to_rate_loop
sdtr_to_rate_done:
        shr     RETURN_1,0x2
        add     RETURN_1,0x18   ret

return:
        ret

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