         Title SDNET Asynch Serial Link Driver (ASLD) (C) 1984 Software Dynamics
         ifund showroughedges
showroughedges equ 1 ; we want to see everything which is unfinished
         fin
         list  0
systemdefs equ 1                       we want system definitions
iopkdefs   equ 1                       ...and I/O package interface info
         include   sdos11defs.asm
         list  1
         include   sdnet10defs.asm     definitions file
         page   ASLD overview
*        Asynchronous Serial Link Driver for SDNET/6800 /6809
*        Copyright (C) 1984 Software Dynamics
*        2111 W. Crescent, Suite G.
*        Anaheim, California 92801
*        All rights Reserved

*        The Asynchronous Serial Link Driver is intended to implement
*        physical network link I/O for SDNET (a set of software modules
*        designed to allow operation of a local area network),
*        for network hardware designed to use 8-bit binary transfers
*        via asynchronous serial devices such as the Motorola 6850 (ACIA)
*        or Synertek 6551 (nicely designed ACIA + baud rate generation).
*        This driver is designed to transfer data bytes efficiently
*        (about 9% overhead at 9600 baud on 2MHz 6809)
*        using an interrupt for EACH data byte transmitted or received.
*        Because of the high overhead involved in moving data between
*        nodes, and because each node must service an interrupt
*        as a result of data received, whether or not the message
*        is intended for that node, such networks typically have
*        just a few nodes in them, and operate at a relatively slow
*        data rate (38.4 Kbps and lower).
*        This assumption allows this driver to be used in a wide
*        variety of environments, without requiring the construction
*        of a complex custom link driver (high-performance networks
*        require a custom link driver for efficiency's sake).
*        Two forms of network are handled: the two-node network,
*        and the multiple-node network.  The multiple-node network
*        requires a special RS232 "star" repeater, which repeats everything
*        it hears on one port to all ports (including the one
*        from which it heard), to give a broadcast effect.  The two
*        node network simply has two computers linked via conventional
*        RS232.  This link driver transmits CRC-checked messages
*        and handles all detected errors by appropriate retries
*        to ensure reliable data delivery over the network.
*        Network access is via a classic Carrier-Sense Multiple Access
*        method with Collision Detection and resolution, so no
*        bus master is required.
         page
*        Installation of this driver on a specific piece of hardware
*        requires fundamentally that routines that deal directly with
*        the LSI serial part be implemented; the ASLD driver handles
*        all the rest of the ugly details.
*
*        Routines that deal with task level interface to link driver are
*        in the link driver so that the link driver can define its own
*        buffer management primitives; this is important from an efficiency
*        point of view, and is also consistent with the Z80 version.
         page Revision History
*        Version 0.9 6/1/81 NCC primitive demo version using Conrac 480s.
*              No field installations.
*
*        Version 1.0 5/1/85 First production implementation.
*
*
*        To assemble:
*           Place the files ASLD.ASM, SDNET10DEFS.ASM and SDOS11DEFS.ASM
*           on the default disk drive. Then type:
*                  .ASM                         [or ASM09]
*                  Source file = ASLD.ASM
*                  Listing file = ASLD.LPT
*                  Object file = ASLD.xxx       [xxx=680 or 689]
*                  >      WITH   WI=xx,DE=yy
*                  >
*           The assembly will be run. The resulting object file must
*           be combined with an SDOS, and I/O package, the module
*           SDNETDEVICE.xxx, and suitable interface software to
*           the network hardware.

         page Data Structures and fine detail
*        Data Structure definitions
*
*        Node Representative Blocks (NRBs) are data structures shared
*           between the Link Driver (LD) and the Socket Manager (SM).
*           An NRB contains information representing this node's view
*           of the state of another node in the network.  A Transmit
*           Ring buffer specified by each NRB holds data not yet
*           reliably sent to the other node; a similar Receive Ring Buffer
*           holds data from the other node not yet processed by the
*           SM or Listener task.
*
*        Because NRBs (plus their buffers) are bulky, only a limited
*           number can be placed in a node.  Since a large number of
*           other nodes can attempt access to a particular node, NRBs
*           must be assigned to nodes dynamically.  This is accomplished
*           by use of an NRB index table for each network in which a node
*           participates (a node may participate officially in only one
*           network; however, it may have identities on other networks for
*           which it can act as a gateway to its official network).  Each
*           NRB table (potentially) has room for 256 slots containing
*           pointers to NRBs; the slots are numbered 0, 1, 2, ...255
*           and correspond to physical node addresses
*           on the local area network bus serviced by ASLD.  NRBs are
*           assigned on demand to slots in NRB index table for the
*           network for the node which the NRB represents.  Use
*           of these tables makes it possible for an interrupt service
*           routine to quickly locate the appropriate NRB as specified
*           by the FROM field of a received message.  A minor difficulty is
*           that such NRB tables are bulky if all 256 slots are permanently
*           available; most "user" nodes will only need to talk
*           to servers.  By assigning servers relatively low numbers,
*           NRB index tables in user nodes can be restricted in size.
*           Trying to allow access to nodes with numbers higher than
*           the size of the NRB index table would require more code than
*           the balance of the table would require, so we don't bother.
*           Besides, networks using Asynch Serial devices don't have
*           many nodes, so keeping the node numbers small should not be hard.
         page
*        To interface SDNET via ASLD to a specific system's hardware,
*           several almost-trivial physical device I/O routines need
*           to be coded, and access points for these routines placed
*           in the Physical Link Control Block for each network device,
*           including:
*        PLCBASC:ILRESET  (which resets the device hardware)
*        PLCBASC:ILPUTDEV (which sends a byte to the device for transmission)
*        PLCBASC:ILENBOI  (which enables xmit buffer empty interrupts)
*        PLCBASC:ILDISOI  (which disables xmit buffer empty interrupts)
*        PLCBASC:ILENBII  (which enables input data available interrupts)
*        PLCBASC:ILDISII  (which disables input data available interrupts)
*        PLCBASC:ILCSMA   (which senses for carrier to allow multiple access)
*        PLCBASC:ILXGAP   (which transmits end-of-message "gap")
*           and
*        Interrupt Poll code, which checks for an interrupt
*           from the network serial device and passes control suitably
*           into the ASLD driver (see example below).
*           and
*        Clock tick interrupt code (passes control to ASLD:ILCLOCKTICK)
*
*        For sample interrupt code for a 6850, see the ASLD PLCB definitions
         page
;   The ASLD driver provides efficient ring management routines for
;   buffering input/output data.  Buffer routines to put and retreive
;   data for both input and output are provided; most of the routines
;   are coded inline to obtain very high performance.
;   Such rings may handle up to 65536 bytes per ring;
;   each NRB contains enough information (in NRB:RINGxxx)
;   to specify everything the driver needs to know about a ring.
;   Some of the values required are slightly unusual in order
;   to allow the most efficient ring management routines possible.
;   A ring buffer may begin at any address.

;   The format of the provided ring buffer control block in the NRB is:
;
;       *-------------------*-------------------*-------------------*
;       | input fetch addr  |      # data       | -dist to end ring !
;       *-------------------*-------------------*-------------------*
;       | input store addr  |      # room       | -dist to end ring |
;       *-------------------*-------------------*-------------------*
;       |   buffer base     |    - length       |
;       *-------------------*-------------------*
;
;       *-------------------*-------------------*-------------------*
;       | output fetch addr |      # data       | -dist to end ring |
;       *-------------------*-------------------*-------------------*
;       | output store addr |      # room       | -dist to end ring |
;       *-------------------*-------------------*-------------------*
;       |   buffer base     |    - length       |
;       *-------------------*-------------------*
;
;   The names used in the code are (xx is "in" or "out"):
;       ringxxfetch, ringxxdata, ringxxfdter,
;       ringxxstore, ringxxroom, ringxxsdter,
;       ringxxbase, ringxxlen
         page
;   ...where the fetch and store addresses are the addresses of the next byte
;   to be accessed, "dist to end ring" (dter) is the number of bytes left
;   before the associated pointer must be "wrapped" to the beginning,
;   # data is amount of data in the ring, # room is ringsize-#data,
;   and the buffer base and length are the origin and length of the buffer.
;   The base and length fields are used to initialize the fetch/store address
;   and distance to end ring fields when a buffer wraps around.
;   Distance to end ring is kept as a negative number, because it is
;   faster to increment a 16 bit quantity than to decrement it, and
;   because checking for overflow is cheap (the upper half of the count
;   goes zero).
;
;                       fetch              store
;           xx00        xx25               xx87         xxFF
;           |              |                  |            |
;
;           *-----------------------------}{---------------*
;           |//////////////      (data)       /////////////|
;           *-------------------------}{-------------------*
;
;             room: 9E bytes            data: 62 bytes
;
;
;                       store              fetch
;           xx00        xx25               xx87         xxFF
;           |              |                  |            |
;
;           *-----------------------------}{---------------*
;       *-->|    (data)    ///////////////////    (data)   |---*
;       |   *-------------------------}{-------------------*   |
;       *--  --  --  --  --  --  --  --  --  --  --  --  --  --*
;
;             room: 62 bytes            data: 9E bytes

;   The above illustrates a 256 byte ring buffer, beginning on a page
;   boundary (page boundaries are NOT a requirement).
          page
*   The ASLD driver uses the Software Dynamics Link Protocol message format.
*
*      SDLP message format: (transmitted left to right, top to bottom)
*
*      ----------------------------------------------------------------
*      !  syn !  to  ! from !  to  ! from ! cntrl !  rcvdok  ! rcvrdy ! ...
*      ----------------------------------------------------------------
*         1b     1b     1b     1b     1b     1b        2b        2b
*
*             ------------------------------------------------
*         ... ! xmtcnt ! xmtbase ! ..data... !  crc  !  etb  ! <gap>
*             ------------------------------------------------
*                2b        2b         nb        2b      1b
*
*       syn = ascii:syn byte, to raise probability of message sync
*       to = destination computer (:00 = "all" )
*       from = source computer (must be <> 0)
*       cntrl = control code (used to establish/break comm link)
*                (if cntrl = "INITIATING", xmtcnt must be zero).
*       rcvdok = position in virtual stream of first byte not received
*                (correctly) yet, modulo 2^16
*       rcvrdy = suggested maximum xmtcnt for use by other computer
*                usually is size of "from" computer's remaining receiver
*                buffer space
*       xmtcnt = number of ..data.. bytes sent in this message
*       xmtbase = position of first ..data.. byte in "to" computer's receive
*                 file, modulo 2^16 (omitted if xmtcnt = 0 )
*       ..data.. = stream of data bytes for "to" computer
*                  (max of 2^16-1; omitted if xmtcnt = 0 )
*       crc = crc of message using crc-ccitt as divisor polynomial
*       etb = ascii:etb character, used to hint that end of message is near
*       <gap> = silent 1+epsilon character time (proves ETB not part of msg)
         page
*       Definition: 1 byte time, not containing "carrier" (i.e., start bit)
*       is assumed to be end of a message.  All transmitted messages
*       always have such a gap after the "ETB" byte.  After collisions,
*       we try to send an ETB in hopes that other nodes will hear them
*       correctly.
*
*       TO and FROM are transmitted twice, to increase the probability
*       that even with a CRC error in the middle of the message, the
*       driver can decide that TO and FROM are correct, and send what
*       amounts to a "negative acknowledgement" to speed recovery from
*       such errors. The second TO/FROM pair is actually the 16 bit CRC of
*       the first TO/FROM pair (because they are the first bytes of the
*       message), and thus gaurantee that any error of 16 bits or less
*       in the first portion of the message will be detected.
*
*       Collision detection is only performed on the part of the message
*       up to XMTBASE.  This keeps the overhead during transmission
*       low for the body of the message, and still gives very high
*       probability of detecting a collision during the early part of
*       the message (Error rate < 1 in (2^8)^11)
*
*       The "window" size (see page 161 of "Computer Networks" by A. Tanenbaum)
*       is 32767 bytes to ensure that after a receiver advances its window
*       that the new window does not overlap the old window.  Otherwise,
*       the receiver might accept a duplicate message with old data as though
*       it were new data.
*
*       *** write down constraints on xmtcnt, rcvrdy ***
        page
*                       SDLP State Transition Table
*  Current            Control field in message received
*  State                     NORMAL/
*  -----  INITIATNG RESYNCHED ACKSOON  WANTQUIT  AGREEQUIT OTHER
*
*DEAD      DEAD     DEAD     DEAD      DEAD      DEAD      DEAD
*RESYNCHD  RESYNCHD CROAK    CROAK     AGREEQUIT CROAK     CROAK
*INITIATNG RESYNCHD INITIATD INITIATNG INITIATNG INITIATNG INITIATNG
*INITIATD  DEAD     INITIATD CONVERSEx AGREEQUIT CROAK     CROAK
*CONVERSER DEAD     CROAK    CONVERSEx AGREEQUIT CROAK     CROAK
*CONVERSES DEAD     CROAK    CONVERSEx AGREEQUIT CROAK     CROAK
*CONVERSEI DEAD     CROAK    CONVERSEx AGREEQUIT CROAK     CROAK
*WANTQUIT  DEAD     CROAK    WANTQUIT  AGREEQUIT QUIT      CROAK
*AGREEQUIT DEAD     CROAK    CROAK     AGREEQUIT CROAK     DEAD
*QUIT      QUIT     QUIT     QUIT      QUIT      QUIT      QUIT
*DIE       DIE      DIE      DIE       DIE       DIE       DIE
*
*     The Current States are listed in the normal order of transitions from
*     DEAD to CONVERSE back to DEAD.  Table entry specifies new "current"
*     state after receipt of message containing control field specified.
*
*     QUIT is a transitory state which causes transmission of a QUIT message
*     and then immediately transits to a DEAD state.  Likewise, CROAK is
*     a transitory state which causes transmission of a DIE message and
*     immediately transits to DEAD.
*
*     Receipt of a DIE message causes termination of a conversation due
*     to "remote requested DIE" and the NRB makes an immediate transition
*     to DEAD state.
*
*     Receipt of a QUIT message is ignored in INITIATING state, otherwise
*     causes transition to CROAK.
*
*     CONVERSEx is the state in which normal data transfers take place.
*     "x" may be "R", for "this node is expecting a response",
*     "S", for "this node should send soon", or "I" for "this node has
*     nothing to send, and expects no data (Idle)".  In the Idle state,
*     this node will not send messages, thus keeping link traffic to a minimum.
*
*     OTHER in the control field refers to receipt of a garbage control
*     field.
*
*     If a TIMEOUT occurs after sending a message, or a response occurs
*     which is garbled (bad CRC) but is identifiable as to the source
*     (note IDCRC field in message format), the same message is sent
*     as on the previous iteration, and the state does not change.
*     If too many timeouts occur, an automatic transition to DEAD occurs.
         page    Notation
*     Notations used in this driver:
*        ASLD       abbreviation for Asynchronous Link Driver
*        LD:...     labels which provide entry points into the Link Driver
*        SM:...     refers to Socket Manager
*        ASLD:...   special entry points or variables shared by ASLD and I/O pkg
*        ASLDT:...  are routines that operate at task level
*        ASLDW:...  are task Wakeup subroutines
*        ASLDV:...  are static storage variables internal to ASLD driver
*        ASLDI:...  labels related to Interrupt code (general)
*        ASLDIC:... labels related to Interrupt Clock code
*        ASLDIX:... labels related to Interrupt Xmit code
*        ASLDIR:... labels related to Interrupt Receive code
*
*        ASLDIx: interrupt routines operate with interrupts disabled
*        unless otherwise noted.
*
         if    m6809
ASLD:DriverEstimatedSize equ $1800     ; change this if it is wrong
         else  m6800
ASLD:DriverEstimatedSize equ $1C00     ; change this if it is wrong
         fin
         page  ASLD: Entry points
         org   ASLD     ; specifies origin for Asynch Link Driver
*        Entry points into the Link Driver from the Socket Manager

;lde:reset ; called by SM to reset link driver at boot
         jmp   asldi:reset
;lde:olnk  ; called by SM to open link using NRB
         jmp   asldt:olnk
;lde:clnk  ; called by SM to nicely close link to other node
         jmp   asldt:clnk
;lde:blnk  ; called by SM to abruptly close link
         jmp   asldt:blnk
;lde:sbyt  ; called by SM to send a single byte
         jmp   asldt:sbyte
;lde:sblk  ; called by SM to send a block of data
         jmp   asldt:sblk
;lde:thnt  ; called by SM to signal "Transmit Hint"
         jmp   asldt:thnt
;lde:rhnt  ; called by SM to when more data needed soon
         jmp   asldt:rhnt
;lde:rcnt  ; called by SM to get received data count
         jmp   asldt:rcnt
;lde:gbyt  ; called by SM to get another byte soon
         jmp   asldt:gbyte
;lde:rbyt  ; called by SM to get another byte leisurely
         jmp   asldt:rbyte
;lde:gblk  ; called by SM to receive data block soon
         jmp   asldt:gblk
;lde:rblk  ; called by SM to receive data block leisurely
         jmp   asldt:rblk
;lde:bcst  ; called by SM to broadcast a message
         jmp   asldt:bcst
;lde:rqb   ; called by SM to get rcvd broadcast count
         jmp   asldt:rqb
;lde:stats ; called to get link error statistics
         jmp   asldt:stats
         page
*        Entry points into Link Driver from I/O package
;asld:interruptplcb ; points to PLCB on interrupt entry to ASLD
         fdb    changed

;asld:inputlateinterrupt ; JMP to here with input interrupt error
*        Come here when we detect lost data, Stacks must not be switched
*        Intended to handle conditions like OVERRUN (we missed a character)
         jmp    asldir:inputlateinterrupt go service error interrupt

;asld:inputbadinterrupt ; JMP to here with input interrupt error
*        Come here when malformed data detected, Stacks must not be switched
*        Intended to handle conditions like FRAMING ERROR (garbled character)
         jmp   asldir:inputbadinterrupt go service probable collision
         page   ASLD Reset Code
asldi:reset ; called by SM to reset link driver at boot
; called once per PLCB referencing this driver in PLCB:LINKDRIVER
; Must reset Link hardware, and initialize for operation of Link.
         if    showroughedges
? ; could put this entire routine into CRCBUFFER somehow?
; Sure, if we could call it at INTENABLE time.
         fin
         jsr   gencrctable             set up table to do fast CRC
         ldx   PLCBpointer             fetch pointer to Physical Link Control Block
         clr   plcbas:state,x          reset state to "uncommitted"
         clr   plcbas:collisionresolutionactive,x flag "not resolving collisions"
         ldaa  #ascii:syn              set 1st byte of message head properly
         staa  plcbas:msgsyn,x
         ldd   PLCBpointer             form pointer to dummy NRB
         addd  #nrb:xmitqflink-plcbas:xmitqhead
         std   plcbas:xmitqhead,x      make XMITQ head point to dummy NRB
         std   plcbas:xmitqhead+2,x    make XMITQ tail point to dummy NRB
         clra                          check way to do "ldd #0#
         clrb
         std   plcbas:xdelaymask,x     zero the conflict resolution mask
         std   plcbasc:charactertimeout+timeout:fuse,x disable the character timeout fuse
         ; plcbasc:charactertimeout+timeout:routine is initialized dynamically by ASLD
         std   plcbas:nrbptr,x         note that no NRB is receiving right now
         ; reset interrupt vectors in PLCB
         ldaa  #$7e                    JMP opcode
         staa  plcbas:ilinputintPC-1,x set up INPUT INTERRUPT ENTRY vector
         staa  plcbas:iloutputintPC-1,x set up OUTPUT INTERRUPT ENTRY vector
         ldd   #asldir:msgstart        set up INPUT INTERRUPT ENTRY vector
         std   plcbas:ilinputintPC,x
         ldd   #asldix:ignoretdre      set up OUTPUT INTERRUPT ENTRY vector
         std   plcbas:iloutputintPC,x  to initially be paranoid
; Assert: Interrupt Poll chain block links established at Sysgen time
; A later revision of this driver may have to insert Poll block in interrupt chain
         jsr   plcbasc:ilreset,x       go reset the link hardware, get PNID
         pshd                          save physical net/node ID
         if    m6800!m6801!m6811
         ldd   PLCBpointer             form pointer to XMITFAILTOB
         addd  plcbas:xmitfailtob
         tdx
         else  (m6809)
         ldx   PLCBpointer
         leax  plcbas:xmitfailtob,x
         fin
         ldd   PLCBpointer             set TOB:PARAMETER to point to PLCB
         jsr   SDOSRT+rt:itmo          initialize timeout block
         if    m6800!m6801!m6811
         addd  plcbas:xmittob          form pointer to XMITTOB
         tdx
         ldd   PLCBpointer             set TOB:PARAMETER to point to PLCB
         else  (m6809)
         ldx   PLCBpointer
         leax  plcbas:xmittob,x
         fin
         jsr   SDOSRT+rt:itmo          initialize timeout block
         if    m6800!m6801!m6811
         ldd   PLCBpointer             form pointer to RECVTOB
         addd  plcbas:recvtob
         tdx
         ldd   PLCBpointer             set TOB:PARAMETER to point to PLCB
         else  (m6809)
         ldx   PLCBpointer
         leax  plcbas:recvtob,x
         fin
         jsr   SDOSRT+rt:itmo          initialize timeout block
         ldx   PLCBpointer             fetch pointer to Physical Link Control Block
         ldd   #asldix:transmitfailtimeout where to go when XMITFAILTOB goes off
         std   plcbas:xmitfailtob+tob:routine,x
         jsr   plcbasc:ilenbii,x       enable input interrupts
         ldb   #plcbas:statisticsend-plcbas:statisticsbegin size of statistics area
asldi:resetstatisticsloop ; zero a statistics byte
         clr   plcbas:statisticsbegin,x zero out the statistics variables
         inx                           advance pointer to next statistics byte
         decb
         bne   asldi:resetstatisticsloop b/ more to zero
         puld                          restore physical net/node ID
         ldx   PLCBpointer             set NIT:NodeID slot
         ldx   plcbasc:NITptr,x        pointer to NIT table for PLCB
         stab  NIT:nodeid,x            record Nodeid assigned to this node
         okrts                         (A) has net id, (B) has node ID
         page
asldi:clearin ; called from interrupt level to clear the input ring buffer
;   (X) must point to desired NRB; (X) is preserved.
;   interrupts MUST be disabled to prevent timing splinters
;   This routine is called very infrequently
         clra                          ; cheap version of ldd #0
         clrb
         std   nrb:ringindata,x        amount of input data ready for task
         subd  nrb:ringinlen,x         = - SizeOfRing
         std   nrb:ringinroom,x        will be -1 when ring is (almost) full
         ldd   nrb:ringoutlen,x        = - SizeOfRing
         std   nrb:ringinsdter,x       dist to end ring for ilputbuf to use
         std   nrb:ringinfdter,x       dist to end ring for tlgetbuf to use
         ldd   nrb:ringinbase,x
         std   nrb:ringinfetch,x
         std   nrb:ringinstore,x
         rts

asldi:clearout ; called from the task level to clear the output ring buffer
;   interrupts MUST be disabled to prevent timing splinters
;   This routine is called very infrequently
         clra                          ; cheap version of ldd #0
         clrb
         std   nrb:ringoutdata,x       amount of data for interrupt level
         subd  nrb:ringoutlen,x        since DCB:RINGOUTLEN contains -size
         std   nrb:ringoutroom,x       = how much space available in ring
         ldd   nrb:ringoutlen,x        = - SizeOfRing
         std   nrb:ringoutfdter,x      dist to end ring for iloutput to use
         std   nrb:ringoutsdter,x      dist to end ring for tlputbuf to use
         ldd   nrb:ringoutbase,x       set pointers to start of ring
         std   nrb:ringoutfetch,x
         std   nrb:ringoutstore,x
         rts
         page   Task level Socket manager interface routines
asldt:bcst ; called by SM to broadcast a message
         swi                           ?needs work?

asldt:rqb ; called by SM to get rcvd broadcast count
         swi                           ?needs work?

asldt:rhnt ; called by SM to when more data needed soon from NRB @ NRBPOINTER
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirercv        signal that we need to receive
         jmp   sdos+sdos:startio

asldt:rcnt ; called by SM to get received data count
         ldx   nrbpointer              which NRB needs attention
         ldd   nrb:ringindata,x        grab amount of data available
         if    m6800
         cmpd  nrb:ringindata,x        check: timing splinter occur ?
         bne   asldt:rcnt              b/ yes, grab count again
         fin
         okrts
         page
asldw:rbytewaitdatacheck ; task wake-up subroutine to wait for data arrival
         ldaa  nrb:state,x             check for remote node gave up the ghost
         beq   asldw:rbytewaitdatacheck1 b/ it did, wake the task
         ldaa  nrb:ringindata,x        check for input available
         oraa  nrb:ringindata+1,x
         rts

asldw:rbytewaitdatacheck1
         inca                          flag "wake up task"
         rts

asldt:rbytewaitdata ; buffer was empty, wait for input data to arrive
         if    m6800!m6801
         cli                           ; (2~) let the sun shine again
         else  (m6809)
         andcc #\%01010000             ; (3~) allow FIRQ and IRQ again
         fin
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirercv        tell interrupt level we want data!
         jsr   sdos+sdos:startio       (high overhead here makes no difference)
         ldd   #asldw:rbytewaitdatacheck
         jsr   sdos+sdos:waitcond      set up task wait
         ldx   nrbpointer
         ldaa  nrb:state,x             check for remote node gave up the ghost
         bne   asldt:rbyte             b/ not dead! so data must be available
asldt:recvwaitdied ; re-enter here if died whle waiting to recieve
         clr   nrb:datarequiredflag,x  our need for data has been aborted
asldt:nodedied ; node died, let caller know
         ldx   nrb:epitaph,x           fetch reason node died
         ; the only way to clear the error is to call ASLDT:RESETNRB
         jmp   sdos+sdos:errored       and propogate error response
         page
asldt:gbyte ; called by NRB Listener Task to get another byte from NRB @ NRBPOINTER
; Remote node must send data soon or we will declare link failure
        ldd    sdos+sdos:clock+1      fetch clock time stamp
        if     m6800
        cmpd   sdos+sdos:clock+1      did we get time stamp splinter-free?
        bne    asldt:gbyte            b/ no, get it again (loops once max)
        fin
        ldx    NRBpointer
        std    nrb:datarequiredtimestamp,x record time when data first required
        ; We will check time stamp each time a message is received.
        ; If message arrival time is too far beyond time stamp,
        ; and no data received yet, then we will declare remote node too slow.
        ; We will get messages because when NRB:DATAREQUIREDFLAG is set,
        ; we will never enter NODESTATE:CONVERSEI, and thus will exchange
        ; messages continuously until the remote node gives us what we want.
        inc    nrb:datarequiredflag,x  record that data IS required
;       bra    asldt:rbyte             go recieve a byte
        page
asldt:rbyte ; called by NRB Listener Task to get another byte from NRB @ NRBPOINTER
* !!! will need ANOTHER copy of ring management routines in SKT MGR
;  next byte from NRB is returned in (A) with carry reset,
;  or returns error with carry set
;  task is put to sleep if no bytes are available, receive hint is signalled
;  NRB need not be locked, as only the Listener task call LD:RBYTE or LD:RBLK
;  MUST be called from the task level.
;  This routine destroys all registers except (A) and (X).
;  Time analysis for 6809: 88~ on fast path
         ldx   NRBpointer              ; (5~) fetch pointer to NRB involved
         if    m6800!m6801
         sei                           ; (2~) make it safe to update dbl bytes
         else  (m6809)
         orcc  #%01010000              ; (3~) mask out FIRQ and IRQ
         fin
         ldab  nrb:ringindata+1,x      ; (4+1~) any data available ?
         bne   asldt:rbyte1            ; (3~) b/ yes, go get byte from ring
         ldaa  nrb:ringindata,x        ; is upper byte also zero ?
         beq   asldt:rbytewaitdata     ; b/ no data available
asldt:rbyte0 ; lotsa data is available
; we use the fastest possible code in rbyte and sbyte for performance
         dec   nrb:ringindata,x        ; propogate borrow from "DEC" below
asldt:rbyte1 ; some data is available from ring buffer
         dec   nrb:ringindata+1,x      ; (6+1~) decrease remaining ring data
         inc   nrb:ringinroom+1,x      ; (6+1~) increase room about to be available
         bne   asldt:rbyte2            ; (3~) b/ no carry
         inc   nrb:ringinroom,x        ; propogate carry
asldt:rbyte2
         page
         if    m6809
         ldu   nrb:ringinfetch,x       ; (5+1~) get the byte from the ring
         lda   ,u+                     ; (4+2~) and advance the pointer
         andcc #\%01010000             ; (3~) allow FIRQ and IRQ again
         else  (m6800!m6801)
         ldx   nrb:ringinfetch,x       ; get the byte from the ring
         ldaa  0,x
         cli                           ; (2~) allow ints as soon as safe
         ldx   nrbpointer              ; make (X) point to NRB again
         fin
         clr   nrb:datarequiredflag,x  ; (6+1~) we got our byte, don't bug remote any more
         ; We have just made some room.  We should tell the interrupt
         ; level code so it can send an ACK now rather than wait for
         ; SOON. We do this when all the following conditions are satisifed:
         ;    1) The input ring is completely empty (max room for data)
         ;    2) We are expected to send next
         ;    3) No data is being sent (xmit buffer is not locked)
         ;    4) Remote node has more data (he send ACKSOON or we lost some)
         ;    5) We have enough room for an efficient message (true when ring empty)
         ldab  nrb:ringindata+1,x      ; (4+1~) any data still left in ring?
         bne   asldt:rbyte2a           ; (3~) b/ more data in ring
         ; note order of test on 16 bit value of RINGINDATA: it is
         ; timing-splinter proof, as the interrupt routines can only
         ; INCREASE nrb:ringindata.
         ; DON'T CHANGE THE ORDER UNLESS YOU WANT ONE HELL OF A BUG!
         ldab  nrb:ringindata,x        ; ...?
         bne   asldt:rbyte2a           ; b/ more data in ring
         ldb   nrb:state,x             ; inspect node state
         cmpb  #nodestate:converses    ; are we expected to send next ?
         bne   asldt:rbyte2a           ; b/ no, don't try to send
         ldb   nrb:xbuflk,x            ; anybody trying to ship data ?
         bne   asldt:rbyte2a           ; b/ yes, don't try to send right now
         ldb   nrb:remotehasmore,x     ; no, does remote node have more data?
         beq   asldt:rbyte2a           ; b/ no, don't bother sending message
         psha                          ; save byte we fetched
         ldx   #asldi:desirexmitnow    ; YES, try to send right NOW
         jsr   sdos+sdos:startio       ; (must not destroy scratchpad)
         pula                         ; restore byte fetched from ring
         page
asldt:rbyte2a ; finish updating ring pointers, etc.
         inc   nrb:ringinfdter+1,x     ; (6+1~) at end of ring ?
         bne   asldt:rbyte3            ; (3~) b/ no, go store updated pointer
         inc   nrb:ringinfdter,x       ; propogate carry
         bne   asldt:rbyte3            ; b/ not at end of ring
         ldab  nrb:ringinlen,x         ; reset data bytes to end...
         stab  nrb:ringinfdter,x       ; to -SizeOfRing
         ldab  nrb:ringinlen+1,x
         stab  nrb:ringinfdter+1,x
         ldab  nrb:ringinbase+1,x      ; move pointer to front of ring
         stab  nrb:ringinfetch+1,x
         ldab  nrb:ringinbase,x
         stab  nrb:ringinfetch,x
         okrts

asldt:rbyte3 ; not at end of ring, update NRB:RINGINFETCH
         if    m6809
         stu   nrb:ringinfetch,x       ; (5+1~) store updated fetch pointer
         else  (m6800!m6801)
         incd  nrb:ringinfetch,x
         fin
         okrts                         ; (3+5~)
         page
asldt:gblkonebyteloop ; loop here to copy a few bytes that we MUST get soon
         if    m6800!m6801!m6811
         pshb                          ; save remaining count
         pshx                          ; save pointer to next byte to store
         ldx   nrbpointer              ; set (X) properly
         bsr   asldt:gbyte             ; recieve 1 byte, die if doesn't come
         bcs   asldt:rblk.erred3       ; b/ receive failed, go handle error
         pulx                          ; restore destination pointer
         pulb                          ; restore count of 1 or more
         sta   0,x                     ; store byte into destination
         inx                           ; advance destination pointer
         else  (m6809)
         pshs  b,x                     ; save count and destination pointer
         ldx   nrbpointer              ; set (X) properly
         bsr   asldt:gbyte             ; recieve 1 byte, die if doesn't come
         bcs   asldt:rblk.erred3       ; b/ receive failed, go handle error
         puls  b,x                     ; restore count and destination
         sta   ,x+                     ; store byte and advance pointer
         fin
         decb                          ; down count # bytes left to get
         bne   asldt:gblkonebyteloop   ; b/ more bytes to fetch
         bra   asldt:rblkdone          ; all bytes fetched

         if    showroughedges
? ; listener task does not know that data is required...
; but socket manager knows !!!
; listener task also knows...because after reading the start of
; a message part, it is obvious that the rest of it must come "soon"

? ; would it be useful for other node to find out that we need data ??
         fin
         page
asldt:gblk ; identical to ASLDT:RBLK, but errors if data does
; not arrive "soon" (ASLDT:RBLK will wait forever for data)
; Sets NRB:DATAREQUIREDFLAG to force continual inquiries of remote node for data
; This ensures that if remote node does not return data after a certain
; number of tries, that a failure will be triggered
; This prevents a data request from being sent to a remote node,
; being ignored (no data ready), and then having this node go back
; to sleep forever.
         ldd   sdos+sdos:clock+1       fetch clock time stamp
         if    m6800
         cmpd  sdos+sdos:clock+1       did we get time stamp splinter-free?
         bne   asldt:gblk              b/ no, get it again (loops once max)
         fin
         stx   tempx                   save for a moment
         ldx   nrbpointer              mark NRB...
         std   nrb:datarequiredtimestamp,x record time when data first required
         ; We will check time stamp each time a message is received.
         ; If message arrival time is too far beyond time stamp,
         ; and no data received yet, then we will declare remote node too slow.
         ; We will get messages because when NRB:DATAREQUIREDFLAG is set,
         ; we will never enter NODESTATE:CONVERSEI, and thus will exchange
         ; messages continuously until the remote node gives us what we want.
         inc    nrb:datarequiredflag,x  record that data IS required
         ldx   tempx
;        bra   asldt:rblk              now do ASLDT:RBLK
         page
asldt:rblk ; called by NRB Listener Task...
;        to receive data block to buffer at (X) for (D) bytes
;        the variable NRBPOINTER specifies the correct NRB
;        task is put to sleep until specified number of bytes arrives
;        does efficient block transfer from input ring if space is available
;        exits with carry reset if no errors:
;              (D):=initial(X)+initial(D), and (X)=NRBpointer
;        otherwise exits with carry set and error code in (X)
         cmpd  #3                      min block transfer req'd for win
         bhs   asldt:rblk.optimized    b/ faster to blk-move than move one-at-a-time
         tstb                          any data at all to move ?
         beq   asldt:rblkdone          b/ no, just exit
asldt:rblkonebyteloop ; loop to copy just a few bytes
         if    m6800!m6801!m6811
         pshb                          ; save remaining count
         pshx                          ; save pointer to next byte to store
         ldx   nrbpointer              ; set (X) properly
         bsr   asldt:rbyte             ; receive 1 byte
         bcs   asldt:rblk.erred3       ; b/ receive failed, go handle error
         pulx                          ; restore destination pointer
         pulb                          ; restore count of 1 or more
         sta   0,x                     ; store byte into destination
         inx                           ; advance destination pointer
         else  (m6809)
         pshs  b,x                     ; save count and destination pointer
         ldx   nrbpointer              ; set (X) properly
         bsr   asldt:rbyte             ; recieve 1 byte
         bcs   asldt:rblk.erred3       ; b/ receive failed, go handle error
         puls  b,x                     ; restore count and destination
         sta   ,x+                     ; store byte and advance pointer
         fin
         decb                          ; down count # bytes left to get
         bne   asldt:rblkonebyteloop   ; b/ more bytes to fetch
         page
asldt:rblkdone ; receive completed successfully, (X) points past last byte rcvd
         txd                           move advanced pointer back to (D)
         ldx   nrbpointer              set (X) up conveniently for caller
         clr   nrb:datarequiredflag,x  reset "we must have data soon" flag
         okrts

asldt:rblk.erred3 ; receive failed
         leas  3,s                     remove buffer pointer, count from stack
         bsr   asldt:rblkdone          release "must have data soon" flag
         jmp   sdos+sdos:errored       and then propogate the error

asldt:rblk.1a ; remote node died while we are trying to send to it
         leas  4,s                     pop buffer pointer, count from stack
         jmp   asldt:recvwaitdied      clear "data req'd" then signal error
         page
asldt:rblk.waitdata ; no data remains in input ring
         if    m6800!m6801
         ldd   tempx                   save destination buffer address
         pshd
         else  (m6809)
         pshx                          save pointer to buffer
         fin
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirercv        tell interrupt level we want data!
         jsr   sdos+sdos:startio       (high overhead here makes no difference)
         ldd   #asldw:rbytewaitdatacheck wait for data available
         jsr   sdos+sdos:waitcond      assert: cannot take error exit
         ldx   nrbpointer
         ldaa  nrb:state,x             did remote node die ?
         beq   asldt:rblk.1a           b/ yes
         pulx                          restore buffer pointer
         puld                          and buffer count
;        bra   asldt:rblk.optimized    try to move bytes quickly again
         page
asldt:rblk.optimized ; try to read a chunk of data into buffer
; An optimization here is to block transfer chunks of text from
; the input ring buffer, instead of moving them one at a time.
; This is accomplished by computing min(RingData,DistToRingEnd,ByteCount)
; and block-moving that many characters to specified input buffer.
; Savings would be about 10% of CPU at 9600 baud
         if    m6800!m6801
         stx   tempx                   set destination buffer address
         else  (m6809)
         tfr   x,y                     set destination buffer address
         fin
         ldx   nrbpointer              so we can inspect ring buffer stuff
         pshd                          save number of bytes left to move
         addd  nrb:ringinfdter,x       more than distance to end of ring ?
         ; since only task level fiddles with nrb:ringinfdter,
         ; there can be no timing splinters when inspecting both halves
         bcs   asldt:rblk.2a           b/ no, use original value
         clra                          yes, use dist to end ring as limit
         clrb                          cheap way to do "ldd #0"
         subd  nrb:ringinfdter,x       get abs value of dist to end ring
         bra   asldt:rblk.2

asldt:rblk.2a ; number of bytes desired is less than distance to end of ring
         if    m6800!m6801
         tsx                           get desired transfer count again
         ldd   0,x
         ldx   nrbpointer              and restore (X) to point to NRB
         else  (m6809)
         ldd   0,s                     get desired transfer count again
         fin
asldt:rblk.2 ; (D) has min(desiredcount,disttoendring)
         ; since interrupt level fiddles with nrb:ringindata,
         ; it is not safe to inspect nrb:ringindata w/o disabling interrupts
         if    m6800!m6801
         sei
         else  (m6809)
         orcc  #%01010000
         fin
         cmpd  nrb:ringindata,x        more than available data ?
         bls   asldt:rblk.3            b/ no (??? does test work for 00 & 09?)
         ldd   nrb:ringindata,x        yes, limit xfer to data available
asldt:rblk.3 ; now (D) has number of bytes save to move to ring (can be zero!)
         if    m6800!m6801
         cli                           re-enable interrupt system
         else  (m6809)
         andcc #\%01010000
         fin
         tstd                          any data in ring ?
         beq   asldt:rblk.waitdata     b/ no, go wait for data to arrive
         pshd                          save # of bytes to block move
         if    m6809
         ldx   nrb:ringinfetch,x       where to move data from
         jsr   sdos+sdos:blockmove     go move bytes!
; now (X)=updated source, (Y)=updated destination
         pshs  y                       save updated destination pointer
         tfr   x,u                     save updated source pointer
         ldx   nrbpointer
         stx   nrb:ringinfetch,x
         else  (m6800!m6801)
         ldx   nrb:ringinfetch,x       where to move data from
         jsr   sdos+sdos:blockmove     go move bytes!
; now (X)=updated source, (TEMPX)=updated destination
         ldd   tempx                   save updated destination pointer
         pshd
         stx   tempx                   save updated source pointer
         ldx   nrbpointer
         ldd   tempx
         std   nrb:ringinfetch,x
         fin
         puld                          get moved byte count
         std   tempx                   and save for updates
         if    m6800!m6801
         sei                           prevent interrupt splinters
         else  (m6809)
         orcc  #%01010000              prevent interrupt splinters
         fin
         ldd   nrb:ringinroom,x        tell interrupt level more room is ready
         addd  tempx                   (add transferred count)
         std   nrb:ringinroom,x
         ldd   nrb:ringindata,x        shrink remaining data in buffer
         subd  tempx
         std   nrb:ringindata,x
         if    m6800!m6801
         cli
         else  (m6809)
         andcc #\%01010000             allow interrupts again
         fin
;        We have just made some room.  We should tell the interrupt
;        level code so it can send an ACK now rather than wait for
;        SOON. We do this when all the following conditions hold:
;        1) The input ring is completely empty (max room for data)
;        2) We are expected to send next
;        3) No data is being sent (xmit buffer is not locked)
;        4) Remote node has more data (he sent ACKSOON or we lost some)
;        5) We have enough room for an efficient message (true when ring empty)
         tstd                          is the input ring completely empty?
         bne   asldt:rblk.3a           b/ no (fast path)
         lda   nrb:state,x             inspect node state
         cmpa  #nodestate:converses    are we expected to send next ?
         bne   asldt:rblk.3a           b/ no, don't try to send
         lda   nrb:xbuflk,x            anybody trying to ship data ?
         bne   asldt:rblk.3a           b/ yes, don't try to send now
         lda   nrb:remotehasmore,x     no, does remote node have more data ?
         beq   asldt:rblk.3a           b/ no
         ldx   #asldi:desirexmitnow    YES, try to send RIGHT NOW.
         jsr   sdos+sdos:startio       (must not destroy scratchpad)
         ldx   nrbpointer              restore (X)
asldt:rblk.3a ; finish updating ring pointers, etc.
         ldd   nrb:ringinfdter,x       decrease number of bytes to buffer top
         addd  tempx
         bne   asldt:rblk.4            b/ buffer top not reached yet
         ; assert: if (A) is zero, so is (B)!
         ; ran off end of ring, put pointers back to front of ring
         ldd   nrb:ringinbase,x
         std   nrb:ringinfetch,x
         ldd   nrb:ringinlen,x
asldt:rblk.4
         std   nrb:ringinfdter,x
         pulx                          restore adjusted destination pointer
         puld                          and original byte count
         subd  tempx                   = remaining count
         bned  asldt:rblk              go try to move another big chunk
         jmp   asldt:rblkdone          clear NRB:DATAREQUIRED and exit

; Performance analysis for 6809:
;    8 machine instructions to execute body of RBLKONEBYTELOOP
;        + 83~/byte moved via ASLD:RBYTE
;    44 machine instructions to execute body of RBLKOPTIMIZED
;        + 5~/byte moved in SDOS:BLOCKMOVE
;    One should use RBLKONEBYTELOOP for small transfers because of lower
;    overhead, and RBLKOPTIMIZED because of low cost of block moves.
;    The crossover point occurs when (8*5+83)x >= 44*5+5x, i.e, when x >= 2
;    Since SDOS:BLOCKMOVE has some overhead for warming up, this computation
;    is biased slightly in favor of RBLKOPTIMIZED, so we offset that
;    bias by making the conservative choice of using RBLKOPTIMIZED for
;    all transfers of length 3 or more.
         page
asldt:olnk ; called by SM to open link connection for brand-new NRB
; assert: SM disabled interrupts, and assigned an NRB for use to the proper
; NIT slot, and has passed control here, all with with INTERRUPTS DISABLED
         ldd   NRBpointer              which NRB to connect
         ldx   #asldi:olnk             which routine to trigger
         jmp   sdos+sdos:startio

asldi:olnk ; STARTIO to here to open a link using an NRB
         std   asldv:interruptNRB      remember NRB involved
         if    m6800!m6801!m6811
         ldx   asldv:interruptNRB      make (X) point to NRB
         else  (m6809)
         tdx
         fin
         bsr   asldi:resetNRB          reset NRB
         jmp   asldi:addNRBtoxmitqnow  put NRB into xmitq for immediate transmission
         page
asldi:resetNRB ; called to initialize an NRB
; interrupts must be disabled here, and NRB must not be in use by tasks
; (otherwise a message might arrive in the middle of this, or someone
; might update the NRB before it is truly ready).
; called immediately after an NRB has been allocated to a link
; NRB:NETID, NRB:NODEID, NRB:LINKDRIVER, NRB:PLCB already initialized
         ldx   asldv:interruptNRB
         ; assert: nrb:ringxxx pointers and counters are statically initz'd
         bsr   asldi:clearin           empty the input ring
         bsr   asldi:clearout          empty the output ring
         clra                          make a 16 bit zero
         clrb
         staa  nrb:xbuflk,x            clear transmit buffer lock
         staa  nrb:state,x             make this node DEAD
         std   nrb:epitaph,x           and reset the cause of death
         staa  nrb:xbuflk,x            leave transmit buffer unlocked
         std   nrb:xvpos,x             zero the transmit virtual position
         std   nrb:maxackrvpos,x       zap the maximum valid ack RVPOS
         std   nrb:rvpos,x             and the receive virtual position
         std   nrb:rcvdrdy,x           zero the last received ready count
         staa  nrb:datarequiredflag,x  flag "data not needed"
         staa  nrb:goof,x              no screwups are current
         if    showroughedges
? ; reset ECBs in NRB ?
         fin
         inca                          be unforgiving about retries
         staa  nrb:retry,x             (only 1 shot allowed on first try)
         ldaa  #nodestate:initiating   set node state so that inserting NRB...
         staa  nrb:state,x             in XMITQ will establish contact with remote node
         if    m6800!m6801!m6811
         ldd   asldv:interruptNRB      form location of NRB:RECVTOB
         addd  #nrb:recvtob
         std   asldv:tempx
         ldx   asldv:tempx
         else  (m6809)
         leax  nrb:recvtob,x
         fin
         ldd   asldv:interruptNRB      set TOB:PARAMETER to point to NRB
         jsr   SDOSRT+rt:itmo          initialize timeout block
         ldx   asldv:interruptNRB      set (X) for following call
         ldd   #asldi:putnrbinxmitqwhentimeout set where to go when delay done
         std   nrb:recvtob+tob:routine,x
         jsr   asldi:markNRBasnotinXMITQ
         okrts
asldt:clnk ; called by SM to nicely close link thru NRB @ NRBPOINTER
; assert: nrb:xbuflk is set here, so no other task will try to send data.
;  This routine sets a flag in the NRB which forces the active transmission
;  of "WantToQuit" messages
;  Driver will send all remaining data bytes, finally discover that
;  "WantToQuit" is required, and will start that process.  When a
;  "Quit", "AgreeToQuit" is received, the driver will change the NRB state
;  to DEAD.  The task level routine must wait for the NRB to die before
;  it can do anything to the NRB. Since the NRB is locked, no other
;  task can get its hands on the output ring; since it is dead, the
;  Listener task will simply ignore all arriving data.  Something
;  needs to enforce these rules.
         ldx    NRBpointer             which NRB is involved
         ldx    nrb:plcb,x             fetch pointer to Physical Link Control Block
         lda    plcbas:state,x         is interrupt routine busy ?
         beq    asldt:clnk0            KLUGE KLUGE Can't stay busy forever
         txd                           so wakeup routine knows which PLCB
         ldx    #asldw:wakeinterruptroutinenotbusy set up to wait for int routine not busy
         jsr    sdos+sdos:waitcond
asldt:clnk0 ; try to close NRB
         ldd    nrbpointer             which NRB to close
         ldx    #asldi:desirequit      which NRB needs to die
         jsr    sdos+sdos:startio      "I wish for NRB to die soon"
         ldx    nrbpointer             restore pointer to NRB
asldt:clnk1 ; loop waiting for node to die
         lda    nrb:state,x            see if NRB died
         cmpa   #nodestate:dead        ...?
; assert: listener task cannot notice this NRB has died...
; until NRB:XBUFLK has been released.  So once the NRB has been killed,
; it stays dead until control passed back here, then back to caller
; who can clear the buffer lock.  THEN the listener task will notice.
         beq    asldt:clnkokrts        b/ NRB has died, we are done
; How do we prevent timing splinter when node transits to dead here ?
; Easy.  We require that NRB:CLOSELINK
; stay set while node is dead until listener task succeeds in resetting
; NRB.  Since it can't notice NRB is dead while XBUFLK is set, it can't
; reset NRB (or NRB:CLOSELINK) while we are looping around here. Therefore
; there is no possibility of a timing splinter.
         lda    nrb:closelink,x        did we succeed in forcing a close ?
         bne    asldt:clnk1            b/ yes, keep waiting for dead node
         beq    asldt:clnk             b/ no, go try forcing a close again

asldt:clnkokrts ; NRB has been closed successfully
         okrts
         page
asldt:blnk ; called by SM to abruptly close link thru NRB @ NRBPOINTER
; assert: nrb:xbuflk is set here, so no other task will try to send data.
         ldx    NRBpointer             which NRB is involved
         ldx    nrb:plcb,x             fetch pointer to Physical Link Control Block
         lda    plcbas:state,x         is interrupt routine busy ?
         beq    asldt:blnk1            b/ no, try to kill the NRB
         txd                           so wakeup routine knows which PLCB
         ldx    #asldw:wakeinterruptroutinenotbusy set up to wait for int routine not busy
         jsr    sdos+sdos:waitcond
asldt:blnk1 ; try to kill the NRB
         ldd    nrbpointer             which NRB needs to die
         ldx    #asldi:desirekill      who carries out the contract
         jsr    sdos+sdos:startio      kill the NRB!
         ldx    nrbpointer             make sure he died
         ldaa   nrb:state,x            (perhaps there was timing splinter)
         cmpa   #nodestate:dead        (and we tried to kill him while busy)
         bne    asldt:blnk             b/ lousy assassin, try to kill it again
; assert: listener task cannot notice this NRB has died...
; until NRB:XBUFLK has been released.  So once the NRB has been killed,
; it stays dead until control passes back here, then back to caller
; who can clear the buffer lock.  THEN the listener task will notice.
         okrts                         the NRB is (reliably) dead!

asldw:wakeinterruptroutinenotbusy ; Task wake up subroutine...
; to test for ASLD interrupt routine not active
         ldaa   plcbas:state,x         is int routine committed to an action ?
         tpa
         anda   #4                     retain "Z" bit ("0" --> "NO")
         rts
         page
asldt:thnt ; called by SM to signal "Transmit Hint" for NRB @ NRBPOINTER
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirexmitnow    tell interrupt level we want to send!
         jmp   sdos+sdos:startio       high overhead not a problem here

         if    showroughedges
? ; Z80 ASLDT:SENDWAITROOM and ...RECVWAITROOM routines have been left out. OK?

         fin
         page
asldw:sbytewaitroomcheck ; task wake-up to check for output buffer space available
         ldaa  nrb:state,x             has node died ?
         beq   asldw:sbytewaitroomcheck1 b/ yes, wake the task
         ldaa  nrb:ringoutroom,x       see if output space is available
         ; acknowledgements from remote node free up chunks of ring buffer
         ; when they arrive, so if we have to wait for room, when it finally
         ; comes, there should be a chunk (much more than 1 byte) of room
         ; available.   So we do NOT need a "thresholding" value to prevent
         ; us from continually discovering only 1 byte is available, causing
         ; a context switch, using the byte, and then causing another context
         ; switch (which is a lot of overhead as is the case with conventional
         ; serial drivers, like the VT driver).
         oraa  nrb:ringoutroom+1,x
asldw:sbytewaitroomcheckrts
         rts

asldw:sbytewaitroomcheck1 ; node has died
         inca                          wake the task
         rts

asldt:sbytewaitroom ; must wait for space in ring
         if    m6800!m6801!m6811
         cli                           ; (2~) let the sun shine again
         else  (m6809)
         andcc #\%01010000             ; (3~) allow FIRQ and IRQ again
         fin
         psha                          save the byte momentarily
         ; force transmission, hoping that this will acheive an
         ; earlier data ack, which in turn will make some buffer space
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirexmitnow    tell interrupt level we want to send!
         jsr   sdos+sdos:startio       high overhead not a problem here
         ldd   #asldw:sbytewaitroomcheck set up to wait for free space
         jsr   sdos+sdos:waitcond      assert: cannot take error exit
         ldx   nrbpointer
         pula
         ldab  nrb:state,x             has node died ?
         bne   asldt:sbyte             b/ no, put byte into buffer
         jmp   asldt:nodedied          yes, notify calling task
         page
asldt:sbyte ; called by SM to send a single byte in (A) via NRB @ NRBPOINTER
;   nrb:xbuflk must be set to ensure atomic transactions by Socket manager
;   stores (a) in the output buffer; blocks the task if no room available
;   forces "transmit soon" to ensure this byte gets to destination
;   returns with carry reset and (X) preserved if all was well,
;   else returns with carry set and error code in (X).
;   Bytes placed into the output ring buffer are removed by ASLD:ILOUTPUT.
;   Note that this routine is in critical output path, keep lean as possible!
;   (This requirement is mitigated somewhat by ASLD:SBLK,
;   which inserts long streams into output buffer without waste of
;   individual buffer updates on each byte).
;   We use fastest possible code in RBYTE and SBYTE for performance reasons
;   Performance analysis for 6809: 112~/byte sent
         ldx   NRBpointer              ; (5~) find out which NRB is involved
         if    m6800!m6801!m6811
         sei                           ; (2~) make it safe to update dbl bytes
         else  (m6809)
         orcc  #%01010000              ; (3~) mask out FIRQ and IRQ
         fin
         ldab  nrb:ringoutroom+1,x     ; (4+1~) any room left in ring ?
         bne   asldt:sbyte1            ; (3~) b/ yes, so put byte into ring (fast path)
         ldab  nrb:ringoutroom,x       ; (4+1~) any space in ring buffer ?
         beq   asldt:sbytewaitroom     ; (3~) b/ no, go wait for space
         dec   nrb:ringoutroom,x       ; (6+1~) decrement upper half
asldt:sbyte1 ; there is room in the ring
         dec   nrb:ringoutroom+1,x     ; (6+1~) decrement remaining room
         inc   nrb:ringoutdata+1,x     ; (4+1~) signal that data is available
         bne   asldt:sbyte2            ; (3~) because it will be in a moment
         inc   nrb:ringoutdata,x       ; (6+1~) propogate carry bit
asldt:sbyte2
         page
         if    m6809
         ldu   nrb:ringoutstore,x      ; (5+1~) store the byte in the ring
         sta   ,u+                     ; (4+2~)
         andcc #\%01010000             ; (3~) allow FIRQ and IRQ again
         else  m6800!m6801
         ldx   nrb:ringoutstore,x
         staa  0,x
         cli                           ; (2~) allow interrupts as soon as safe
         ldx   nrbpointer
         fin
         inc   nrb:ringoutsdter+1,x    ; (6+1~) hit end of ring ?
         bne   asldt:sbyte3            ; (3~) b/ no, fast path
         inc   nrb:ringoutsdter,x      ; (6+1~) ...?
         bne   asldt:sbyte3            ; (3~) b/ no
         ; end of ring hit, reset pointer [low probability (<1%) path]
         ldd   nrb:ringoutlen,x        ; (5+1~) holds -SizeOfRing
         std   nrb:ringoutsdter,x      ; (5+1~)
         ldd   nrb:ringoutbase,x       ; (5+1~)
         std   nrb:ringoutstore,x      ; (5+1~)
         bra   asldt:sbyte4            ; rejoin main code

asldt:sbyte3 ; not at end of ring yet
         if    m6809
         stu   nrb:ringoutstore,x      ; (5+1~) store updated ring pointer
         else  (m6800!m6801!m6811)
         incd  nrb:ringoutstore,x      ; update ring pointer
         fin   m6809
asldt:sbyte4 ; byte now safely stashed in output ring
         ; now ensure that a transmission will occur "soon"
         ; if node is in NODESTATE:CONVERSEI (i.e., idle --> not in queue)
         ; then put in XMITQ with short delay; the delay gives more time
         ; for data to be placed in the transmit buffer
         ldaa  nrb:state,x             ; (4+1~) does node expect to receive next ?
         cmpa  #nodestate:conversei    ; (2~) (bne below is frequent, fast path)
         bne   asldt:sbyte5            ; (3~) b/ no, it expects to do something else
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirexmitsoon   tell interrupt level of intentions
         jsr   sdos+sdos:startio
         ldx   nrbpointer              restore pointer to NRB
asldt:sbyte5
         page
         ldab  nrb:ringoutroom+1,x     ; (4+1~) any room left in output ring ?
         bne   asldt:sbyte.okrts       ; (3~) b/ yes, exit (fast path)
         ldab  nrb:ringoutroom,x       ...?
         bne   asldt:sbyte.okrts       b/ no
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirexmitnow    tell interrupt level...
         jsr   sdos+sdos:startio       that NRB is ready to send
         ldx   nrbpointer              set (X) properly for exit
asldt:sbyte.okrts
         ldd   sdos+sdos:clock+1       (6~) fetch time stamp
         if    m6800
         cmpd  sdos+sdos:clock+1       trip over timing splinter ?
         bned  asldt:sbyte.okrts       b/ yes, do it again
         fin
         std   nrb:datasenttimestamp,x (5~) record when data was last sent
         okrts                         (3+5~)
         page
asldt:sblk ; called by SM to send a block of data at (X) for (D) bytes
;        the variable NRBPOINTER specifies the correct NRB
;        does efficient block transfer to output ring if space is available
;        normal return (carry reset):
;               (D):= entry(X)+entry(D), and (X) has NRBpointer

         cmpd  #3                      min block transfer req'd for win
         bhs   asldt:sblk.optimized    b/ block move more efficient than one-at-a-time
         tstb                          any bytes to move at all ?
         beq   asldt:sblkokrts         b/ fewer, must be zero so we're done
asldt:sblkoneloop ; send one byte, then see what else we need to do
; 6809 performance: loop takes 40~ + time for asldt:sbyte (93~) = 133~/byte
         ; assert: count is one or greater
         if    m6809
         ldaa  ,x+                     (4+2~) fetch the byte
         pshs  x,b                     (4+3~) save advanced buffer pointer and count
         ldx   nrbpointer              (5~) set (X) properly
         jsr   asldt:sbyte             (7~) go send the byte
         bcs   asldt:sblk.erred3       (3~) b/ errored trying to send byte
         puls  x,b                     (4+3~) restore (X) and count
         else  (m6800!m6801!m6811)
         pshx                          save buffer pointer
         ldaa  ,x                      fetch the byte
         pshb                          save remaining count
         ldx   nrbpointer              set (X) properly
         jsr   asldt:sbyte             go send the byte
         bcs   asldt:sblk.erred3       b/ errored trying to send byte
         pulb                          recover remaining count
         pulx                          recover buffer pointer
         inx                           advance the pointer
         fin
         decb                          (2~) down count the count
         bne   asldt:sblkoneloop       (3~) b/ not exhausted, do it again
         page
asldt:sblkokrts ; send completed successfully
         stx   tempx                   save final store pointer
         ldx   nrbpointer              set (X) properly
asldt:sblkokrts1
         ldd   sdos+sdos:clock+1       (6~) fetch time stamp
         if    m6800
         cmpd  sdos+sdos:clock+1       trip over timing splinter ?
         bned  asldt:sblkokrts1        b/ yes, do it again
         fin
         std   nrb:datasenttimestamp,x (5~) record when data was last sent
         ldd   tempx                   fetch store pointer for exit
         okrts
         page
asldt:sblk.erred3 ; send failed
         leas  3,s                     remove buffer pointer, count from stack
         jmp   sdos+sdos:errored       and then propogate the error

asldt:sblk.1a ; remote node died while we are trying to send to it
         leas  4,s                     pop buffer pointer, count from stack
         jmp   asldt:nodedied           go fetch error code and propogate

asldt:sblk.waitroom ; no room left in output ring
         ldd   tempx+2                 save source buffer address
         pshd
         ldd   nrbpointer              which NRB needs attention
         ; force transmission, hoping that this will acheive an
         ; earlier data ack, which in turn will free some buffer space
         ldx   #asldi:desirexmitnow    tell interrupt level we want to send!
         jsr   sdos+sdos:startio       high overhead not a problem here
         ldd   #asldw:sbytewaitroomcheck wait for room available
         jsr   sdos+sdos:waitcond      assert: cannot take error exit
         ; when we wake up, there should be a lot of available buffer space
         ldx   nrbpointer
         ldaa  nrb:state,x             did remote node die ?
         beq   asldt:sblk.1a           b/ yes
         pulx                          restore buffer pointer
         puld                          and buffer count
;        bra   asldt:sblk.optimized    try to send optimized again
         page
asldt:sblk.optimized ; try to write a chunk of characters into buffer
;   An optimization here is to block transfer chunks of text to
;   the output ring buffer, instead of moving them one at a time.
;   This is accomplished by computing min(RingSpace,DistToRingEnd,ByteCount)
;   and block-moving that many characters to output ring.
;   Savings would be about 10% of CPU at 9600 baud
         stx   tempx+2                 save source pointer
         ldx   nrbpointer              so we can fool with ring stuff
         pshd                          save number of bytes left to move
         addd  nrb:ringoutsdter,x      more than distance to end ring ?
         ; since only task level fiddles with nrb:ringoutsdter,
         ; there can be no timing splinters when inspecting both halves
         bcs   asldt:sblk.2a           b/ no, use specified count
         clra                          no, use dist to end ring as limit
         clrb                          cheap way to "ldd #0"
         subd  nrb:ringoutsdter,x      get abs value of dist to end ring
         bra   asldt:sblk.2            b/ no

asldt:sblk.2a ; dist to end ring is greater than number bytes to move
         if    m6800!m6801!m6811
         tsx                           use specified count
         ldd   0,x
         ldx   nrbpointer              restore pointer to NRB
         else  (m6809)
         ldd   0,s                     use specified count
         fin
asldt:sblk.2 ; (D) contains min(bytestosend,disttoendring)
         if    m6800!m6801!m6811
         sei                           ; (2~) interrupt level diddles RINGOUTROOM
         else  (m6809)
         orcc  #%01010000              ; (3~) mask out FIRQ and IRQ
         fin
         cmpd  nrb:ringoutroom,x       more than room available for task ?
         bls   asldt:sblk.3            b/ no
         ldd   nrb:ringoutroom,x       yes, limit to task space available
asldt:sblk.3 ; now (D) has number of bytes save to move to ring (can be zero!)
         if    m6800!m6801!m6811
         cli                           ; (2~) reallow interrupts
         else  (m6809)
         andcc #\%01010000             ; (3~) reallow FIRQ and IRQ
         fin
         tstd                          any room in ring ?
         beq   asldt:sblk.waitroom     b/ no, go wait for room
         pshd                          save # of bytes to block move
         if    m6809
         ldy   nrb:ringoutstore,x      where to move it to
         ldx   tempx+2                 where to move it from
         jsr   sdos+sdos:blockmove     go move bytes!
         puld                          get moved byte count
         std   tempx                   and save for updates
         pshx                          save updated source pointer
         ldx   nrbpointer              now update all the pointers, counters
         sty   nrb:ringoutstore,x      update the output ring pointer
         else  (m6800!m6801!m6811)
         ldx   nrb:ringoutstore,x      where to
         stx   tempx                   set up destination
         ldx   tempx+2                 restore the source
         jsr   sdos+sdos:blockmove     go move bytes!
         ldd   tempx                   fetch next output ring location
         stx   tempx+2                 save updated source pointer
         ldx   nrbpointer              now update all the pointers, counters
         std   nrb:ringoutstore,x      update the output ring pointer
         puld                          get moved byte count
         std   tempx                   and save for updates
         ldd   tempx+2                 fetch updated source pointer
         pshd                          save updated source pointer
         fin
         if    m6800!m6801!m6811
         sei                           prevent interrupt splinters
         else  (m6809)
         orcc  #%01010000              prevent interrupt splinters
         fin
         ldd   nrb:ringoutdata,x       tell interrupt level that data is ready
         addd  tempx                   add transferred count
         std   nrb:ringoutdata,x
         ldd   nrb:ringoutroom,x       shrink available buffer room
         subd  tempx
         std   nrb:ringoutroom,x
         if    m6800!m6801!m6811
         cli
         else  (m6809)
         andcc #\%01010000             allow interrupts again
         fin
         ; now ensure that a transmission will occur "soon"
         ; if node is in NODESTATE:CONVERSEI (idle --> not in queue)
         ; then put in XMITQ with short delay; the delay gives more time
         ; for data to be placed in the transmit buffer
         ldaa  nrb:state,x             does node expect to receive next ?
         cmpa  #nodestate:conversei    (bne below is frequent, fast path)
         bne   asldt:sblk.5            b/ no, it expects to do something else
         ; STARTIO must not destroy TEMPX
         ldd   nrbpointer              which NRB needs attention
         ldx   #asldi:desirexmitsoon   tell interrupt level of intentions
         jsr   sdos+sdos:startio
         ldx   nrbpointer              restore pointer to NRB
asldt:sblk.5 ; see if tim to transmit is NOW
         ldb   nrb:ringoutroom+1,x     any output room left ?
         bne   asldt:sblk.6            b/ yes, go finish pointer updates
         ldb   nrb:ringoutroom,x       any output room left ?
         bne   asldt:sblk.6            b/ yes, go finish pointer updates
         ldx   #asldi:desirexmitnow    tell interrupt routines...
         ; STARTIO must not destroy TEMPX
         jsr   sdos+sdos:startio       there is no point in waiting longer
         ldx   nrbpointer              restore pointer to NRB
asldt:sblk.6 ; finish updating xmit buffer pointers
         ldd   nrb:ringoutsdter,x      decrease number of bytes to buffer top
         addd  tempx
         bne   asldt:sblk.4            b/ still some distance to end ring
         ; note: top half can be zero ONLY when entire sum is zero
         ; ran off end of ring, put pointers back to front of ring
         ldd   nrb:ringoutbase,x
         std   nrb:ringoutstore,x
         ldd   nrb:ringoutlen,x
asldt:sblk.4
         std   nrb:ringoutsdter,x
         pulx                          restore adjusted source pointer
         puld                          and original byte count
         subd  tempx                   = remaining count
         bned  asldt:sblk              go try to move another big chunk
         bra   asldt:sblkokrts         done, do exit
         page
asldt:stats ; called by SM to get link error statistics
; (X) on entry points to buffer address
         jsr   sdos+sdos:checkrdlen    check reply buffer length
         #plcbas:statisticsend-plcbas:statisticsbegin+1 room for stats + type byte
         ldx   scblk:rdbuf,x           get pointer to reply buffer
         ldaa  #ldtype:asld            return type of link driver as 1st byte
         staa  ,x+                     advance (X) to new destination
         if    m6811!m6809
         tfr   x,y                     set destination address
         else  m6800!m6801
         stx   tempx
         fin
         ldx   NRBpointer              fetch NRB selected
         ldd   nrb:plcb,x              find pointer to PLCB
         addd  #plcbas:statisticsbegin compute source address
         tdx
         ldd   #plcbas:statisticsend-plcbas:statisticsbegin get count
         jmp   sdos+sdos:blockmove     and copy statistics to destination
         page
asldi:csma ; Test for carrier presence, return C bit set if no carrier
; On entry, PLCBAS:STATE must be non-zero, since we don't want clock
; ticks waking up NRBs and causing them to attempt transmission while
; we are in this routine, looping around with interrupts enabled.
; We enable interrupts because we loop a (relatively) long time...
; (several milliseconds), and we really don't want to miss other device
; interrupts. While interrupts are enabled, ASLD may service some
; other PLCB/NRB pair, so the values of ASLD:INTERRUPTPLCB and
; ASLDV:INTERRUPTNRB are saved on a stack, and then restored as we exit.
; We exit with input interrupts disabled, and CPU ints disabled.
; All of this makes for a rather ugly routine.
         ldx     ASLD+asld:interruptplcb so caller doesn't have to do this
         jsr     plcbasc:ildisii,x     disable input interrupt
;        jsr     plcbasc:ildisoi,x     assert: output ints are disabled here
         inc     sdos+sdos:stackswitched switch stack
         bne     asldi:csma1           b/ stack already switched
         ldx     sdos+sdos:currentask  save current stack in TCB
         sts     tcb:stack,x
         ldx     sdos+sdos:configuration switch to interrupt stack
         lds     cnfg:interruptstack,x
asldi:csma1 ; now running with interrupt stack
         ldd     asldv:interruptnrb    save interrupt NRB pointer
         pshd
         ldd     ASLD+asld:interruptplcb save pointer to PLCB desired
         pshd
         ldx     sdos+sdos:configuration enable interrupts
         jsr     cnfg:intenable,x
         ldx     0,s                   fetch PLCB address in question
         jsr     plcbasc:ilcsma,x      is ether available ?
         ; I/O package implementer codes ILCSMA to loop for a fixed
         ; time watching for data/error arrival.  If this happens, a
         ; "Nonzero" status is returned; the data is NOT acknowledged (this
         ; allows us to re-enable interrupts and have the receiver code hear
         ; the data)
         tpa                           save CC bits
         psha
         ldx     sdos+sdos:configuration disable interrupts
         jsr     cnfg:intdisable,x
         pula                          grab copy of CC before unswitching stacks
         rora                          move Z bit to C bit...
         rora                          so it doesn't get trashed during STx
         puld
         std     ASLD+asld:interruptplcb restore pointer to PLCB
         puld
         std     asldv:interruptnrb    restore interrupt NRB pointer
         dec     sdos+sdos:stackswitched unswitch stack (ASSERT: CARRY UNAFFECTED)
         bpl     asldi:csma2           b/ stack doesn't need unswitching
         ldx     sdos+sdos:currentask  switch back to user task
         lds     tcb:stack,x
asldi:csma2 ; stack is switched back
         ldx     ASLD+asld:interruptplcb so caller doesn't have to do this
         rts                           and exit with Carry set properly
         page
asldi:xgap ; transmit gap after message
; Returns Carry set if no carrier present when gap transmission is complete
; Carry reset --> carrier present --> other node trashing our transmission
;
; On entry, ASLD:STATE must be non-zero, since we don't want clock
; ticks waking up NRBs and causing them to attempt transmission while
; we are in this routine, looping around with interrupts enabled.
; We enable interrupts because we loop a (relatively) long time...
; (several milliseconds), and we really don't want to miss other device
; interrupts. While interrupts are enabled, ASLD may service some
; other PLCB/NRB pair, so the values of ASLD:INTERRUPTPLCB and
; ASLDV:INTERRUPTNRB are saved on a stack, and then restored as we exit.
; We exit with input interrupts disabled, and CPU ints disabled.
; All of this makes for a rather ugly routine.
         ldx     ASLD+asld:interruptplcb so caller doesn't have to do this
         jsr     plcbasc:ildisii,x     disable input interrupt
         jsr     plcbasc:ildisoi,x     disable output interrupt
         ldd     #asldix:ignoretdre    set up to ignore TDRE interrupts
         std     plcbas:iloutputintPC,x
         inc     sdos+sdos:stackswitched switch stack
         bne     asldi:gap1            b/ stack already switched
         ldx     sdos+sdos:currentask  save current stack in TCB
         sts     tcb:stack,x
         ldx     sdos+sdos:configuration switch to interrupt stack
         lds     cnfg:interruptstack,x
asldi:gap1 ; now running with interrupt stack
         ldd     asldv:interruptnrb    save interrupt NRB pointer
         pshd
         ldd     ASLD+asld:interruptplcb save pointer to PLCB desired
         pshd
         ldx     sdos+sdos:configuration enable interrupts
         jsr     cnfg:intenable,x
         ldx     0,s                   fetch PLCB address in question
         jsr     plcbasc:ilxgap,x      transmit gap after message
         ; ILXGAP calls ILCSMA twice.  The second time checks for carrier
         ; after we have sent our message.
         ; ILCSMA loop delay must be mean time between chars plus 2 sigma
         tpa                           save CC bits
         psha
         ldx     sdos+sdos:configuration disable interrupts
         jsr     cnfg:intdisable,x
         pula                          grab copy of CC before unswitching stacks
         rora                          move Z bit to C bit...
         rora                          so it doesn't get trashed during STx
         puld
         std     ASLD+asld:interruptplcb restore pointer to PLCB
         puld
         std     asldv:interruptnrb    restore interrupt NRB pointer
         dec     sdos+sdos:stackswitched unswitch stack
         bpl     asldi:gap2            b/ stack doesn't need unswitching
         ldx     sdos+sdos:currentask  switch back to user task
         lds     tcb:stack,x
asldi:gap2 ; stack is switched back
         ldx     ASLD+asld:interruptplcb so caller doesn't have to do this
         rts                           and exit with Carry set properly
         page    STARTIO routines
asldi:desirexmitsoon ; do STARTIO to here to force xmit soon
; note: since a STARTIO to INTDESIREXMITNOW can occur asynchronously
; w.r.t. message transmissions, it is possible (splinter!) to get here
; DURING transmission (or even reciept) of a message for this NRB.
; If NRB is in XMITQ, then it is transmitting now or it it will soon,
; so we need do nothing.  If not in XMITQ, it is either CONVERSES
; (expects to send next, soon), CONVERSER (and expecting response),
; CONVERSEI (idle and not expecting response), or some other state.
; If it is in CONVERSES state, it will send soon so we need do nothing.
; If it is in CONVERSER state, it will get a response and then transmit,
; or it will timeout and then transmit; either way, it will transmit
; (soon) so we need do nothing.
; If it is CONVERSEI (and idle), then we need merely place the NRB into
; a CONVERSES state so it will send soon.  If it is in any other state,
; it is either transmitting right now, expects to soon, or expects a
; response soon, so we need do nothing.  Whew.
        std     asldv:interruptnrb     save address of nrb
        if      m6809
        tfr     d,x                    get nrb for xmit
        else    m6800!m6801!m6811
        ldx     asldv:interruptnrb
        fin
        ldd     nrb:xmitqflink,x       is NRB in transmit queue ?
        bned    asldi:decideifxmitreqd b/ yes, we need do nothing
        ldaa    nrb:state,x            is nrb ...
        cmpa    #nodestate:conversei   idle and not expecting response ?
        bne     asldi:decideifxmitreqd b/ no, leave nrb alone
        ldaa    #nodestate:converses   set state to "want to send"
        staa    nrb:state,x            (so we don't count a TIMEDOUT error)
        ldx     nrb:plcb,x             fetch pointer to Physical Link Control Block
        ldd     plcbasc:respondtime,x  set time delay before transmission
        ; assert: plcbasc:respondtime <> 0
        jsr     asldi:putnrbinxmitq    add nrb to xmitq so he'll send soon
        bra     asldi:decideifxmitreqd see if we must transmit now
        page
asldi:desirexmitnow ; do STARTIO to here to force xmit immediately
        std     asldv:interruptnrb     save address of nrb
        if      m6809
        tfr     d,x                    get nrb for xmit
        else    m6800!m6801
        ldx     asldv:interruptnrb
        fin
        ldd     nrb:xmitqflink,x       is NRB in transmit queue ?
        bne     asldi:decideifxmitreqd b/ yes, we need do nothing
        ldaa    nrb:state,x            is nrb still connected ?
        cmpa    #nodestate:converses   (in fact, are we spos'd to send next ?)
        beq     asldi:desirexmitnow1   b/ yes, kill timer, set up to send
        cmpa    #nodestate:conversei   idle NRB ?
        bne     asldi:decideifxmitreqd b/ no, leave nrb alone
        bra     asldi:addNRBtoxmitqnow yes, cause transmission

asldi:desirexmitnow1 ; NRB is in CONVERSES state
; note: since a STARTIO to ASLDI:DESIREXMITNOW can occur asynchronously
; w.r.t. message transmissions, it is possible (splinter!) to get here
; DURING transmission of a message for some NRB (perhaps even this one).
        ldaa    nrb:xmitqflink,x       is this NRB transmitting right now ?
        bne     asldi:decideifxmitreqd b/ yes, our wish accomplished!
        bra     asldi:desirexmit0      b/ no, go try to transmit now
        page
asldi:desirercv ; do STARTIO to here if data is needed by a socket
; note: since a STARTIO to ASLDI:DESIRERCV can occur asynchronously
; w.r.t. message transmissions, it is possible (timing splinter!) to get here
; DURING transmission of a message for some NRB (perhaps even this one).
; assert: this routine can only be called by the listener task
        std     asldv:interruptnrb     save address of nrb
        if      m6809
        tfr     d,x                    get nrb for xmit
        else    m6800!m6801
        ldx     asldv:interruptnrb
        fin
        ldd     nrb:xmitqflink,x       is NRB in transmit queue ?
        bned    asldi:decideifxmitreqd b/ yes, we need do nothing
        ldaa    nrb:state,x            are we spos'd to send next ?
        ; if there is unack'd xmitted data, we will be in
        ; CONVERSES or CONVERSER state in the XMITQ
        if      showroughedges
? ; this routine needs attention. It seems that the conditions under
; which we should send when we want to receive are unclear.
; also, see ASLDIR:NORMALMSG for similar problem
        fin
        cmpa    #nodestate:converses   ...?
        beq     asldi:desirexmit0      b/ yes, so send now to get us into rcv
        cmpa    #nodestate:conversei   is NRB idle ?
        bne     asldi:decideifxmitreqd b/ no, leave node alone
        ; if there were data to send, we would be in CONVERSES state
        ; if there is unacknowledged data, we are awaiting response
        ldaa    #nodestate:converses   set state to "want to send"
        staa    nrb:state,x
; assert: nrb:datarequiredflag is zero here (can only be nonzero while
; listener task is executing ...gbyt or ...gblk, and this routine
; can only be called by the listener task.
        if      0
        ldaa    nrb:ringoutdata,x      any data to send ?
        oraa    nrb:ringoutdata+1,x
        bne     asldi:desirexmit0      b/ yes, set up to send
        ldd     nrb:lastxmitcnt,x      did we transmit any data last time?
        beqd    asldi:rti              b/ no, no reason to send anything
        fin
asldi:desirexmit0 ; we are spos'd to send next
        jsr     asldi:removenrbfromxmitq so we don't insert it twice
asldi:addNRBtoxmitqnow ; add NRB to XMITQ right now!
        clra                           set time delay = 0
        clrb
        jsr     asldi:putnrbinxmitq    add this nrb to xmitq
;       bra     asldi:decideifxmitreqd go see if now is the time to transmit
         page   Interrupt Exit routines
; This driver keeps two logical queues of NRBs:
; XMITQ: those which are ready to transmit NOW (in order they became ready)
; WAITQ: those which are waiting for a response from their remote node
; The XMITQ is a real queue; WAITQ is "invisible", all NRBs on it
; really just have NRB$RECVTOB activated (so the queue is actually kept
; by SDOSRT).  Timeout blocks expiring cause NRBs to move from WAITQ to
; XMITQ.

; We handle collision resolution by simply setting the desired delay into
; the XMITTOB owned by the PLCB rather than a particular NRB.

asldi:decideifxmitreqd ; come here after adding an NRB to XMITQ
; Come here if:
;         1) Some ASLDI:DESIRExxx routine has added an NRB to XMITQ
;         2) An NRB timed out and added itself to the XMITQ
;         3) XMITTOB expired while waiting for collision resolution
; Assert: stacks are switched
; Assert: PLCBAS:STATE="uncommitted" and Receive hardware is enabled
;         or PLCBAS:STATE is committed to some transaction right now.
; In particular, if uncommitted, we need not (MUST NOT) re-enable receiver.
        ldx     ASLD+asld:interruptPLCB fetch pointer to current PLCB
        ldaa    plcbas:state,x         is driver doing something ?
        oraa    plcbas:collisionresolutionactive,x ...?
        beq     asldi:rti              b/ yes, now is bad time to start new transmission
        ldd     ASLD+asld:interruptPLCB is XMITQ empty ?
        addd    #plcbas:xmitqhead-nrb:xmitqflink (form pointer to dummy NRB)
        cmpd    plcbas:xmitqhead,x     (does Q head point to dummy NRB) ?
        beqd    asldi:rti              b/ yes, there's nothing to transmit!
        bra     asldix:trytoacquireether NRB is ready to send, try to send!
        page
asldi:decidenextoperation ; come here when a XMIT or RECV transaction has just been completed.
; Common exit point for major phases of ASLDI: interrupt routines
; If nothing is in the XMITQ, then enable receiver and get out
; Else attempt to transmit another message immediately.
; Assert: stacks are switched; PLCBAS:STATE=UNCOMMITTED
; Control is passed here after:
;      1) transmission of a message completes,
;      2) a message has been correctly received,
;      3) a message has been ignored (i.e., ETB<gap> has been found or missed)
        ldx     ASLD+asld:interruptPLCB fetch pointer to current PLCB
?       if      1                      debug
        ldaa    plcbas:state,x         fetch state variable
        beq     *+3                    check assertion
        swi
        fin
        ldaa    plcbas:collisionresolutionactive,x waiting for something ?
        ; If PLCBAS:COLLISIONRESOLUTIONACTIVE is set, then we are waiting
        ; for collision resolution interval to expire, so we can't send now.
        ; We don't force the driver state busy while waiting for collision
        ; resolution because we want newly received data to change us
        ; into the Receive state.
        ; When collision interval finally expires, control will come back
        ; here, and we will attempt to send again.  If we should receive
        ; a message while waiting for collision interval, then we simply
        ; capture the message, continue waiting for the collision resolution
        ; timer to go off, and then, in true Ethernet style, simply forget
        ; about the old collision resolution interval and learn about
        ; the network load all over gain.  Isn't that tacky?
        bne     asldi:nextoperationisreceive b/ yes, go back to receiving
        ldd     ASLD+asld:interruptPLCB is XMITQ empty ?
        addd    #plcbas:xmitqhead-nrb:xmitqflink (form pointer to dummy NRB)
        cmpd    plcbas:xmitqhead,x     (does Q head point to dummy NRB) ?
        bned    asldix:trytoacquireether b/ no, NRB needs service!
asldi:nextoperationisreceive ; set up hardware to receive a message
        bsr     asldi:enablereceive    set up hardware to receive a message
asldi:rti ; note: SDOS:SURPRISE flag may be set here
        jmp     sdos+sdos:rti          no, just go 'way (whew!)
        page
asldi:enablereceive ; subroutine to enable hardware to receive next message
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        incd    plcbas:enbrcvcount,x   count the number of times we enabled reciever
        ldd     #asldir:msgstart       where to go on receipt of message start
        std     plcbas:ilinputintPC,x
        jsr     plcbasc:ildisoi,x      shut off output interrupts
        ldd     #asldix:ignoretdre     set up to ignore TDRE interrupts
        std     plcbas:iloutputintPC,x (they should not ever occur)
        clr     plcbas:state,x         put us back into "uncommitted" state
;       jsr     plcbasc:ilenbii,x      enable input interrupts
;       rts
        jmp     plcbasc:ilenbii,x      enable input interrupts
        page    Message Transmission: Acquire bus phase
asldix:trytoacquireether ; an NRB is waiting for a chance to send
; See if we can acquire the ether and transmit a message.
; We really don't care how long it takes to acquire the ether.
; What we do care about is whether or not the NRB which is trying
; to transmit succeeds in transmitting within a reasonable length of time.
; (consider the case where the node actually acquires the ether,
; but fails due to collisions on every attempt: the collisions should
; not be counted as transmission failures, but the delay before success
; in transmission SHOULD count).
; A special timer, XMITFAILTOB, has already been started
; to time out all the transmission attempts of this NRB.
; (A single successful transmission may need several attempts
; to acquire the ether and resolve collisions).
; The timer is set to a value in the several sigma tail
; on the high side of the distribution of message delivery times.
; If this timer goes off, we declare that the network delivery
; system is too congested for practical delivery of messages,
; and kill the NRB that was attempting transmission.

        ldx     ASLD+asld:interruptPLCB  fetch pointer to Physical Link Control Block
        jsr     asldi:csma               is ether available ?
        bcc     asldix:etherfree         b/ ether is empty, go transmit !
        incd    plcbas:etherbusycount,x  count # times we delay until traffic is gone
        dec     plcbas:state,x           go into receive state
        ldd     #asldir:inputtimeoutinterrupt where to go if we don't get int
        std     plcbas:recvtob+tob:routine,x set up paranoia handler
        jsr     plcbasc:ilenbii,x        enable input interrupt for detected data
        ; Assert: plcbas:ilinputintPC is currently aimed at ASLDIR:MESSAGESTART
        if      0
; CSMA has told us that a character is waiting. Therefore the only
; timer we need set running is CHARACTERTIMEOUT to ensure the hardware doesn't
; die.  Setting PLCBAS:RECVTOB running would be completely inappropriate
; as it will tell us things are bad only if an entire message time goes
; by without any further interrupts; CHARACTERTIMEOUT should go off well before
; this point.  So we don't enable RECVTOB here.
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of RECV timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldd     plcbasc:messagetime,x  fetch max delay
        ldx     asldv:tempx
        else    (m6809)
        ldd     plcbasc:messagetime,x  fetch max delay
        leax    plcbas:recvtob,x       form pointer to RECVTOB
        fin
        jsr     SDOSRT+rt:stmo         set timeout running
        fin     0
        ldd     #asldir:charactertimeoutinterrupt where to go if we don't get int
        std     plcbasc:charactertimeout+timeout:routine,x set up paranoia handler
        ldaa    plcbasc:characterticks,x fetch max time before input interrupt
        staa    plcbasc:charactertimeout+timeout:fuse,x set fuse
        ; We got here because ASLDI:CSMA thinks new data is arriving...
        ; --> Receiver part is requesting an interrupt right NOW
        ; --> One character time is generous timeout for not getting interrupt
        ; now, receiver will either receive a message, ignore a message body,
        ; or it will time out and return control to ASLDIX:TRYTOACQUIREETHER
        jmp     asldi:rti              nothing useful to do right now
        page
asldix:transmitfailtimeout ; come here after waiting a long time...
; and not managing to send a complete message
; XMITFAILTOB just went off, so we need not reset it.
; Cleaning up after this kind of a mess is a mess (surprised? c'mon).
        std     asld:interruptPLCB     record PLCB that timed out
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:xmitqhead,x     which NRB died...
        std     asldv:interruptnrb     set pointer to NRB that timed out
        incd    plcbas:xmitfailcount,x bump count of transmit timeouts
        ldaa    plcbas:state,x         test state of driver...
        beq     asldix:transmitfail1   b/ idle (must be resolving collision)
        bpl     asldix:transmitfail2   b/ transmitting, abort transmission
        ; It seems a message is arriving. Find out who it is for.
        ldd     plcbas:nrbptr,x        do we know who it for yet ?
        cmpd    asldv:interruptNRB     (is for the failed NRB?)
        bne     asldix:transmitfail3   b/ arriving message not for failed NRB
        ; Message is arriving for failed NRB.  Too bad; he has been trying
        ; to tranmit for eons without success, so we'll kill him anyway.
        ; We must kill off the message reception.
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill receive character timeout
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of RECVTOB
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:recvtob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill "recv message" timeout
asldix:transmitfail3 ; message arriving for this NRB has been killed.
        ; Now, note that XMITTOB might have been active handling a
        ; collision resolution. If it was, we must kill it; if it wasn't
        ; it won't hurt to kill it.  So we always kill XMITTOB.
        bra     asldix:transmitfail1   go kill XMITTOB
        page
asldix:transmitfail2 ; transmitting, abort transmission
        ; Well...it seems we are in the middle of transmitting a message.
        ; Too bad: from the length of time we have been trying, this
        ; one probably won't get thru correctly either.  Kill off the
        ; transmission.  We would have liked to have been nice, and
        ; send an ETB at the end of our aborted transmission, but to
        ; do so, we have to count on the transmit hardware working correctly
        ; (i.e., CHARACTERTIMEOUT must not trigger).  If CHARACTERTIMEOUT went
        ; off while we were sending the ETB, we would lose control of
        ; the ABORT process, and would end up in a state with an NRB
        ; that was trying to send without XMITFAILTOB to ensure that it
        ; succeeded eventually.  Since we do not expect to go thru here
        ; often, and the price of the above disease is a bug which
        ; would probably drive us crazy with its rarity, we choose instead
        ; not to have the problem, and so we don't send a nice trailing ETB.
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill xmit character timeout
asldix:transmitfail1 ; re-enter here if idle: XMITTOB is timing out collision
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:xmittob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill "xmit message" timeout
        jsr     asldi:xgap             send gap to terminate message
        clr     plcbas:state,x         put us back in uncommitted state
asldix:transmitfailed ; all TOBs related to this NRB have been disabled
        ldd     #err:cantsendmessage   record error
        bra     asldir:killforreasonj1 go kill off NRB (tells task, too)

asldir:noresponse ; remote node doesn't respond to messages, give up
        ldd     #err:nodenoresponse    record cause of death
        bra     asldir:killforreasonj1 go kill off NRB (tells task, too)

asldir:nodetooslow ; remote node hasn't sent us any data despite our request
        ldd     #err:nodetooslow       record cause of death
asldir:killforreasonj1 ; go kill off NRB (tells task, too)
        jmp     asldir:killforreason
        page
asldix:etherfree ; ether appears to be free, transmit NOW!
*       First sets up message header and data part pointers
*       Then starts transmission
        inc     plcbas:state,x         commit us to transmitting
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        inc     plcbas:state,x         switch from "trying to acquire ether"
        ldx     plcbas:xmitqhead,x     This is the NRB which is ready
        stx     asldv:interruptnrb     save pointer to node rep. block
        ldaa    nrb:retry,x            did we ever get response to message ?
        beq     asldir:noresponse      b/ no, declare remote node as dead
        ldaa    nrb:datarequiredflag,x do we need data ?
        beq     asldix:0m              b/ no
        ldd     sdos+sdos:clock+1      get current time (not splinterable)
        subd    nrb:datarequiredtimestamp,x compute elapsed time
        bpl     asldir:nodetooslow     b/ elapsed time too long, node not responsive enough
asldix:0m
        ldaa    nrb:nodeid,x           grab "to" byte
        ldx     nrb:plcb,x             which PLCB to use
        ldx     plcbasc:NITptr,x       where Network information is
        ldab    NIT:nodeid,x           get the "from" byte (our node id)
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgto,x         and save to/from in msg header
        std     plcbas:msgidcrc,x      save duplicate header info
        tsta                           is this a broadcast ?
        bne     asldix:0f              b/ no
        ldaa    #sdlpcontrol:initiatequit yes, use this as control field
        bra     asldix:0d
        page
asldix:0f ; not a broadcast, decide what to send for control field
        ldx     asldv:interruptnrb     fetch pointer to node representative block
        ldaa    nrb:state,x
        if      m6800!m6801
        staa    asldv:statecontrol1+1  look control field up...
        ldx     asldv:statecontrol1    in a "state-to-control field" xlate table
        ldaa    asldv:statecontrol&$ff,x
        else    (m6809)
        ldx     #asldv:statecontrol    look control field up...
        lda     a,x                    in "state-to-control field" xlate table
        fin
asldix:0d ; send (a) as control field
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        staa    plcbas:msgcontrol,x
        ldx     asldv:interruptnrb
        ldd     nrb:rvpos,x            get the received count mod 2**16
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgrvpos,x      and store into message header

        ldx     asldv:interruptnrb
        ldd     nrb:ringinroom,x       get ready to receive count
        if      showroughedges
? ; following line may be unreasonable
        addd    #10                    egg other node on a little
; Perhaps a better solution would be to add 10-avgexcess or something similar
        fin
        cmpd    nrb:ringinlen,x        room promised exceed room available ?
        blo     asldix:0e              b/ yes, use egged on count
        ldd     nrb:ringinlen,x        no, use max ring room as prediction
asldix:0e ; (D) has ring in room predicted available when other node sends
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgreadycount,x and place into message header
        page
        ldd     #0                     assume...
        std     plcbas:msgxmitcount,x  that we will transmit no data bytes
        std     plcbas:msgpart2count,x so count of 2nd part of data portion is empty

        ldx     asldv:interruptnrb
        ldaa    nrb:state,x            is this an "INITIATING" message ?
        cmpa    #nodestate:initiating  ...

        ; We send an empty data portion for "INITIATING" messages,
        ; since it is not safe to send data with an INITIATING message.
        ; One cannot know which incarnation of a remote node is sending
        ; the initiating message, so data attached to such a message
        ; could be contradictory with a previous INITIATING message,
        ; and yet not be incorrect.
        beq     asldix:startxmit       yes, leave data portion of message empty
        ldd     nrb:xvpos,x            set up virtual position of 1st transmitted data byte
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgxmitbase,x
        ldd     nrb:ringoutdata,x      fetch number of bytes ready to send
        beqd    asldix:startxmit       b/ no data, leave data portion empty
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgxmitcount,x  use this as the default transmit count
        ; we could estimate other node's rcvdrdy here, as it must be
        ; larger than the last value he sent us!
        ldx     asldv:interruptnrb
        subd    nrb:rcvdrdy,x          is target node prepared for this many ?
        if      m6801!m6809
        bls     asldix:0c              b/ he's prepared for all we have
        else    m6800
        beqd    asldix:0c              b/ he's prepared for all we have
        blo     asldix:0c
        fin
        ldd     nrb:rcvdrdy,x          no, use target node's count as limit
        addd    #1                     fudge by one, so target node will know
                                       ; we have data even if he has zero room
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgxmitcount,x  save transmit count limit
        bra     asldix:0a
        page
asldix:0c ; receiver has room for all the data we have in the xmit buffer
        ldaa    nrb:xbuflk,x           is transmit buffer locked ?
        beq     asldix:0z              b/ no, xmit buffer not being filled
        ; sigh... even though we must transmit right now, it appears
        ; that new data is arriving in the transmit buffer.  Since we are
        ; already committed to the amount we are going to send, we will
        ; have to send the newly arriving stuff in another message.
asldix:0a ; try to convert message type to "desire ACK"
        ; Since we can't send all of our data, request an immediate ACK
        ; this will cause remote node to send us a signal as soon as
        ; he has enough buffer space for us to make an efficient transmission
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbas:msgcontrol,x    is this a "no hurry about ACK" msg ?
        cmpa    #sdlpcontrol:normal    ...?
        bne     asldix:0z              b/ no, leave message type alone
        ldaa    #sdlpcontrol:acksoon   yes, tell remote node...
        staa    plcbas:msgcontrol,x    that we would like response soon
asldix:0z ; some data to send, determine where data comes from
        ldx     asldv:interruptnrb
        ldd     nrb:ringoutfetch,x     pointer to 1st byte to transmit
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart1ptr,x   set start of 1st data chunk to send
        ldd     plcbas:msgxmitcount,x  how much data we will send
        negd                           make it easy to increment
        std     plcbas:msgpart1count,x assume data does not wrap past end ring
        page
        ldx     asldv:interruptnrb
        ldd     plcbas:msgxmitcount,x  how much data we will send
        addd    nrb:ringoutsdter,x     distance left to end of ring
        if      m6800
        beqd    asldix:startxmit       b/ all data is in first chunk
        bcs     asldix:startxmit       b/ all data is in first chunk
        else    (m6801!m6809)
        bls     asldix:startxmit       b/ all data is in first chunk
        fin
        negd                           data wraps, this is amount in 2nd chunk
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart2count,x
        ldd     plcbas:msgpart1count,x amount we thought was in 1st chunk
        subd    plcbas:msgpart2count,x = - actual count in 1st chunk
        std     plcbas:msgpart1count,x
        ldx     asldv:interruptnrb
        ldd     nrb:ringoutbase,x      remember where 2nd chunk starts
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart2ptr,x
        page    ASLD: Transmit Message (interrupt routines)
asldix:startxmit ;  This is the code that sends the message
*        SDLP message head and all message parameters have been pre-computed
*        This code is optimized to minimize interrupt overhead.
*             --> don't monkey with it.
*        This code requires 8 bytes of user stack to service interrupt
*
*        Transmission occurs in 4 phases:
*        Phase 1: Transmit the message preamble, message header
*        Phase 2: Transmit the portion of message until top of ring
*        Phase 3: Transmit the portion of message from bottom of ring
*        Phase 4: Transmit message CRC, ETB and <gap> postamble
*
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        jsr     plcbasc:ilenboi,x      enable output interrupt on buffer empty
        jsr     plcbasc:ilenbii,x      enable input interrupt to match msg head
        ldd     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        addd    #plcbas:msgsyn         pointer to message head
        stx     plcbas:msgheadptr,x    set up to match 1st transmitted character
        ldx     #asldir:verify         where to go to verify input
        stx     plcbas:ilinputintPC,x  set up interrupt vector
        ; Note: we don't set input timeout on verified bytes, because we
        ; only have 1 timeout mechanism, and it is used to watch for
        ; transmit hardware failures during transmission
        ; We might be hooked up in a full-duplex (2 node-only) configuration,
        ; in which case we never hear our transmitted data.
        ldaa    #plcbas:msgidcrc+2-plcbas:msgsyn # message bytes to verify
        ; We verify only those bytes which are transmitted without CRC generation,
        ; on the grounds that the verification process is expensive,
        ; and should not be done if we are also doing CRC generation.
        ; A further justification is that the unCRC'd bytes supply enough
        ; bits to effectively resolve any collisions, which is the
        ; purpose of the verification, and verification of further bits
        ; is just a waste of energy.
        staa    plcbas:counter+1,x     (will down count in verify logic)
        page
        ldd     #asldix:charactertimeout where to go if transmit hardware fails
        std     plcbasc:charactertimeout+timeout:routine,x
        ldd     #asldix:xmittimeout    where to go if message not sent on time
        std     plcbas:xmittob+tob:routine,x
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldd     plcbasc:messagetime,x  set timeout to ensure message xmit...
        ldx     asldv:tempx            (get address of XMITTOB)
        else    (m6809)
        ldd     plcbasc:messagetime,x  set timeout to ensure message xmit...
        leax    plcbas:xmittob,x       (form address of XMITTOB)
        fin
        jsr     SDOSRT+rt:stmo         doesn't take longer than reasonable
        dec     sdos+sdos:stackswitched unswitch stacks
        bpl     asldix:startxmit1      b/ stack doesn't need unswitching
        ldx     sdos+sdos:currentask   switch back to user task
        lds     tcb:stack,x
asldix:startxmit1 ; stacks now unswitched
        page
asldix:sendmessageheader ; send message header
        ; assert: transmitter data register IS empty here
*       jsr     asldix:waittdre        wait for tdre to occur
        ldaa    #ascii:syn             send message preamble byte
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        staa    plcbas:msgsyn,x        for ASLDIR:VERIFY to check
        jsr     asldix:justxmit
; we don't need to CRC first 4 bytes because message head MSGIDCRC bytes...
; are effectively a CRC! Thus we save time during possible collision resolution
; When we are verifying message head, the overhead is DOUBLE because
; we are simulatneously transmitting AND receiving, so we want to keep
; the cost as low as possible.  Once the message head has been transmitted
; and verified, a collision presumably can't occur and the receive/verify
; logic turns itself off, and we can then afford slightly more expensive code.
; assert: (X) points to PLCB after returning from JUSTXMIT or CRCANDXMIT
        ldaa    plcbas:msgto,x         send who message is TO
        jsr     asldix:justxmit
        ldaa    plcbas:msgfrom,x       send who message is FROM
        jsr     asldix:justxmit
        ldaa    plcbas:msgidcrc,x      send MSGIDCRC (duplicate TO byte)
        jsr     asldix:justxmit
        ldaa    plcbas:msgidcrc+1,x    send MSGIDCRC (duplicate FROM byte)
        jsr     asldix:justxmit
        clr     plcbas:computedcrcupper,x zero the message CRC
        clr     plcbas:computedcrclower,x
        ; Note: we must send the message head using inline code instead
        ; of a loop using PLCBAS:MSGHEADPTR, because the pointer variable
        ; is in used by the verify logic, and we are unwilling to put
        ; yet another pointer variable into the PLCB.
        ldaa    plcbas:msgcontrol,x    transmit message type
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgrvpos,x      transmit message RVPOS upper 8 bits
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgrvpos+1,x    transmit message RVPOS lower 8 bits
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgreadycount,x transmit message Ready count upper 8 bits
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgreadycount+1,x transmit message Ready count lower 8 bits
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgxmitcount,x  transmit message Transmit count upper 8 bits
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgxmitcount+1,x transmit message Transmit count lower 8 bits
        jsr     asldix:crcandxmit
        ldd     plcbas:msgxmitcount,x  any data to send ?
        beqd    asldix:msgcrc          b/ no, just send crc next
        ldaa    plcbas:msgxmitbase,x   send transmission base
        jsr     asldix:crcandxmit
        ldaa    plcbas:msgxmitbase+1,x (send lower 8 bits of xmit base)
        jsr     asldix:crcandxmit
asldix:msgpart1xmitloop ; send bytes from part1 of data portion of the message
; overhead here is 36~ per byte for a 6809; 47~ for 6800
        lda     [plcbas:msgpart1ptr,x] (4+4~~) now send a byte from first part of message body (data bytes)
        if      m6800!m6801!m6811
        ldx     asld:interruptplcb     (5~) restore PLCB pointer for CRCANDXMIT to use
        fin
        jsr     asldix:crcandxmit      (8~) crc the data byte, then transmit it!
        incd    plcbas:msgpart1ptr,x   (6+1+3~) advance buffer pointer
        inc     plcbas:msgpart1count+1,x (6+1~) down count remaining bytes in part 1
        bne     asldix:msgpart1xmitloop (3~) b/ more to send
        inc     plcbas:msgpart1count+1,x (propogate carry)
        bne     asldix:msgpart1xmitloop b/ more to send
        ldd     plcbas:msgpart2count   any bytes to send in 2nd part ?
        beq     asldix:msgcrc          b/ no data in second part of message
asldix:msgpart2xmitloop ; send bytes from part2 of data portion of the message
        lda     [plcbas:msgpart2ptr,x] now send a byte from second part of message body (data bytes)
        if      m6800!m6801!m6811
        ldx     asld:interruptplcb     (5~) restore PLCB pointer for CRCANDXMIT to use
        fin
        jsr     asldix:crcandxmit      crc the data byte, then transmit it!
        incd    plcbas:msgpart2ptr,x   advance buffer pointer
        inc     plcbas:msgpart2count+1,x down count remaining bytes in part 2
        bne     asldix:msgpart2xmitloop b/ more to send
        inc     plcbas:msgpart2count+1,x (propogate carry)
        bne     asldix:msgpart2xmitloop b/ more to send
        page
asldix:msgcrc ; all message bytes are sent, now send crc
        ldaa    plcbas:computedcrcupper,x send 1st crc byte
        coma                           send complemented crc
        jsr     asldix:justxmit        (don't crc it!)
        ldaa    plcbas:computedcrclower,x send 2nd crc byte
        coma                           send complemented crc
        jsr     asldix:justxmit        (don't crc it either!)
        ldaa    #ascii:etb             send "end of transmission block"...
        jsr     asldix:justxmit        to help other nodes know when to send
        page
; The receiver in the remote node requires a minimum 1 character
; time gap (actually, mean time to send character plus 2 sigma)
; after receiving the ETB to be convinced that the true
; end-of-message has been reached. To transmit a 1 character gap
; here is not sufficient, as slight clock differences between this CPU
; and the remote may make 1 character time be .99 character time
; at the remote; to solve this problem, we transmit TWO character
; gaps. This is easily accomplished: we wait until we have heard
; our own ETB, and then we sit idle for 1 character time (the 1st of
; two); the second character time happens for free because
; neither we (nor any other node) will transmit again until
; we have listened for silence on the bus for 1 character time.

        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldix:msgcrc          b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB restore pointer to PLCB
asldix:msgcrc0 ; stack switch completed
        ldd     #asldix:ignoretdre     set up to ignore TDRE interrupts
        std     plcbas:iloutputintPC,x
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill watchdog on transmit hardware
        if      m6800!m6801!m6811
        ldd     ALSD+asld:interruptPLCB form pointer to XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:xmittob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill message xmit timeout
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
;       jsr     plcbasc:ildisoi,x      disable xmit buffer empty interrupts
;       jsr     plcbasc:ildisii,x      shut off receiver interrupts if enabled
        ; ASLDI:XGAP should "hear" the ETB, so receiver logic will
        ; not hear tail end of transmitted messages
        if      showroughedges
? ; Why can't we send a "gap" via a timeout instead of actively looping?
        fin
        jsr     asldi:xgap             transmit end of message gap
        if      showroughedges
? ; check for carrier here, if we got it, message xmit not successful!
        fin
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbas:counter+1,x     were all header bytes verified ?
        beq     asldix:msgdone         b/ all header bytes verified
        cmpa    #plcbas:msgidcrc+2-plcbas:msgsyn were no header bytes verified?
        beq     asldix:msgdone         b/ exactly 0 header bytes verified
        ; OOPS: only SOME bytes of message header were verified!
        ; Note that no timeout occurred. Hmmm.
        incd    plcbas:headerverifyfailcount,x count number of funny events
        ; Well... I don't know what to do here.  We didn't get a collision.
        ; Why don't we just pray the message got thru?  If we are wrong,
        ; we will eventually retransmit it anyway, so no great harm.
;       jmp     asldix:msgdone
        page
asldix:msgdone ; **** message has been completely transmitted! ****
        ; assert: receive hardware will not hear tail end of just-finished transmission
        incd    plcbas:xmitokcount,x   count # times we succeed in transmission
        clr     plcbas:xdelaymask,x    we appear to have successfully transmitted a message
        clr     plcbas:xdelaymask+1,x  shorten transmit conflict resolution window
        ; we zero the delay mask in true Ethernet style
        ; (see Tanenbaum, Computer Networks if you want to know a lot more)
        ; this forces node to re-learn about network load each time it tries
        ; to send a packet.  A more efficient scheme might consider trying
        ; to hold the load level constant by only allowing a node to transmit
        ; a maximum of n times per second.

        ldd     plcbas:msgxmitcount,x  fetch number of data bytes sent
        ldx     plcbas:nrbptr,x        now update NRB
        stx     asldv:interruptnrb     remember which NRB is involved
        addd    nrb:xvpos,x            revise MAXACKRVPOS upward
        std     nrb:maxackrvpos,x      save for acknowledgement check
        jsr     asldi:removenrbfromxmitq take nrb out of the XMITQ
        ; above step kills XMITFAILTOB for this NRB (or refreshes it for next)
        dec     nrb:retry,x            down count # xmits with no response
        ; nrb:retry is set nonzero when a message from a remote node is rcvd
        ; we check retry count for zero just before we try to transmit
        ldaa    nrb:nodeid,x           is this a broadcast message ?
        bne     asldix:msgdone1        b/ not a broadcast message, leave nrb alone
*       broadcast message, release the buffer contents, leave nrb out of xmitq
        jsr     asldi:clearout
        ; signal buffer space availability to task level for broadcasts
        clr     sdos+sdos:surprise     what a tacky way to do this
        bra     asldi:decidenextoperationj1
        page
asldix:msgdone1 ; non-broadcast message just transmitted
        ldaa    nrb:state,x
        cmpa    #nodestate:quit        were we just spos'd to send "quit" ?
        beq     asldir:deadj1          b/ yes, go clean up (nrb:epitaph = 0)
        cmpa    #nodestate:die         were we just spos'd to send "die" ?
        beq     asldir:deadj1          b/ yes, nrb:epitaph already set
        cmpa    #nodestate:conversei   was NRB idle before we xmitted ?
        beq     asldix:msgdone2d       b/ yes, force "expect to recieve"
        cmpa    #nodestate:converses   were we in "sposd to send" state ?
        bne     asldix:msgdone2b       b/ no
asldix:msgdone2d ; force nodestate to "conversing, expect to receive next"
        ldaa    #nodestate:converser   yes, force state to "sposd to receive"
        staa    nrb:state,x
asldix:msgdone2b
        cmpa    #nodestate:converser   in a "sposd to receive next" state ?
        bne     asldix:msgdone2a       b/ no, must be special function, set a timeout
        ldab    nrb:ringoutdata,x      do we have data to send ?
        orab    nrb:ringoutdata+1,x
        bne     asldix:msgdone2a       b/ data to send, set timeout
        ldaa    nrb:datarequiredflag,x do we need data ?
        bne     asldix:msgdone2a       b/ yes, set a timeout
        ldaa    #nodestate:conversei   no, mark NRB as idle
        staa    nrb:state,x            don't set a timeout
        bra     asldi:decidenextoperationj1 go see if something else to do

asldix:msgdone2a ; must set a timeout
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbasc:retrytime,x    set time delay before re-transmit
        ; assert: plcbasc:retryttime is non-zero
        ; we expect to get a response back well before then
        jsr     asldi:putnrbinxmitq    put nrb in xmitq to be processed later
asldi:decidenextoperationj1
        jmp     asldi:decidenextoperation

asldir:deadj1
        jmp     asldir:dead            now for the coup de grace
        page
asldir:verify ; received data, verify that it matches what we sent
; If verification fails, it is (probably) because there is bus contention.
; Since there is no way to distinguish between contention (collsion) and
; simple bus noise, we simply assume a collision has occurred.
; Note: when xmitting the SDLP message header, 2 interrupts occur for
; each transmitted byte; one for byte transmitted, and one for the echoed
; (received) byte.  Since this is expensive, we only do the verification
; process on the message header.   Once the header has been verified,
; we are extremely certain that we "own" the bus and no collisions will
; occur, so we stop verifying and thus save the verification overhead.

; Ideally, we should also watch for input device timeout while verifying
; the header of our transmission, but to do this requires a timeout to
; go off during the middle of a message transmission (even under normal
; circumstances) and the overhead and code complexity to accomplish this
; was deemed not worth the effort.

; note: input verifies are NOT timed out, because the timeout
; is used to keep XMIT hardware honest.
        ; (A) contains received byte
        suba    [plcbas:msgheadptr,x]  (4+4~) fetch next byte to match against
        bne     asldix:collide         (3~) b/ verification failed --> collision
        if      m6800!m6801!m6811
        ldx     ASLD+asld:interruptPLCB (5~) fetch pointer to Physical Link Control Block
        fin
        incd    plcbas:msgheadptr,x    (6+1+3~)
        dec     plcbas:counter+1,x     (6~) end of region to verify ?
        bne     asldir:verifydone      (3~) b/ no, exit and wait for next int
        jsr     plcbasc:ildisii,x      shut down the input interrupt routine
        ; This way, we don't see more input interrupts until we are
        ; through transmitting the current message.
        ; Since input interrupts are now disabled, we don't care where
        ; the input interrupt routine points
asldir:verifydone ; verification of transmitted msg head byte is complete
        rti                            (15~) we are done with interrupt!
; Overhead used here is 47~ per byte for 6809, 57~ for 6800
        page
asldix:collide ; transmission failed due to collision
; We should only get here during transmission of a message header
; asldir:verify comes here when verification of transmitted data fails
; or when a badly formed character is received while transmitting
; (someday, we might avoid collisions by using a collision-free protocol!)
        ; assert: stacks are not switched
        ; arrival here occurs because of something received, and is thus
        ; not synchronized with Transmitter Data Register Empty
        jsr     plcbasc:ildisii,x      shut down the input interrupt routine
        jsr     asldix:waittdre        wait for acia ready to send
        ldaa    #$a5                   force transmission of an "abort"!
        jsr     asldix:justxmit        so that everybody else hates our message
        ldaa    #ascii:etb             send ETB (somebody might hear it...)
        ; It might seem pointless to send an ETB here, because there is a
        ; good chance it will get mangled in the collision recovery process.
        ; If it gets mangled, oh well... but if not, then it usefully
        ; signals the end of message has arrived to other nodes,
        ; making their operation more efficient. So we send it anyway.
        jsr     asldix:justxmit        to signal end of message
        ldd     #asldix:ignoretdre     set up to ignore TDRE interrupts
        std     plcbas:iloutputintPC,x
        clr     plcbasc:charactertimeout+timeout:fuse,x disable transmit hardware watchdog
        jsr     asldi:xgap             send gap after ETB to flush buffer
        ; To be sure noise on bus has gone away, we actually need to
        ; wait TWO character times, checking for 1 character time silence
        ; We solve this problem by ignoring it, transmit logic will
        ; listen for bus silence before it attempts to send again.
        page
        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldix:collide1        b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldix:collide1
        ldd     #asldix:collisionresolutiontimeout set up to retransmit if bus is quiet...
        std     plcbas:xmittob+tob:routine,x after delay
        incd    plcbas:collisioncount,x  bump # collisions encountered
        inc     plcbas:collisionresolutionactive,x flag "resolving collision"
        sec                              xmit conflict, compute new delay
        rol     plcbas:xdelaymask+1,x
        rol     plcbas:xdelaymask,x
        ; Now compute a "random" number in (D).
        ldd     plcbas:goodcrccount,x    value should vary widely between nodes
        eord    sdos+sdos:clock+1        to ensure out-of-sync sometimes
        eord    plcbas:collisioncount+1,x to prevent delay from being permanently zero, thereby causing timeout
        anda    plcbas:xdelaymask,x
        andb    plcbas:xdelaymask+1,x
        if      m6800!m6801!m6811
        pshd                           save desired delay
        ldd     ALSD+asld:interruptPLCB form pointer to XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldx     asldv:tempx
        puld                           note: this value can be zero
        else    (m6809)
        leax    plcbas:xmittob,x       convert xmit timeout...
        fin
        jsr     SDOSRT+rt:utmo         into collision resolution timeout
;       clr     plcbas:state,x         reset state to "uncommitted"
        if      showroughedges
? ; perhaps the following should really go to WAIT FOR ETB?
        fin
        jsr     asldi:enablereceive    turn on the receive logic
        jmp     asldi:rti              we can't possibly transmit next...
        page
asldix:collisionresolutiontimeout ; come here when...
; random collision resolution interval is over
; XMITTOB has deactivated itself
        std     ASLD+asld:interruptPLCB record which PLCB is ready
        ldx     ASLD+asld:interruptPLCB
        clr     plcbas:collisionresolutionactive,x exit collision resolution mode
        jmp     asldi:decideifxmitreqd go see if we can start transmitting
        page
asldix:charactertimeout ; Serial port timed out on transmission
;       we get here if not TDRE interrupt occurs after sending a character
;       PLCBAS:CHARACTERTIMEOUT has disabled itself in the process of getting here
        std     ASLD+asld:interruptPLCB set pointer to Physical Link Control Block
        if      m6800!m6801!m6811
        ldd     ALSD+asld:interruptPLCB form pointer to XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        leax    plcbas:xmittob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill xmit message timeout
        if      m6800!m6801!m6811
        ldd     ALSD+asld:interruptPLCB form pointer to XMITFAILTOB
        addd    #plcbas:xmitfailtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        leax    plcbas:xmitfailtob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill XMITFAIL timeout
        incd    plcbas:xmittimeoutcount,x bump number of timeouts
        clr     plcbas:state,x         reset state to "uncommitted"
        ldd     #asldix:ignoretdre     set up to ignore tdre interrupts
        std     plcbas:iloutputintPC,x
;       jsr     plcbasc:ildisoi,x      disable xmit buffer empty interrupts
;       jsr     plcbasc:ildibi,x       stop message head verification
        jsr     plcbasc:ilreset,x      serial port is sick, bang its head
        ldx     plcbas:nrbptr,x        which NRB was involved in transmission
        stx     asldv:interruptnrb     kill off the transmitting NRB
        ldd     #err:deviceerrored     this is the cause
        ; this NRB will be useless until terminated and then revived,
        ; which will clear its nrb:epitaph
        jmp     asldir:killforreason   make NRB die
        page
asldix:xmittimeout ; XMITTOB timed out --> we were too slow while sending a message
; This probably occurs if SDOS takes an interrupt in the middle of message
; which takes a long time to service, which screws up our message transmission
; PLCBAS:XMITTOB has disabled itself in the process of getting here
        if      showroughedges
? ; should compute tighter bounds on XMITTOB fuse (to match msg length exactly
        fin
        std     ASLD+asld:interruptPLCB set pointer to Physical Link Control Block
        ldx     ASLD+asld:interruptPLCB fetch pointer to PLCB
        incd    plcbas:xmittooslow,x   bump number of too-slow timeouts
        jsr     asldix:waittdre        wait for buffer empty interrupt
        ldaa    #ascii:etb             send ETB to help other guys
        jsr     asldix:justxmit        note: only CHARACTERTIMEOUT active here
        clr     plcbasc:charactertimeout+timeout:routine,x kill character timer
        jsr     asldi:xgap             send gap after (bad) end of message
        clr     plcbas:state,x         reset state to "uncommitted"
        jmp     asldi:decideifxmitreqd try to transmit again
        page
asldix:waittdre ; wait for transmitter data register emtpy (TDRE) to occur
; set up timeout in case it takes too long
;       ldx     ASLD+asld:interruptPLCB
        puld                           save return address so we can get back to caller
        std     plcbas:iloutputintPC,x
;       ldd     #asldi:outputtimeout   set up output timeout handler
;       std     plcbasc:charactertimeout+timeout:routine,x (this is set by ASLD:XINTETHERFREE)
        ldab    plcbasc:characterticks,x set up fuse on device (usual paranoia)
        stab    plcbasc:charactertimeout+timeout:fuse,x
        rti

asldix:ignoretdre ; ignore TDRE interrupt
;       ldx     ASLD+asld:interruptPLCB
        incd    plcbas:unexpectedTDREcount,x count # times this happened
        jsr     plcbasc:ildisoi,x      shut off the output interrupt
        rti                            and just ignore it
        page
asldix:crcandxmit ; adjust crc for byte (a), send (a), wait for tdre interrupt!
; ***** Fast CRC generation code (duplicate in asldir:waitbyteandcrc) *****
; adjust XMITCOMPUTEDCRC according to byte in (A); destroys (A)
; This codes works for 6800/6809; you'll find it hard to make it faster.
; The basic idea here is to do an XOR based upon an entire byte at one time,
; rather than one bit at a time.  CRCTABLE contains pre-computed 8 bits CRCs
; (set up at initz time by GENCRCTABLE) to help us do this process quickly.
; (A) is preserved as we still need to transmit it.
;
; The following picture represents state of affairs on entry to this
; routine.  The OLDCRC (really the remainder of a partially complete mode 2
; divide by the CRC polynomial over the entire message) has the new
; message byte concatenated to it, and then 8 conventional CRC
; "divide" steps are performed in one step by a table lookup.
;
;  _________________________________________________
;  |  oldcrcupper  |  oldcrclower  | newbytetocrc  |
;  -------------------------------------------------
;
; OLDCRCUPPER byte is used to select 16 bit precomputed value from table
; Precomputed value is XOR'd against [OLDCRCLOWER,NEWBYTETOCRC]
; to form NEWCRC.
;
; Assert: (X) has PLCB pointer in it already
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        if      m6809
        ldb     plcbas:computedcrcupper,x (4+1~) fetch CRCTABLE offset needed
        ldu     #crctable+128          (3~) set up to index CRCTABLE
        leau    b,u                    (4+1~) form CRCTABLE slot address
        leau    b,u                    (4+1~) = CRCTABLE + 2 * CRCUPPER
        ; Note: CRC table is built with first 128 slots being indexed when
        ; high bit of CRCUPPER is set because of way that "leau b,u" works
        ldab    plcbas:computedcrclower,x (4+1~) fetch lower half of current CRC
        eorb    ,u                     (4+0~) form new upper byte of CRC
        stab    plcbas:computedcrcupper,x (4+1~) save new upper byte of CRC
        eora    1,u                    (4+1~) form new lower byte of CRC
        staa    plcbas:computedcrclower,x (4+1~)
        eora    1,u                    (4+1~) regenerate byte to transmit
        ; overhead here is 47~ for 6809
        else    m6800!m6801!m6811
        psha                           (4~) save byte to send
        ldb     plcbas:computedcrcupper,x (5~) fetch CRCTABLE offset needed
        stab    asldv:crctable1+1      (5~) set lower half pointer
        stab    asldv:crctable2+1      (5~) set upper half pointer
        ldab    plcbas:computedcrclower,x (5~) fetch lower half of current CRC
        ldx     asldv:crctable1        (5~) fetch pointer to lower CRCTABLE slot
        eorb    #crctable&$FF,x        (5~) compute new upper byte of CRC
        ldx     asldv:crctable2        (5~) fetch pointer to upper CRCTABLE slot
        eora    #(crctable+256)&$FF,x  (5~) compute new lower byte of CRC
        ldx     ASLD+asld:interruptPLCB (5~) fetch pointer to Physical Link Control Block
        staa    plcbas:computedcrclower,x (5~) record new lower CRC byte
        stab    plcbas:computedcrcupper,x (5~) record new upper CRC byte
        pula                           (4~) restore byte to send
        ldx     ASLD+asld:interruptPLCB (5~) fetch pointer to Physical Link Control Block
        ; overhead here is 68~ for 6800
        fin
;       bra     asldix:justxmit        (0~)
        page
asldix:justxmit ; send (a) and wait for TDRE (Transmitter Data Register Empty) interrupt
        ; assert: hardware device transmit buffer is empty
        ; assert: interrupt is currently enabled on xmit buffer empty
        ; We set up output timeout on net device primarily to protect the
        ; OTHER nodes in the network, not to protect us.  If it were to
        ; protect us, we wouldn't bother, because it is extra overhead.
        if      m6800!m6801!m6811
        pulb                           (4~) save return address so we can...
        stab    plcbas:iloutputintPC,x (5~) return to caller when next output...
        pulb                           (4~) interrupt occurs
        stab    plcbas:iloutputintPC+1,x (5~)
;       ldd     plcbas:outputtimeout,x (5+2~) set up output timeout handler
;       std     plcbasc:charactertimeout+timeout:routine,x (5+2~) (this is set by ASLD:XINTETHERFREE)
        ldab    plcbasc:characterticks,x (4+1~) set up fuse on device (usual paranoia)
        stab    plcbasc:charactertimeout+timeout:fuse+1,x (4+1~)
        jmp     plcbasc:ilputdev,x     (3+1~) go output the character and do RTI
        ; 40~?? overhead on 6800
        else    (m6809)
        puls    u                      (5+2~) save return address so we can get back to caller
        stu     plcbas:iloutputintPC,x (5+1~)
;       ldd     plcbas:outputtimeout,x (5+2~) set up output timeout handler
;       std     plcbas:charactertimeout+timeout:routine,x (5+2~) (this is set by ASLD:XINTETHERFREE)
        ldab    plcbasc:characterticks,x (4+1~) set up fuse on device (usual paranoia)
        stab    plcbasc:charactertimeout+timeout:fuse,x (4+1~)
        jmp     plcbasc:ilputdev,x     (3+1~) go output the character and do RTI
        ; 27~ overhead on 6809
        fin
        page    ASLD: Input Interrupt handling
asldir:inputlateinterrupt ; Come here when we detect lost data, registers saved
;       Intended to handle conditions like OVERRUN (we missed a character)
;       Caused by being too slow to respond to input interrupt in time
;       Registers have been saved as specified in program description up front.
;       Hardware device has been acknowledged.
;       Because of the variability of LSI Asynch parts, we are not sure
;       if (A) has the most recently received character code in it,
;       so we choose to ignore this information.  The only time this
;       makes a difference is if we miss the ETB at the end of a message,
;       and since messages sizes are from 20 to 200 bytes, this only
;       happens <5% of the time when a data late occurs, so we don't care.
;
;       What we do here depends on ASLD:STATE:
;       If transmitting:
;           DON'T count a collision (it is our fault if we are slow),
;           Abort xmission, send an ETB (so other nodes know msg is over)
;           Contend for bus and try to transmit again
;       If receiving:
;           We lost part of the message, so give up trying to capture it
;           If we haven't received the message id correctly,
;           then start watching for ETB.
;           If the message id says the message is for us,
;           then set up to transmit a NAK as soon as bus is available
;           (We could try to cause a collision, which would cause sender
;           to re-send right away, but we aren't sure we can cause a collision,
;           and the sending node would draw erroneous conclusions about the
;           bus load if he detected the collision we caused).
;       If watching for ETB:
;           We assume that we didn't miss the ETB.
;           Continue watching for ETB
;             (We could check received character for ETB, but we don't).
;       If uncommitted state:
;           We assume we missed the beginning of a message.
;           Start watching for ETB.
;             (We could check received character for ETB, but we don't).
        page
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        incd    plcbas:inputlatecount,x bump number of late input errors seen
        ldab    plcbas:state,x         recieve, send or idle?
        beq     asldir:ignoreuntiletb  b/ idle, must have missed msg head
        bmi     asldir:inputlate1      b/ receiving, send NAK as response
        ; assert: we must be transmitting, stop verification of msg header
        jsr     plcbasc:ildisii,x      disable input interrupts
        jsr     asldix:waittdre        wait for next chance to transmit
        ldaa    #ascii:etb             transmit an ETB as signal to other nodes
        jsr     asldix:justxmit        doing CRC is a waste of effort
;       jsr     plcbasc:ildisoi,x      disable output interrupts
        jsr     asldi:xgap             transmit gap after ETB
        clr     plcbas:state,x         exit transmission state
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill output character watchdog
        ; There is no watchdog to kill on verified bytes.
        ; Note: the XMITFAILTOB is left active to ensure that this
        ; NRB eventually manages to transmit its message.
        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldir:inputlate2      b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB restore pointer to PLCB
asldir:inputlate2
        if      m6800!m6801!m6811
        ldd     ALSD+asld:interruptPLCB form pointer to XMITTOB
        addd    #plcbas:xmittob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:xmittob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill XMITTOB timeout block
        jmp     asldi:decidenextoperation go try to transmit again
        page
asldir:inputbadinterrupt ; Come here if mal-formed data detected.
;       Registers saved as per ASLD:LATEINTERRUPT
;       This entry is caused by detection of a FRAMING ERROR or similar fault;
;       probably caused by a collision. Hardware device has been acknowledged.
;       (A) contains a garbled character, which we ignore.
;
;       What we do here depends on ASLD:STATE:
;       If transmitting:
;           We presume this means that there is a collision on the bus
;              (there is only a low probability that it could be noise damaging
;              a STOP bit [because receive tap is close to the transmit tap,
;              so any bus noise should be overwhelmed by transmitter signal].
;              Since we can't distinguish between noise and a collision,
;              we ignore the noise possibility, at the risk of convincing this
;              node that the bus load is heavy when the line is merely noisy)
;           Count a collision
;           Enforce the collision
;           Abort transmission, send an ETB (so other nodes know msg is over)
;           Contend for bus and try again
;       If receiving:
;           We presume a damaged STOP bit from noise happened.
;           If we haven't received the message id correctly,
;           Then just restart the receiver.
;           If the message id says the message is for us, set up
;           to transmit a NAK soon (presumably, the sender will also see
;           a collision, and will transmit right away, obviating the need
;           to send the NAK).
;       If watching for ETB:
;            We presume a damaged STOP bit from noise happened.
;            Continue watching for ETB.
;       If uncommitted:
;           We presume a character with damaged STOP bit has been received
;              It was probably part of a message, so we should watch
;              for end of message before trying to get in sync again.
        page
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        incd    plcbas:inputlatecount,x count number of bad interrupts
        ldaa    plcbas:state,x         recieve, send or idle?
        beq     asldir:ignoreuntiletb  b/ idle, must be bad msg head
        lbpl    asldix:collide         b/ transmitting, abort it and try again
asldir:inputlate1 ; enter here if data late while receiving a message
        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldir:inputbadinterrupt2 b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB restore pointer to PLCB
asldir:inputbadinterrupt2
        jsr     asldir:sendNAK         to make remote node aware of error
        dec     sdos+sdos:stackswitched unswitch stacks
        bpl     asldir:ignoreuntiletb  b/ stacks unswitched
        ldx     sdos+sdos:currentask   switch back to user task
        lds     tcb:stack,s
;       bra     asldir:ignoreuntiletb  ignore data until ETB seen
        page
asldir:ignoreuntiletb ; we have lost sync with message, hunt for ETB <gap>
; When we get here, all we know is that some bytes are arriving.
; We assume that the bytes are part of a message; this driver
; can't receive a message until we get back into sync with message starts,
; nor can it transmit until the <gap> between messages is found,
; so we need to watch for the ETB <gap> sequence that follows a message.
; This code watches merely for ETB <gap>, using no other knowledge.
; There is another code segment (ASLDIR:IGNOREMESSAGE) that watches for ETB,
; knowing how long the message is; that chunk will not be fooled
; by ETB codes embedded in messages, as this code will be.
;       ldx     ASLD+asld:interruptPLCB   fetch pointer to Physical Link Control Block
        ; The following is necessary because we might have been listening for
        ; gap at the end of message (which we do after ILDISII), and discover,
        ; much to our horror, that the gap isn't present (i.e., we were)
        ; listening to the middle of some message).  So we want to wait
        ; for ETB, which requires we undo ILDISII.
        jsr     plcbasc:ilenbii,x      enable input interrupts again
        ldaa    #$80                   = "receiving, but only for ETB"
        staa    plcbas:state,x         exit receive mode, but prevent transmit
        ; assert: CHARACTERTIMEOUT already aimed at ASLDIR:CHARACTERTIMEOUT
        ldd     #asldir:waitETBtimeout where to go if nothing arrives
        ; assert: PLCBAS:RECVTOB is already running !
        std     plcbas:recvtob+tob:routine,x set message timeout trap
asldir:waitforetb ; lost sync with msg, loop here waiting for ETB <gap> to arrive
        jsr     asldir:waitETBbyte     wait for next message byte
        ; if waiting for ETB and we get no data, we go to ASLDIR:WAITETBTIMEOUT
        ; if waiting for ETB and we get DATALATE, we continue waiting for ETB
        ; if waiting for ETB and we get garbled data, continue waiting for ETB
        cmpa    #ascii:etb             appear to be end of message ?
        bne     asldir:waitforetb      b/ no, wait some more
        ; Note: we can't kill off TOBs before we switch stacks,
        ; or we might overpush the 8 bytes alloted to us.
        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldir:waitforetb1     b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB restore PLCB pointer
asldir:waitforetb1
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill off character watchdog
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB kill off the message timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:recvtob,x       form address of TOB to kill
        fin
        jsr     SDOSRT+rt:rtmo         remove the timeout
        jsr     asldi:csma             check for <gap>
        bcc     asldi:decidenextoperation b/ heard gap, go see if need to transmit
        dec     sdos+sdos:stackswitched unswitch stacks
        bpl     asldir:waitforetb2     b/ stack doesn't need unswitching
        ldx     sdos+sdos:currentask   switch back to user stack
        lds     tcb:stack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldir:waitforetb2 ; stack is now in same state as before interrupt
        jsr     plcbasc:ilenbii,x      enable input interrupts to hear char
        bra     asldir:waitforetb      go fetch byte and inspect

asldir:waitETBtimeout ; come here via PLCBAS:RECVTOB timeout interrupt...
; if we missed ETB at end of message.
; This can happen if we aren't in sync when we start reciept of a message,
; or if the message has malformed header, bad data count, or garbled ETB
; PLCBAS:RECVTOB has disabled itself, so we need not do that.
        ; assert: stacks are switched when we get here
        std     ASLD+asld:interruptPLCB save PLCB address
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill the character watchdog
;       clr     plcbas:state,x          exit receive mode
        incd    plcbas:missedETBcount,x bump goof count
        bra     asldi:decidenextoperationj2 go enable the receiver
        page
asldir:charactertimeoutinterrupt ; come here via PLCBASC:CHARACTERTIMEOUT interrupt
; This happens if the remote node fails to send us another message character
; soon enough, or if the receiver hardware dies.
; It can happen if we aren't in sync when we start reciept of a message,
; or if the message has malformed header, bad data count, or garbled ETB
; PLCBAS:CHARACTERTIMEOUT has disabled itself, so we need not do that.
        ; assert: stacks are switched when we get here
        std     ASLD+asld:interruptPLCB save PLCB address
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
;       clr     plcbasc:charactertimeout+timeout:fuse+1,x kill the character watchdog
;       clr     plcbas:state,x          exit receive mode
        incd    plcbas:missedETBcount,x bump goof count
        jsr     asldir:sendNAK          send NAK to remote node if identifiable
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB kill off the message timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:recvtob,x        form address of TOB to kill
        fin
        jsr     SDOSRT+rt:rtmo          remove the timeout
asldi:decidenextoperationj2
        jmp     asldi:decidenextoperation go enable the receiver
        page
asldir:sendNAK ;subroutine to send negative acknowledgement in response to message
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldx     plcbas:nrbptr,x        send NAK at next opportunity
        beq     asldir:sendNAKrts      b/ can't, MSGCRCID not verified
        stx     asldv:interruptnrb     remember which NRB is involved
        clr     plcbas:nrbptr,x        zero the receiving NRB pointer in PLCB
        clr     plcbas:nrbptr+1,x
        ldaa    plcbas:msgto,x         a broadcast message ?
        beq     asldir:sendNAKrts      b/ yes, can't send NAK back
        ldaa    nrb:state,x            what state is NRB in ?
        cmpa    #nodestate:converses   expected to send next ?
        beq     asldir:sendNAKrts      b/ yes, we will send msg soon anyhow
        jsr     asldi:removenrbfromxmitq take NRB out of xmit queue
        ldaa    nrb:state,x            what state is NRB in ?
        cmpa    #nodestate:conversei   was NRB idle ?
        beq     asldir:sendNAK3        b/ yes, go check for local delays
        cmpa    #nodestate:converser   were we in "expect to receive next" ?
        bne     asldir:sendNAKnow      b/ some other state, respond ASAP
asldir:sendNAK3 ; change state to "expect to send" next
        ldaa    #nodestate:converses   yes, change to "expect to send"
        staa    nrb:state,x            (this prevents unwanted TIMEOUT errors)
asldir:sendNAK1a ; state is now CONVERSES
        ldd     sdos+sdos:clock+1      fetch current time in ticks
        subd    nrb:datasenttimestamp,x any data recently sent ?
        bned    asldir:sendNAKnow      no, safe to send response now
        ldaa    nrb:xbuflk,x           is transmit buffer locked ?
        beq     asldir:sendNAKnow      b/ no, apparantly nobody filling buffer
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbasc:respondtime,x  yes, looks like new data being prepared
asldir:sendNAK2 ; insert NRB into XMITQ with delay (B)
        jsr     asldi:putnrbinxmitq    go stuff it in! it will soon be handled
asldir:sendNAKrts
        rts                            no error possible

asldir:sendNAKnow ; insert NRB into XMITQ NOW
        clra                           form 16 bit zero
        clrb
        jmp     asldi:putnrbinxmitq    go stuff it in!
        page
asldir:inputtimeoutinterrupt ; come here via PLCBAS:RECVTOB timeout interrupt
; only when committed to receive and expected receive data did not arrive.
; This can happen if a message has been aborted by its sender,
; or if we synchronize on a false message start in the middle of a message,
; or when a message ends before the place we think the CRC should be
; PLCBAS:RECVTOB has self-disabled when it causes control to arrive here
        ; assert: stacks are switched when we get here
        std     ASLD+asld:interruptPLCB save pointer to PLCB
        clr     plcbasc:charactertimeout+timeout:fuse,x kill character watchdog
;       clr     plcbas:state,x         exit the receiving state
        incd    plcbas:noclockcount,x  bump number of times "TMO" occurs
        jsr     asldir:sendNAK         try to notify other node of our problem
        bra     asldi:decidenextoperationj2 ignore message, see if we need to send
        page
asldir:waitETBbyte ; wait for ETB byte to arrive
; We don't know what the next byte it, because we are out of sync with message
; In particular, we don't know if the next byte is the ETB or not,
; so we set up a timeout on each character in case it was the ETB,
; and it got garbled.  We may or may not have a timeout set on entire message.
asldir:waitknownbyte ; wait for byte to arrive; we are in sync with message
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbasc:characterticks,x (4+1~) max allowable delay...
        staa    plcbasc:charactertimeout+timeout:fuse+1,x (4+1~)
        puld                           (5+2~) get where to go on interrupt
        std     plcbas:ilinputintPC,x  (5+1~)
        rti                            (15~) and exit the interrupt routine
;                                      Total: 39~ for 6809
        page
asldir:waitfirstcrcdbyte ; come here to wait for 1st byte which must be crc'd
        ldd     #plcbas:waitbytedocrc  (3~) set up character timeout trap...
        std     plcbas:ilinputintPC,x  (5+2~) just once, to save overhead
asldir:waitbyteandcrc ; wait for known byte to arrive and compute its crc
; this entry called ONLY if are in sync with message
; --> a timeout has already been set on receiving the entire message
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbasc:characterticks,x (4+1~) maximum amount of time to wait
        ; note: ...charactertime must be LONGER than 1 character time by epsilon
        staa    plcbasc:charactertimeout+timeout:fuse+1,x (4+1~) set timeout on patience for character
        puld                           (5+2~) get where to return after doing CRC
        std     plcbas:waitbyteandcrcreturn,x (5+2~)
        ; We don't need to try to transmit, because we are recieving NOW.
        rti                            (15~) and exit the interrupt routine
;                                      Total: 39~ for 6809

plcbas:waitbytedocrc ; do CRC on received byte and return to caller
; ***** Fast CRC generation code (for detail, see asldix:crcandxmit *****
; Computation is done in-line to save 4%(?) of CPU while RECVing at 9600 baud
; adjust COMPUTEDCRC according to byte in (A), destroys (A)
; The (A) is preserved as we have yet to store it into a buffer.
; Assert: (X) has PLCB pointer in it already
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        if      m6809
        ldb     plcbas:computedcrcupper,x (4+1~) fetch CRCTABLE offset needed
        ldu     #crctable+128          (3~) set up to index CRCTABLE
        leau    b,u                    (4+1~) form CRCTABLE slot address
        leau    b,u                    (4+1~) = CRCTABLE + 2 * CRCUPPER
        ; Note: CRC table is built with first 128 slots being indexed when
        ; high bit of CRCUPPER is set because of way that "leau b,u" works
        ldab    plcbas:computedcrclower,x (4+1~) fetch lower half of current CRC
        eorb    ,u                     (4+0~) form new upper byte of CRC
        stab    plcbas:computedcrcupper,x (4+1~) save new upper byte of CRC
        eora    1,u                    (4+1~) form new lower byte of CRC
        staa    plcbas:computedcrclower,x (4+1~)
        eora    1,u                    (4+1~) regenerate byte to transmit
        jmp     [plcbas:waitbyteandcrcreturn,x] (3+4~) return to caller
        ; overhead here is 54~ for 6809
        else    m6800!m6801!m6811
        psha                           (4~) save byte to CRC
        ldb     plcbas:computedcrcupper,x (5~) fetch CRCTABLE offset needed
        stab    asldv:crctable1+1      (5~) set lower half pointer
        stab    asldv:crctable2+1      (5~) set upper half pointer
        ldab    plcbas:computedcrclower,x (5~) fetch lower half of current CRC
        ldx     asldv:crctable1        (5~) fetch pointer to lower CRCTABLE slot
        eorb    #crctable&$FF,x        (5~) compute new upper byte of CRC
        ldx     asldv:crctable2        (5~) fetch pointer to upper CRCTABLE slot
        eora    #(crctable+256)&$FF,x  (5~) compute new lower byte of CRC
        ldx     ASLD+asld:interruptPLCB (5~) fetch pointer to Physical Link Control Block
        staa    plcbas:computedcrclower,x (5~) record new lower CRC byte
        stab    plcbas:computedcrcupper,x (5~) record new upper CRC byte
        ldab    plcbas:waitbyteandcrcreturn+1,x (5~) push return onto stack
        pula                           (4~) restore byte to send
        pshb                           (4~)
        ldab    plcbas:waitbyteandcrcreturn+1,x (5~) push return onto stack
        pshb                           (4~)
        rts                            (5~)
        ; overhead here is 86~ for 6800
        fin
        page
asldir:checkforbadcrc ; Come here to handle message header that looks strange
; (we hope it is merely a CRC error, and that the other node is not crazy).
; When we have received a message header, and are trying to process
; it so we can receive the message data bytes directly into a buffer,
; there is the possibility that that message header bytes have an
; unreasonable configuration.  Since the CRC bytes have not arrived yet,
; we can't trust any of the message header information to be error-free.
; So if we run into something that smells funny, it is easier (and safe) to
; beleive the CRC (coming up) must be wrong, than to beleive the other node
; is crazy.  This code listens to the rest of the message bytes,
; (but doesn't save any) and checks for a bad CRC.  If the CRC turns
; up as "bad", then the other node is vindicated, otherwise we
; declare the other node to be outright crazy and refuse to have
; anything else to do with him.
;       ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgxmitcount,x  = number of data bytes to receive
        beq     asldir:checkforbadcrc0 b/ no data bytes in message
        negd                           make it easy to count
        std     plcbas:msgpart1count,x
asldir:checkforbadcrcloop ; receive and ignore bytes
        jsr     asldir:waitbyteandcrc  go collect a byte and include in CRC
        ; if we get a timeout here, we simply go and watch for ETB
        inc     plcbas:msgpart1count+1,x down count # bytes to ignore
        bne     asldir:checkforbadcrcloop b/ more to receive
        inc     plcbas:msgpart1count,x down count # bytes to ignore
        bne     asldir:checkforbadcrcloop b/ more bytes to ignore
asldir:checkforbadcrc0 ; all message data bytes received
        bsr     asldir:waitknownbyte   wait for 1st CRC byte
        staa    plcbas:msgcrc,x        store message upper CRC byte
        bsr     asldir:waitknownbyte   wait for 2nd CRC byte
        staa    plcbas:msgcrc+1,x      store message lower CRC byte
        bsr     asldir:waitknownbyte   wait for ETB
        cmpa    #ascii:etb             does message end properly ?
        bne     asldir:ignoreuntiletb  b/ no, we must be out of sync
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill character watchdog
        ; Note: we can't kill off TOBs before we switch stacks,
        ; or we might overpush the 8 bytes alloted to us.
        inc     sdos+sdos:stackswitched perform stack switch
        bne     asldir:checkforbadcrc1 b/ stacks already switched
        ldx     sdos+sdos:currentask
        sts     tcb:stack,x
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB restore PLCB pointer to (X)
asldir:checkforbadcrc1
        clr     plcbasc:charactertimeout+timeout:fuse+1,x kill off character TIMEOUT
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB kill off the receive message timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:recvtob,x       form address of TOB to kill
        fin
        jsr     SDOSRT+rt:rtmo
        jsr     asldi:csma             check for <gap>
        ; If no carry on return, then we didn't hear <gap>!
        ; --> must be out of sync, other node is not crazy
        ; since we are out of sync, message is not real, don't send NAK
        bcs     asldir:ignoreuntiletb  we must be out of sync with message
        ; message seems well-formed, check for bad CRC
        ldd     plcbas:msgcrc,x        check: does rcvd crc match computed ?
        sec                            so we add one, CRC, and CRC complement
        adcb    plcbas:computedcrclower,x (rcvd crc s/b complement)
        bne     asldir:checkforbadcrc4 b/ CRC is bad, as expected
        adca    plcbas:computedcrcupper,x is CRC correct ?
        bne     asldir:checkforbadcrc4 b/ CRC is bad, as expected
        jmp     asldir:crazymsg        b/ CRC is fine, other node is crazy!

asldir:checkforbadcrc4 ; we found that CRC is bad, so must be bus noise in msg
        jmp     asldir:crcerror       go handle CRC error, ignore message
         page
;asldir:ignoremessage ; ignore message, it is not intended for us
; Come here when we determine message is not for us
; Pick up message header, with intent of grabbing SDLP:XMITCNT
; Our purpose in acquiring XMITCNT is so we know exactly how long
; the message is (we assume error rate on link is very low), and we
; can thus ignore exactly the balance of the message to the ETB.
; This prevents us from wasting time on ETB bytes embedded IN the message,
; at the small risk of picking up a garbage count (which is handled by
; a timeout).  Should a received byte be garbled (FRAMING error or data late)
; we give up trying to do an exact count, and simply hunt for ETB <gap>.
; Estimate: 20 insts/byte ignored --> 20% overhead at 38.4kb for ignored msgs
;       jsr     asldir:waitknownbyte   get "syn" byte and ignore
;       jsr     asldir:waitknownbyte   get "to" byte and ignore
;       jsr     asldir:waitknownbyte   get "from" byte and ignore
;       jsr     asldir:waitknownbyte   get duplicate "to" byte and ignore
asldir:ignoremessage1 ; ignore message starting at duplicate "from" byte
        jsr     asldir:waitknownbyte   get duplicate "from" byte and ignore
asldir:ignoremessage2 ; ignore message starting at SDLP:CNTRL byte
        jsr     asldir:waitknownbyte   get SDLP:CNTRL byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:RCVDOK upper byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:RCVDOK lower byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:RCVRDY upper byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:RCVRDY lower byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:XMTCNT upper byte
        staa    plcbas:msgxmitcount,x  and save
        jsr     asldir:waitknownbyte   get SDLP:XMTCNT lower byte...
        staa    plcbas:msgxmitcount+1,x and save
asldir:ignoremessagebody ; ignore body of message
        ldd     plcbas:msgxmitcount,x  any data sent in message ?
        beqd    asldir:ignorecrc       b/ data portion empty, ignore CRC
        negd                           make it possible to efficiently...
        std     plcbas:msgpart1count,x "decrement" counter
        jsr     asldir:waitknownbyte   get SDLP:XMTBASE upper byte and ignore
        jsr     asldir:waitknownbyte   get SDLP:XMTBASE lower byte and ignore
asldir:ignoremessagebyte ; ignore byte from body of message
        jsr     asldir:waitknownbyte   wait for next message data byte
        inc     plcbas:msgpart1count+1,x down count remaining data
        bne     asldir:ignoremessagebyte b/ more message to ignore
        inc     plcbas:msgpart1count,x down count remaining data
        bne     asldir:ignoremessagebyte b/ more message to ignore
asldir:ignorecrc ; ignore CRC and ETB bytes
        jsr     asldir:waitknownbyte   wait for 1st CRC byte
        jsr     asldir:waitknownbyte   wait for 2nd CRC byte
        jmp     asldir:waitforetb      go wait for ETB character
        page    Receive Message Interrupt Routines
asldir:msgstart ; come here when 1st byte of a message arrives
; We expend very little energy (overhead) on messages that are not for us
; We expend more (i.e., doing CRC computations, etc.) on those that are
        cmpa    #ascii:syn             apparant message start ?
        bne     asldir:ignoreuntiletb  b/ no, garbled SYN or out of sync
        dec     plcbas:state,x         commit us to receive (prevents transmit)
        clr     plcbas:nrbptr,x        remember that no NRB selected yet
        clr     plcbas:nrbptr+1,x
        ldd     #asldir:inputtimeoutinterrupt where to go if expected data...
        std     plcbas:recvtob+tob:routine,x doesn't arrive in message time
        ldd     #asldir:charactertimeoutinterrupt where to go if individual characters
        std     plcbasc:charactertimeout+timeout:routine,x don't arrive
        if      showroughedges
? ; It is unfortunate that we must have stacks switched to call timer routines
; It costs a lot of code, and it adds a bunch of extra overhead we just don't
; need right here. Perhaps clock routines could be designed to work assuming
; that only 6 bytes (remember, the return address uses up 2?) are available
; on the stack? Then I could get rid of a lot of useless stack switches.
        fin
        inc     sdos+sdos:stackswitched switch stacks so we can call SDOSRT
        bne     asldir:msgstart0a      b/ stacks already switched
        ldx     sdos+sdos:currentask   save current stack pointer
        sts     tcb:stack,x            (what a rotten place to have to do all this!)
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldir:msgstart0a ; now stacks are switched
        if      m6800!m6801!m6811
        ldd     plcbas:messagetime,x   fetch max time for message
        pshd
        ldd     ASLD+asld:interruptPLCB form address of message timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        puld                           fetch desired delay
        else    (m6809)
        ldd     plcbasc:messagetime,x  fetch max time for message
        leax    plcbas:recvtob,x       form address of message timeout
        fin
        jsr     SDOSRT+rt:stmo         set high accuracy timeout
; Note: NRB:RECVTOB must be left running even if PLCBAS:RECVTOB is running,
; because when we start receiving a message, we are not sure it is valid,
; it is for this node, or if it is from the node this NRB represents.
        dec     sdos+sdos:stackswitched unswitch stacks
        bpl     asldir:msgstart0b      b/ don't need unswitching
        ldx     sdos+sdos:currentask   switch back to user stack
        lds     tcb:stack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldir:msgstart0b ; now we are switched back to original stack
        jsr     asldir:waitknownbyte   wait for "to" byte to arrive
        staa    plcbas:msgto,x         save it
        bmi     asldir:ignoreuntiletb  b/ illegal node id, perhaps out of sync?
                                       ; if in sync, then sending node is crazy
        jsr     asldir:waitknownbyte   wait for "from" byte to arrive
        staa    plcbas:msgfrom,x       remember who sent the message
        ble     asldir:ignoreuntiletb  b/ illegal node id, perhaps out of sync?
        ldx     plcbasc:NITptr,x       information about Network stored here
        cmpa    NIT:nodeid,x           is the message "from" us ?
                                       ; this test is cheap at this point
        beq     asldir:ignoreuntiletb  b/ yes, must be out of sync
        jsr     asldir:waitknownbyte   wait for message id crc upper half
        cmpa    plcbas:msgto,x         match who the message is for ?
        bne     asldir:ignoremessage1  b/ garbled message id
        ; note: low error probability --> xmtcnt is probably correct, so use it!
*       staa    plcbas:msgidcrc,x      save upper half
        ; do some work now (while we have plenty of time) that we will need
        ; later (when we we are pressed for time; this evens out the load)
        clr     plcbas:computedcrcupper,x reset the message crc
        clr     plcbas:computedcrclower,x (all 16 bits of it!)
        ldd     ASLD+asld:interruptPLCB form address of message head buffer
        addd    #plcbas:msgcontrol     = address of next byte to fill
        std     plcbas:msgheadptr,x
        ldb     #plcbas:msgxmitbase-plcbas:msgcontrol # bytes in message head
        stb     plcbas:counter+1,x
        jsr     asldir:waitknownbyte   wait for message id crc lower half
*       staa    plcbas:msgidcrc+1,x    save lower half
        cmpa    plcbas:msgfrom,x       from address correct ?
        bne     asldir:ignoremessage2j b/ no, can't ID sender, ignore message
        incd    plcbas:goodidcount,x   bump # good message IDs required
        page
        ; Determine which NRB will be affected by the message
        ; Also determine if TO/FROM fields are valid
        ; We do this here to spread the computing energy invested in
        ; receiving the message out as much as possible.
        ; (We could do it after receiving the head part of the message,
        ; but the extra work might make us late and we would lose the message)
        ldab    plcbas:msgto,x         see who message is for
        bne     asldir:msgstart1       b/ not a broadcast message
        ldx     plcbasc:NITptr,x       broadcast, make NRB 0 receive message
        ldx     NIT:NodeLookupBase,x   points to NRB 0
        beq     asldir:ignoremessage2j b/ not allocated! message gets lost...
        ldaa    nrb:state,x            is NRB 0 ready for message receipt ?
        cmpa    #nodestate:initiating  (in this state when ready to receive)
        ; assert: nrb:state changes to nodestate:resynched after msg receipt
        ;         so we won't receive a second broadcast until 1st is processed
        bne     asldir:ignoremessage2j b/ no, ignore broadcast message
; perhaps link driver could have a nicer way of receiving broadcasts...
; if each NRB had a broadcast receiver buffer, then all broadcasts from
; a node could be caught.
        stx     asldv:interruptnrb     record which NRB will receive message
        ldd     asldv:interruptNRB     save NRB to use in PLCB
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:nrbptr,x        remember which NRB to use
        ; We need not set PLCBAS:NRBINDEXPTR because PLCBAS:NRBPTR is <>0
        bra     asldir:msgheadloop     allocated, go receive broadcast msg

asldir:ignoremessage2j
        jmp     asldir:ignoremessage2
        page
asldir:msgstart1 ; message is not a broadcast
        ldx     plcbasc:NITptr,x       locate information about Net
        cmpb    NIT:nodeid,x           is message intended for us ?
        bne     asldir:ignoremessage2j b/ no, so ignore the message
;       ldaa    plcbas:msgfrom,x       valid node ID? can we retain the message ?
        cmpa    NIT:MaxNodeID,x        sender node ID too large for us to handle ?
        bhs     asldir:ignoremessage2j b/ not enough NRB slots, ignore message

        asla                           form word index (all node IDs are <=127)
        leax    NIT:NodeLookupBase,x   so (X) points to slot for NRB 0
        if      m6809
        leau    a,x                    determine NRB index slot pointer
        leau    a,u                    *4 because of NITNDE:Size
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        stu     plcbas:nrbindexptr,x   save pointer to NRB slot
        ldd     0,u                    fetch pointer to NRB from slot
        std     plcbas:nrbptr,x        save pointer to receiving NRB
        ; Note: PLCBAS:NRBPTR tells ASLDIR:SENDNAK who to send a negative
        ; response to if it is nonzero: this causes some trick code elsewhere
        ; that is related to using PLCBAS:NRBPTR. Be aware.
        ; Note: the NRBINDEX scheme allows us up to 128 nodes, as the receiving
        ; node need not have an NRB for itself! It only needs NRBs for those
        ; nodes with which it desires to communicate, so if it has a slot
        ; for NRB#1 (NRB#0 is for broadcasts, remember?), then it has
        ; sufficient table space to talk to another node.  The implications
        ; are that popular nodes (like fileservers) should have very low
        ; node numbers, and should have large NRBINDEX tables.
        elseif  m6801!m6811
        tab                            set up for ABX
        abx                            form pointer to Nth NRB
        abx                            *4 because of NITNDE:Size
        ldd     0,x                    fetch pointer to NRB from slot
        pshx                           set up to store NRB index slot pointer
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:nrbptr,x        save pointer to receiving NRB
        puld                           grab NRB slot address again
        std     plcbas:nrbindexptr,x   save pointer to NRB slot
        ldd     plcbas:nrbptr,x        inspect NRB pointer
        else    (m6800)
        stx     asldv:tempx            save NIT pointer
        asla                           *4 because of size of NITNDE:Size
        bcc     asldir:msgstart1a      b/ no carry
        inc     asldv:tempx
asldir:msgstart1a ; asldv:tempx points to NRB index slot
        adda    asldv:tempx+1          some things the 6800 does badly
        staa    asldv:tempx+1
        bcc     asldir:msgstart1b      b/ no carry
        inc     asldv:tempx
asldir:msgstart1b ; asldv:tempx points to NRB index slot
        ldd     asldv:tempx            set up to store NRB index slot pointer
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:nrbindexptr,x   save pointer to NRB slot
        ldx     plcbas:nrbindexptr,x   so (X) points to NRB slot
        ldd     0,x                    fetch pointer to NRB from slot
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:nrbptr,x        save pointer to receiving NRB
        fin
; when there is no NRB assigned, it must be that we have not heard from
; the sender before.  We receive balance of message and verify that it
; is an INITIATING request before we assign an NRB.  If the message type
; if not INITIATING, the sender must be crazy so we simply ignore the message.
        beqd    asldir:msgheadloop     b/ no NRB is assigned to handle this
        ldx     plcbas:nrbptr,x        set (X) to access NRB
        ldaa    nrb:state,x            if node rep is marked as 'dead',
        cmpa    #nodestate:dead        then ignore any messages for it
        lbeq    asldir:ignoremessagebody b/ a zombie! run for your life...
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        page
asldir:msgheadloop ; loop to collect message head bytes
        jsr     asldir:waitfirstcrcdbyte (8~) wait for next byte to arrive
        sta     [plcbas:msgheadptr,x]  (4+4~) save the data byte
        incd    plcbas:msgheadptr,x    (6+1+3~) advance the buffer pointer
        dec     plcbas:counter+1,x     (6~) decrement # bytes to collect
        bne     asldir:msgheadloop     (3~) b/ message header not filled yet
        ldd     plcbas:msgxmitcount,x  get number of data bytes transmitted
        std     plcbas:datasavedcount,x save as amount of data placed in ring
        lbeq    asldir:crc             b/ no data sent, go fetch crc
        ldd     plcbas:nrbptr,x        NRB assigned yet ?
        ; no NRB assigned --> this is first time we have heard from sender
        ; so this should be an INITIATING message, which MUST have a zero
        ; plcbas:msgxmitcount to be valid.
        beqd    asldir:checkforbadcrcj2 b/ no, must be garbled message
        ldaa    plcbas:msgcontrol,x    is this an INITIATING request ?
        cmpa    #sdlpcontrol:initiating ...?
        ; all other message types can carry data without causing buffer sync troubles
        bne     asldir:msghead1        b/ no, message can have data content
asldir:checkforbadcrcj2
        jmp     asldir:checkforbadcrc
        page
asldir:msghead1 ; Collect the data portion of the message
        jsr     asldir:waitbyteandcrc  get xmit base, upper byte
        staa    plcbas:msgxmitbase,x
        ; do some setup for receiving message data into buffer here...
        ; to help distribute the computational load over rcv'd bytes
        clr     plcbas:msgexcesscount,x assume buffer space for all data in msg
        clr     plcbas:msgexcesscount+1,x (i.e., we throw away 0 bytes)
        ldx     plcbas:nrbptr,x        set up default buffer fill pointers
        ldd     nrb:ringinsdter,x      = - # bytes to end of ring
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart1count,x store count to end of ring
        ldx     plcbas:nrbptr,x        find NRB
        ldd     nrb:ringinstore,x      where to start filling
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart1ptr,x
        ldx     plcbas:nrbptr,x        find NRB
        ldd     nrb:ringinbase,x       where to continue filling
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart2ptr,x
        clr     plcbas:msgpart2count,x assume we don't fill 2nd part of ring
        clr     plcbas:msgpart2count+1,x
        jsr     asldir:waitbyteandcrc  get xmit base, lower byte
        staa    plcbas:msgxmitbase+1,x
        page
; If we checked for available space on the fly, then space becoming
; newly available as a result of the listener task sucking the buffer
; dry could be used.  Because of the overhead of inspecting the NRB
; on every data character interrupt, we choose not to do this,
; so we calculate available buffer space at a particular instant
; in time and use that to recieve the message.
;
; With a buffered asynch serial parts, we have about 2 byte times here.
; Even at 38.4 kb, this is about 1/2 millisecond, so there should be
; enough time to precompute all parameters required to receive the
; message directly into the the NRB ring buffer.  This makes
; receiving the message very efficient.
;
; We know which NRB is to capture the data, and where
; in the virtual stream the data belongs, so we can determine
; where to put received data in the NRB's input ring buffer.
; if transmitting node is not crazy, we can divide his
; transmitted data into 3 parts:
;     1) bytes we have already received and recorded in the ring
;           (plcbas:redundantcount)
;     2) bytes we haven't received and are space for
;           (plcbas:datasavedcount)
;        a) bytes that fill from current ring position to end of ring
;           (plcbas:msgpart1ptr for plcbas:msgpart1count)
;        b) bytes that fill from start of ring to end of free ring room
;           (plcbas:msgpart2ptr for plcbas:msgpart2count)
;     3) bytes we haven't received but have no space for
;           (plcbas:excessdatacount)
        page
*                           ...data... part of message
* -------------------------------------------------------------------------
* ! <-- redundantcount --> ! <-- datasavedcount --> ! <-- excesscount --> !
* -------------------------------------------------------------------------
* ^                        ^                                              ^
* !                        !                                              !
* ! <----------------------- plcbas:msgxmitcount -------------------------> !
* !                        !
* \                    nrb:rvpos
*  \
*   plcbas:msgxmitbase
*
*
*       if plcbas:msgxmitbase <= nrb:rvpos ...
*       and plcbas:msgxmitbase+plcbas:msgxmitcount > nrb:rvpos
*       then
*            ignorecount:=nrb:rvpos-plcbas:msgxmitbase
*            saveddatacount:=min(availablebufferspace,...
*                                plcbas:msgxmitcount-redundantcount
*            excesscount = plcbas:msgxmitcount-(saveddatacount+redundantcount);
*       else
*            message CRC is bad or other node is crazy
*       fi
        ldd     plcbas:msgxmitbase,x   virtual position of 1st data byte sent
        ldx     plcbas:nrbptr,x        determine how many bytes to ignore
        subd    nrb:rvpos,x            virtual position of 1st unreceived byte
        ; transmitting node's logical XMITBASE must be <= our logical RVPOS
        if      m6801!m6811!m6809
        bhi     asldir:checkforbadcrcj b/ diff > 32767, message is bad
        else    m6800
        bhi     asldir:checkforbadcrcj b/ diff > 32767, message is bad
        bmi     asldir:msghead2a       b/ diff <= 32767, is ok
        tstb
        bne     asldir:checkforbadcrcj b/ diff > 32767, message is bad
asldir:msghead2a
        fin
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:counter,x       save -redundantcount
        ldd     plcbas:msgxmitcount,x  get number of bytes being sent
        addd    plcbas:counter,x       = data bytes not already collected
        ; Once a node has sent a byte at virtual position P, further messages
        ; from that node must include bytes at positions P and greater.
        bhs     asldir:checkforbadcrcj b/ other node sent fewer than last time!
        std     plcbas:datasavedcount,x save data bytes not already collected
        ldx     plcbas:nrbptr,x        determine how many bytes to ignore
        subd    nrb:ringinroom,x       get max storable data count
;       assert: plcbas:msgexcesscount = 0  (# bytes we don't have room for)
        blo     asldir:msghead3        b/ buffer has room enuf for all msg data
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgexcesscount,x store # bytes we don't have room for
        ldd     plcbas:datasavedcount,x revise # bytes we save down
        subd    plcbas:msgexcesscount,x
        std     plcbas:datasavedcount,x true number of bytes we can save
asldir:msghead3
        ldd     plcbas:datasavedcount,x # bytes we will place in ring
        ldx     plcbas:nrbptr,x        which NRB is involved
        addd    nrb:ringinsdter,x      add -(distance to end of ring)
        bhs     asldir:msghead4        b/ msg data won't run off top of ring
        negd                           msg data will fill to top of ring
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        std     plcbas:msgpart2count,x determine limit on filling ring base
        ; assert: plcbas:msgpart1count=-(nrb:ringinbase-nrb:ringinlen)
        bra     asldir:msghead5

asldir:checkforbadcrcj
        jmp     asldir:checkforbadcrc

asldir:msghead4 ; message data won't run off top of ring
        ldd     plcbas:datasavedcount,x message data won't run off top of ring
        negd
        std     plcbas:msgpart1count,x
        ; assert: plcbas:msgpart2count=0 --> 0 bytes to fill
asldir:msghead5 ; all ring fill parameters are now computed
        page
; Now, we know where to put everything as it arrives.  Capture it!
        ldd     plcbas:counter,x       # bytes to ignore before we save data
        beq     asldir:datapart1       b/ don't ignore any bytes
asldir:ignoreredundantdataloop
        jsr     asldir:waitbyteandcrc  get data byte
        inc     plcbas:counter+1,x     down count number of bytes to ignore
        bne     asldir:ignoreredundantdataloop b/ more to ignore
        inc     plcbas:counter,x       down count number of bytes to ignore
        bne     asldir:ignoreredundantdataloop b/ more to ignore
asldir:datapart1 ; pick up data to be stored in ring buffer
        ldd     plcbas:msgpart1count,x where to store data byte in ring buffers
        beqd    asldir:ignoreexcess    b/ DATASAVEDCOUNT=0, go see if excess bytes
asldir:datapart1loop ; loop to collect 1st part of message data bytes
        jsr     asldir:waitbyteandcrc  wait for data byte to arrive
        sta     [plcbas:msgpart1ptr,x] store byte
        if      m6800!m6801!m6811
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        fin
        incd    plcbas:msgpart1ptr,x   advance pointer
        inc     plcbas:msgpart1count+1,x down count # bytes to recieve
        bne     asldir:datapart1loop   b/ no, go collect some more bytes
        inc     plcbas:msgpart1count,x down count # bytes to recieve
        bne     asldir:datapart1loop   b/ no, go collect some more bytes
        ldd     plcbas:msgpart2count,x size of 2nd chunk
        beqd    asldir:ignoreexcess    b/ yes
asldir:datapart2loop ; loop to collect 2nd part of message data bytes
        jsr     asldir:waitbyteandcrc  wait for data byte to arrive
        sta     [plcbas:msgpart2ptr,x] store byte
        if      m6800!m6801!m6811
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        fin
        incd    plcbas:msgpart2ptr,x   advance pointer
        inc     plcbas:msgpart2count+1,x down count # bytes to recieve
        bne     asldir:datapart2loop   b/ no, go collect some more bytes
        inc     plcbas:msgpart2count,x down count # bytes to recieve
        bne     asldir:datapart2loop   b/ no, go collect some more bytes
asldir:ignoreexcess ; no room to save more data bytes in buffer
        ldd     plcbas:msgexcesscount,x pick up number of bytes left
        beqd    asldir:crc             b/ all data received, go check CRC
        negd                           make an efficient counter
        std     plcbas:counter,x
asldir:ignoreexcessloop ; loop here, throwing bytes away
        jsr     asldir:waitbyteandcrc  wait for data byte to arrive
        inc     plcbas:counter+1,x     down count bytes ignored in the message
        bne     asldir:ignoreexcessloop b/ more bytes to ignore
        inc     plcbas:counter,x       down count bytes ignored in the message
        bne     asldir:ignoreexcessloop b/ more bytes to ignore
        page
asldir:crc ; receive crc bytes of message
; To believe a message is real, one must not only collect the CRC,
; but must check to make sure CRC is followed by ETB and then <silence>.

; Otherwise, a message image packed INTO data part of a message...
; might fool us into beleiving the image was real!
        jsr     asldir:waitknownbyte   get next byte, *** don't crc it ***
        staa    plcbas:msgcrc,x
        jsr     asldir:waitknownbyte
        staa    plcbas:msgcrc+1,x
        jsr     asldir:waitknownbyte
        cmpa    #ascii:etb             does message end properly ?
        bne     asldir:ignoreuntiletbj1 b/ no, we must be out of sync
;       call    plcbasc:ildisii,x      disable input interrupt
        inc     sdos+sdos:stackswitched switch stacks so we can call SDOSRT
        bne     asldir:msgcrc0a        b/ stacks already switched
        ldx     sdos+sdos:currentask   save current stack pointer
        sts     tcb:stack,x            (what a rotten place to have to do all this!)
        ldx     sdos+sdos:configuration
        lds     cnfg:interruptstack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldir:msgcrc0a ; now stacks are switched
        clr     plcbasc:charactertimeout+timeout:fuse,x kill character watchdog
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of message timeout
        addd    #plcbas:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        leax    plcbas:recvtob,x       form address of message timeout
        fin
        jsr     SDOSRT+rt:rtmo         kill message timeout
        jsr     asldi:csma             check for quiet bus (also disables input interrupt
        bcc     asldir:crccheck        b/ bus is quiet, we found valid end msg
        ; got to end of message, and were out of sync
        jsr     asldir:sendNAK         try to send negative acknowledge
        dec     sdos+sdos:stackswitched unswitch stacks
        bpl     asldir:msgcrc0b        b/ don't need unswitching
        ldx     sdos+sdos:currentask   switch back to user stack
        lds     tcb:stack,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
asldir:msgcrc0b ; now we are switched back to original stack
        clr     plcbas:state,x         exit receive state
        incd    plcbas:outofsynccount,x count # times we were out of sync
;       jsr     plcbasc:ilenbii,x      re-enable reciever logic
asldir:ignoreuntiletbj1
        jmp     asldir:ignoreuntiletb  go hunt for ETB <gap> at end of message
        page
asldir:crcerror ; CRC is wrong on message
; We know SDLP:MSGIDCRC is ok, so we are sure message was for us.
; We can frame an immediate NAK to speed error recovery
        incd    plcbas:badcrccount,x   bump number of messages with bad CRCs
asldir:crcerror1 ; re-enter here from asldir:crazymsg
        jsr     asldir:sendNAK         no, send NAK to speed up error recovery
        jmp     asldi:decidenextoperation         go decide what to do next

asldir:crazymsg ; crazy message recieved (CRC is good, contents are nutzo)
; count the message as bonkers
; It is safe to believe that the CRC simply didn't detect an error in msg,
; (CRCs are very good, but there ARE some error patterns they can't detect)
; rather than to believe the remote node is insane, as long as we simply
; discard this message.
        ; There are a zillion ways to get here, with heavens knows what
        ; in the (X) register.  We solve the problem by setting (X) ourselves.
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        incd    plcbas:crazymsgcount,x update statistics
        bra     asldir:crcerror1       recover identically to CRC fault
        page
asldir:crccheck ; check for correct CRC on message
        clr     plcbas:state,x         exit recieve state
        ldd     plcbas:msgcrc,x        check: does rcvd crc match computed ?
        sec                            so we add one, CRC, and CRC complement
        adcb    plcbas:computedcrclower,x (rcvd crc s/b complement)
        bne     asldir:crcerror        b/ no, bad message!
        adca    plcbas:computedcrcupper,x CRC+~CRC+1 = 0 ?
        bne     asldir:crcerror        b/ no, bad message!
        ; looks like a good message has been received !
        incd    plcbas:goodcrccount,x  bump # good messages we have received
        ldx     plcbas:nrbptr,x        is this message from somebody new ?
        stx     asldv:interruptNRB     (remember which NRB for use from here on)
        bne     asldir:NRBassigned     b/ no, NRB already assigned for remote node
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbas:msgcontrol,x    only assign NRB if message is INITIATING
        cmpa    #sdlpcontrol:initiating is it ?
        bne     asldir:crazymsg        b/ not INITIATING, remote node is crazy
        ldd     plcbas:msgrvpos,x      make sure INITIATING message is not crazy
        bned    asldir:crazymsg        not sane, treat it like bad news
        ; assert: we are sure plcbas:msgxmitcount is zero here...
        ; because we checked it in ASLDIR:MSGHEADLOOP

        ; We assign the 1st NRB in the NRBCHAIN, which is a list of free NRBs.
        ; We know now that the message CRC is ok, that the remote node is
        ; not crazy, and the message is intended to establish a connection.
        ; Assert: NRBs in ASLDC:NRBCHAIN have NRB:STATE = NODESTATE:AVAILABLE,
        ;         and have all buffers initialized for xvpos, rvpos = 0

        ldx     sdos+sdos:configuration locate list of free NRBs
        ldx     cnfg:freeNRBs,x         pick 1st free NRB and use it
        bne     asldir:assignNRB        b/ there is an NRB available for this

        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        incd    plcbas:notenoughNRBscount,x NO AVAILABLE NRBs! count shortage
        jmp     asldi:decidenextoperation throw the message away
        page
asldir:assignNRB ; assign free NRB to remote node that sent msg
        ; assigned NRB is ready to receive a message.
        ; Processing the INITIATING message will cause a response to be sent, thus
        ; ensuring that a timeout will be established, and so the NRB
        ; cannot be tied up forever.
        stx     asldv:interruptnrb     save pointer to assigned NRB
        ldd     nrb:nextfreenrb,x      remove NRB from pool of free NRBs
        ldx     sdos+sdos:configuration locate list of free NRBs
        std     cnfg:freeNRBs,x        by removing NRB from head of free NRB list
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     asldv:interruptnrb     update NRBINDEX table to record assignment
        std     plcbas:nrbptr,x        set NRB pointer in PLCB correctly
        ldx     plcbas:nrbindexptr,x   points to NRBINDEX slot to update
        std     0,x                    now NRBINDEX knows about assigned NRB
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldab    plcbas:msgfrom,x       who the remote node is
        ldx     plcbasc:NITptr,x       determine network ID for NRB
        ldaa    NIT:netid,x            grab net ID
        ldx     asldv:interruptNRB     which NRB we just assigned
        staa    nrb:netid,x            remember his family for future reference
        stab    nrb:nodeid,x           remember his name for future reference
        ldaa    #nodestate:resynched   mark this node as "resynched"
        staa    nrb:state,x            so INITIATING request gets honored
        ldd     ASLD+asld:interruptPLCB which PLCB is in use right now
        std     nrb:plcb,x             pledge new NRB to current PLCB
        jsr     asldi:resetNRB         reset rest of NRB so it is ready for use

        if      showroughedges
? ; >>> Listener task de-assigns dead NRB fron NRBINDEX slot
; >>> Also, Listener task eventually puts dead NRB back into free list.
        fin
        page
asldir:NRBassigned ; (X) points to assigned NRB
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbas:msgto,x         broadcast message ?
        bne     asldir:notbroadcast    b/ no, must be normal data message
        ldaa    plcbas:msgcontrol,x    check message type for "broadcast"
        cmpa    #sdlpcontrol:initiatequit "Initiate and then quit" type?
        bne     asldir:crazymsgj1      b/ no, something's terribly wrong
        ldd     plcbas:msgrvpos,x      broadcast rcvd, ensure RVPOS = 0
        bned    asldir:crazymsgj1      b/ its not, sender is nuts
        ldd     plcbas:msgxmitcount,x  broadcast must send data bytes...
        beqd    asldir:crazymsgj1
        ldd     plcbas:msgxmitbase     and xmit base must be zero
        bned    asldir:crazymsgj1
        ldd     plcbas:msgexcesscount,x did we keep entire broadcast message ?
        ; assert: nrb:rvpos was zero --> plcbas:ignoredatacount is zero,
        ; so we did not ignore bytes at beginning of broadcast
        beq     asldir:broadcastsane  b/ captured entire broadcast message
        incd    plcbas:broadcasttoolongcount,x bump # rediculous broadcasts
asldi:decidenextoperationj
        jmp     asldi:decidenextoperation

asldir:broadcastsane ; broadcast fits into buffers and is sane
        jsr     asldir:receivebufferupdate record arrival of data in NRB
*       also, leave broadcast nrb in xmitq if it is already there
*       this ensures that it gets its chance to broadcast correctly
        clr     sdos+sdos:surprise     tell scheduler something happened
        jmp     asldi:decidenextoperation go wake up broadcast listener task
                                        ; then decide what to do next.        page
asldir:notbroadcast ; received a normal message with correct CRC
; Now check message acknowledgement information for sanity
; Remote node must not acknowledge more data than we last sent
; nor must it acknowledge less than than it acknowledged previously
*
*       if plcbas:msgrvpos - nrb:xvpos  > nrb:ringoutdata
*       or plcbas:msgrvpos < nrb:xvpos
*       then
*           remote node is crazy
*       else
*           remove acknowledged data from buffer (plcbas:msgrvpos-nrb:xvpos)
*           nrb:xvpos:=plcbas:msgxvpos
*       fi
*
        ldaa    plcbas:msgcontrol,x    INITIATING messages need not have sync'd ACK
        cmpa    #sdlpcontrol:initiating is it an INITIATING message ?
        bne     asldir:ack0            b/ not INITIATING, we must check ACK sync
        ; we are sure plcbas:msgxmitcount is zero here...
        ; because we checked it in ASLDIR:MSGHEADLOOP
        ldd     plcbas:msgrvpos,x      make sure INITIATING message is not crazy
        lbeq    asldir:checkmsgtype    sane INITIATING message, go process
asldir:crazymsgj1
        jmp     asldir:crazymsg        not sane, treat it like bad news

asldir:ack0 ; we must check ACK synchronization
        ldd     plcbas:msgrvpos,x      compute data acknowledged count
        ldx     plcbas:nrbptr,x        find NRB
        subd    nrb:xvpos,x
        bmi     asldir:crazymsgj1      b/ more than 32k bytes behind --> crazy!
        std     asldv:dataacknowledgedcount save amount of data acknowledged
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgrvpos,x      is plcbas:msgrvpos <= maxackrvpos ?
        ldx     plcbas:nrbptr,x        find NRB
        ; can't acknowledge more bytes than we sent last.
        ; note also that nrb:ringoutdata bytes might not all have been in
        ; the transmit buffer the last time we sent a message, so
        ; we can't check against nrb:ringoutdata.
        subd    nrb:maxackrvpos,x      reasonable acknowledgement ?
        bmi     asldir:crazymsgj1      b/ no, acknowledged too much!
        page
asldir:messagesane ; Hooray! normal message appears to be sane (remote node not crazy)
; Now it is safe to adjust NRB contents
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldaa    plcbasc:retrycount,x   remote node sent us something...
        ldx     plcbas:nrbptr,x        locate NRB again
        staa    nrb:retry,x            so set retry counter back to maximum
        clr     nrb:remotehasmore,x    assume that remote sent us all he had
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgrvpos,x      update the transmit virtual position
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:xvpos,x
        ldd     asldv:dataacknowledgedcount = amount remote node says he got
        beqd    asldir:ack3            b/ no data ack'd, don't bother with updates
        addd    nrb:ringoutroom,x      add newly freed room to available room
        std     nrb:ringoutroom,x
        ldd     nrb:ringoutdata,x      advance xmit buffer ptrs
        subd    asldv:dataacknowledgedcount = unacknowledged data left to send
        std     nrb:ringoutdata,x      decrease stored amount by ack'd amount
        ldd     nrb:ringoutfdter,x     distance left to end of ring
        addd    asldv:dataacknowledgedcount new distance left
        blo     asldir:ack1            b/ ringoutfetch pointer won't wrap
        pshd                           save count after pointer wraps
        ldx     plcbas:nrbptr,x        find NRB
        addd    nrb:ringoutlen,x       compute distance left after pointer wraps
        std     nrb:ringoutfdter,x
        puld                           count after pointer wraps
        addd    nrb:ringoutbase,x      new value for wrapped pointer
        std     nrb:ringoutfetch,x
        bra     asldir:ack2
        page
asldir:ack1 ; advancing ringoutfetch pointer won't wrap past top of ring
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:ringoutfdter,x     new value for distance to end ring
        ldd     nrb:ringoutfetch,x     advance ringoutfetch pointer...
        addd    asldv:dataacknowledgedcount by amount of data acknowledged
        std     nrb:ringoutfetch,x
asldir:ack2 ; acknowledged data has been purged from the ring
        ldaa    nrb:xbuflk,x           is somebody trying to transmit ?
        beq     asldir:ack3            b/ no, nobody can be waiting for space
        clr     sdos+sdos:surprise     yes, let possibly waiting task know about data
? ; ref Z80 listing for Event management code
asldir:ack3 ; data acknowledgement is now entirely handled
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgreadycount     save the received ready count
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:rcvdrdy,x          (used on next transmission)
        bsr     asldir:receivebufferupdate update receive buffer information
        page
asldir:checkmsgtype ; Inspect the message type to determine what should happen next.
        clr     plcbas:nrbptr,x        zero out PLCBAS:NRBPTR here so we don't...
        clr     plcbas:nrbptr+1,x      have to do it at each of the zillion exits below
        ; WARNING: FROM THIS POINT ON, USE OF PLCBAS:NRBPTR IS WRONG!
        ldd     plcbas:msgexcesscount,x adjust NRB:AVERAGEMSGEXCESS
        ldx     asldv:interruptnrb     locate NRB again
        addd    nrb:averagemsgexcess,x sum new excess and old average
        rord                           = new average
        std     nrb:averagemsgexcess,x keep running average
        jsr     asldi:removenrbfromxmitq we might not set another timeout on NRB
        ldaa    nrb:closelink,x        socket manager want to disconnect ?
        beq     asldir:checkmsgtype1   b/ no
        ldd     nrb:ringoutdata,x      any data left to send ?
        bne     asldir:checkmsgtype1   b/ yes, ignore disconnect request
        ldaa    nrb:state,x            no, are we in a state...
        cmpa    #nodestate:converses   where it is safe to disconnect ?
        beq     asldir:checkmsgtype2   b/ yes, go set up disconnect
        cmpa    #nodestate:converser   ...?
        beq     asldir:checkmsgtype2   b/ yes, go set up disconnect
        cmpa    #nodestate:conversei   ...?
        bne     asldir:checkmsgtype1   b/ no, ignore disconnect request
asldir:checkmsgtype2 ; safe to honor disconnect request when in this state
        ldaa    #nodestate:wantquit    switch states
        jmp     asldir:newstate        and send response instantly
        page
asldir:checkmsgtype1 ; no disconnect request, or not safe to honor it yet
        ldaa    nrb:state,x            does state force response ?
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldab    plcbas:msgcontrol,x    (get message type)
        cmpa    #nodestate:initiating  trying to activate this node ?
        lbeq    asldir:tryingtocontact b/ yes
        cmpb    #sdlpcontrol:initiatequit a broadcast style control code ?
        lbhi    asldir:sendquit        b/ not a valid control code, but crc is right !?
        aslb                           do table jump on control code
        if      m6800!m6801!m6811
        stab    asldv:control1+1
        ldx     asldv:control1
        ldab    (asldv:control&$ff)+1,x
        pshb
        ldab    asldv:control&$ff,x
        pshb
        ldx     asldv:interruptnrb     grab nrb pointer again
*       ldaa    nrb:state,x            for convenience of called routine
        rts                            go to control message handler
        else    (m6809)
        ldu     #asldv:control
        jmp     [b,u]                  with (A) = old NRB:STATE
        fin
        page
asldir:receivebufferupdate ; Subroutine to update the receive buffer pointers
; to reflect newly arrived data
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgxmitcount,x  any data received ?
        beqd    asldir:updatedone      b/ no, no adjustments required
        ldaa    plcbas:msgexcesscount,x how much data we ignored
        oraa    plcbas:msgexcesscount+1,x  set nonzero if remote has more
        ldx     asldv:interruptNRB     find NRB
        oraa    nrb:remotehasmore,x    combine with other sources of info
        staa    nrb:remotehasmore,x
        ; plcbas:datasavedcount holds number of bytes we put into ring.
        ; assert: we cannot get here with (X) pointing to broadcast NRB
        ; Now we have heard some data.  Should send ACK soon to remote node
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:datasavedcount,x = # data bytes we captured
        ; if we saved 0, received message must be a duplicate.
        ; we can skip adjusting the recieve buffer pointers, but more
        ; importantly, we can carefully NOT bother the listener task
        ; with an unnecessary wakeup.
        beqd    asldir:updatedone      b/ none saved, skip adjustments
        ldx     plcbas:nrbptr,x        find NRB
        addd    nrb:rvpos,x            adjust position in received virtual file
        std     nrb:rvpos,x            carry out is lost --> modulo 2^16, as spec'd
        ldd     nrb:ringindata,x       adjust amount of data in ring upward
        bned    asldir:update2         b/ lister task already knows that data available
        clr     sdos+sdos:surprise     wake up the listener task
; see Z80 code for Event control block update
asldir:update2
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        addd    plcbas:datasavedcount,x
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:ringindata,x       revise RINGINDATA count upward
        ldd     nrb:ringinroom,x       revise RINGINROOM count downward
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbas:datasavedcount,x
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:ringinroom,x
        ldd     nrb:ringinsdter,x      distance left to end of ring
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        addd    plcbas:datasavedcount,x new distance left
        bcc     asldir:update1         b/ ringinstore pointer won't wrap
        pshd                           save count after pointer wraps
        ldx     plcbas:nrbptr,x        find NRB
        addd    nrb:ringinlen,x        = distance left after pointer wraps
        std     nrb:ringinsdter,x
        puld                           count after pointer wraps
        addd    nrb:ringinbase,x       new value for wrapped pointer
        std     nrb:ringinstore,x
        rts                            done

asldir:update1 ; advancing ringinstore pointer won't wrap past top of ring
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:ringinsdter,x      new value for distance to end ring
        ldd     nrb:ringinstore,x      advance ringinstore pointer...
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        addd    plcbas:datasavedcount,x by amount of data captured
        ldx     plcbas:nrbptr,x        find NRB
        std     nrb:ringinstore,x
asldir:updatedone ; update of NRB buffers is complete
        rts                            no error possible
        page
; NOTE: ALL THE MESSAGE-TYPE SPECIFIC PROCESSING ROUTINES...
; TRY TO KEEP ASLDV:INTERRUPTNRB IN (X)
;
asldir:agreequitmsg ; "agree to quit" message received
        cmpa    #nodestate:wantquit    expect this message ?
        bne     asldir:croak           b/ no, go complain
asldir:sendquit ; send QUIT message and then die
        ldaa    #nodestate:quit        mark the node as dead
        bra     asldir:newstate        and go send "quit!" response (just ONCE!)

asldir:tryingtocontact ; we are trying to establish contact with remote node
;       ldab    plcbas:msgcontrol,x    now inspect message type
        ldx     plcbas:nrbptr,x        find NRB
        cmpb    #sdlpcontrol:initiating did we get "initiating" message ?
        beq     asldir:initiating1     b/ that's ok with us
        cmpb    #sdlpcontrol:resynched did we get proper response to "INITIATING" ?
        bne     asldir:instant         b/ no, go send INITIATING again
        ldaa    nodestate:initiated    yes: switch NRB to initiated state
        bra     asldir:newstate

asldir:initiatingmsg ; "initiating" conversation request arrived
        ; assert: plcbas:msgxmitcount=0 here (checked by asldir:msgheadloop)
;       ldab    plcbas:msgxmitcount,x  did we receive any data with "INITIATING" ?
;       orab    plcbas:msgxmitcount+1    ...?
;       bne     asldir:croak           b/ yes, that's total garbage...
        cmpa    #nodestate:resynched   "INITIATING" only legal if "resynched"
        beq     asldir:instant         b/ legal, tell remote "resynched"
; "INITIATING" conversation request from remote node.
; We are not expecting this, so it must be a new incarnation of remote node.
; --> We must kill this conversation, as it is with old incarnation.
        ldd     #err:newincarnation    grab error code
        bra     asldir:killforreason   then make it die
        page
asldir:croak ; got inappropriate mesage, other node must be crazy
        ldd     #err:nodecrazy         remember why NRB croaked
        std     nrb:epitaph,x
        ldaa    #nodestate:die         send remote node a DIE message...
        bra     asldir:newstate        in hopes he'll quit bothering us

asldir:diemsg ; remote node sent us a DIE message, he thinks we're crazy
        ldd     #err:nodesaiddie       record why we killed conversation
        bra     asldir:killforreason   then make it die
        page
asldir:initiating1 ; switch to "resynched" state
        ldaa    #nodestate:resynched   ok, switch to "resynched" state
asldir:newstate ; set new state to (a) and send instant response
        staa    nrb:state,x
asldir:instant ; send response instantly
        clra                           set time delay = 0
        clrb                           (form 16 bit zero)
        jsr     asldi:putnrbinxmitq
        jmp     asldi:decidenextoperation

asldir:resynchedmsg ; re-synchronized message was received
        cmpa    #nodestate:initiated   do we expect this response here ?
        bne     asldir:croak           b/ no, not legal here
        ; don't need to check for XMITBASE = 0, because it were not,
        ; then message would be declared as crazy by code that checks
        ; XMITCOUNT/XMITBASE virtual positions
        bra     asldir:instant         tell node we are "initiated"
        page
asldir:wantquitmsg ; "want to quit" request received
        ldaa    #nodestate:agreequit   go to "agree to quit" state
        bra     asldir:newstate        go set state and send instant response

asldir:quitmsg ; "quit!" message received
        ldd     #err:nodequit          record cause of NRB death
asldir:killforreason ; reason to kill node is in (C)
        ldx     asldv:interruptNRB     don't trust caller to set (X) correctly
        std     nrb:epitaph,x          DIE --> don't send reply, ever
asldir:dead ; other node croaked or went crazy, give up!
        jsr     asldi:removenrbfromxmitq so he becomes very uninteresting
        ldaa    #nodestate:dead        mark the node state appropriately
        staa    nrb:state,x
*       it doesn't do any good to reset buffer pointers,
*       since user/listener task might be fiddling with them right now.
*       So we wait for the listener task to discover that this NRB has
*       died, and we let it do the reset.
        jsr     asldi:signalkilled     notify any waiting tasks
        jmp     asldi:decidenextoperation set up to receive another message
        page
asldir:acksoon ; "acksoon" (normal data) message received
        cmpa    #nodestate:resynched   expect this message in this state ?
        blo     asldir:normal0         b/ no, see if WANTQUIT state
;       cmpa    #nodestate:initiated   has connection been firmly established ?
;       beq     asldir:acksoon1        b/ yes, normal message expected
;       cmpa    #nodestate:converser   does node expect to receive next ?
;       beq     asldir:acksoon1        b/ yes, normal message expected
;       cmpa    #nodestate:converses   does node expect to transmit next ?
;       beq     asldir:acksoon1        b/ yes, normal message expected
*       below test does all of the above, FORCES ORDERING ON NODESTATE VALUES!
        cmpa    #nodestate:conversei+1 is node idle ?
        bhs     asldir:normal0         b/ don't expect this message type now
;asldir:acksoon1
; remote node sends ACKSOON when he has more data to send to us.
; So we should respond as soon as we have enough buffer space
; for him to send us an efficient message. We defer message transmission
; if some task is filling the NRB buffer on the grounds that the filling
; process will surely complete quickly, with little effect on transmission
; efficiency, whereas if we started transmission instantly, we might
; actually have to send 2 data messages to carry all the data across,
; and that is demonstrably more inefficient than just one message.
;
; If we have just received an ACKSOON message:
;       Don't send a response if:
;            Never (he said, "acksoon").
;       Send a response now if:
;            ( NRB transmit buffer is full or
;              xbuflk is zero [no more data to be sent arriving from task])
;          and
;            ( we have some data and
;              he has enough room and
;              sending would be efficient
;                   or
;              we have enough room to receive an efficient message)
;       Send a response soon otherwise.
;
        staa    nrb:remotehasmore,x    ACKSOON means "remote has more"
        ldaa    #nodestate:converser   assume we will respond instantly
        staa    nrb:state,x            (i.e., we will xmit then wait for msg)
        ldd     nrb:ringoutroom,x      any output ring space left ?
        beqd    asldir:acksoon2        b/ no, send now if remote has enuf room
        ldaa    nrb:xbuflk,x           new data arriving that needs sending ?
        lbne    asldir:normalsoon      b/ yes, wait before sending
asldir:acksoon2
        ldd     nrb:ringinroom,x       do we have enough buffer space...
        if      showroughedges
? ; if we have no room, we should wait a long time before sending ?
        fin
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbasc:efficientmsgthreshold,x for him to send us an efficient msg ?
        bhs     asldir:instant         b/ yes, send to him now!
        ldx     asldv:interruptNRB     find NRB again
; We time-stamped the arrival in ringoutdata of the latest stuff,
; so we could tell if new data had arrived in the last clock tick or so.
; If not, assume no more is coming and simply send the stuff,
; efficient or not.  This helps small fragments at the tail end
; of large transmissions get through.
        ldd     sdos+sdos:clock+1      current time in 60Hz ticks
        subd    nrb:datasenttimestamp,x when was data last sent ?
        bned    asldir:instant         b/ a long time ago, just send this stuff
        ldd     nrb:ringoutdata,x      no, enough bytes to send efficient message?
;       beqd    asldir:normalsoon      b/ no data --> can't be efficient
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbasc:efficientmsgthreshold,x
        lblo    asldir:normalsoon      b/ not enough to send efficient message
        ldx     asldv:interruptNRB     find NRB again
        ldd     nrb:rcvdrdy,x          can send efficient message
        subd    nrb:ringoutdata,x      does receiver have room for all of it ?
        bhs     asldir:instant         b/ yes, send reply instantly
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbasc:efficientmsgthreshold,x no, would message be efficient anyway ?
; Even if we succeed in sending an efficient message now, the balance
; of the ring data might not be enough to send efficiently, and so
; we might delay, hoping for more. Thus the data takes 2 message overheads,
; one delay, and the time to transmit the data.  If we waited now
; and sent everything later, the total time would be smaller by one
; message delay.  On the other hand, if we are sending a lot, by
; the time this efficient message has been processed, more data will
; be in the ring and we will be able to send a second message of efficient
; size.  It seems like a wash, so I give up.
        bhs     asldir:instant         b/ yes, send anyway
        bra     asldir:normalsoon      b/ no, wait (he'll make more room soon)
        page
asldir:normal ; "normal" message received
        cmpa    #nodestate:resynched   expect this message in this state ?
        blo     asldir:normal0         b/ no, see if WANTQUIT state
;       cmpa    #nodestate:initiated   has connection been firmly established ?
;       beq     asldir:normal1         b/ yes, normal message expected
;       cmpa    #nodestate:converser   does node expect to receive next ?
;       beq     asldir:normal1         b/ yes, normal message expected
;       cmpa    #nodestate:converses   does node expect to transmit next ?
;       beq     asldir:normal1         b/ yes, normal message expected
*       below test does all of the above, FORCES ORDERING ON NODESTATE VALUES!
        cmpa    #nodestate:conversei+1 is node idle ?
        blo     asldir:normal1         b/ normal message acceptable
asldir:normal0 ; don't expect normal message now, remote probably missed a msg
        cmpa    #nodestate:wantquit    might be response to this if missed
        beq     asldir:instant         b/ yes, go send him our request again
        bra     asldir:sendquit        go send "QUIT" response (just ONCE)
        page
asldir:normal1 ; decide if we should respond right away or not
; "Normal" messages are frequent, so we are concerned about link traffic.
; We try not to send messages that are inefficient or waste energy
; (a waste is an efficient message to a node that has no buffer space)
; [note: inefficient or wasteful messages don't affect protocol reliability]
; for all other message types, we respond immediately (they are rare, so
; they add no significant burden to link traffic).
;
; If we have just received a NORMAL data message:
;       Don't send a response if:
;            remote sent us no data and
;            we have no data to send and
;            we don't need any data (?? is this really wise ??)
;            xbuflk is is not locked.
;       Send a response now if:
;            ( NRB transmit buffer is full or
;              xbuflk is not locked [no more data to be sent arriving from task])
;          and
;            ( we have some data and
;              he has enough room and
;              sending would be efficient )
;       Send a response soon otherwise.
;
        ldaa    #nodestate:converser   assume we will respond instantly
        staa    nrb:state,x            (i.e., we will xmit then wait for msg)
        ldaa    nrb:xbuflk,x           is data to be sent arriving in buffer ?
        bne     asldir:normal2         b/ yes, we have reason to send soon if not now
        ldaa    nrb:datarequiredflag,x does some task need data ?
        bne     asldir:normal2         b/ yes, force xmit no later than "soon"
        ldd     nrb:ringoutdata,x      any data bytes to send ?
        bned    asldir:normal2         b/ we have some data to send
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbas:msgxmitcount,x  did other node send us some data ?
        bned    asldir:normal2         b/ yes, send a response sometime
        ldx     asldv:interruptNRB     find NRB again
        ldaa    #nodestate:conversei   record that this NRB is idle
        staa    nrb:state,x
        bra     asldi:decidenextoperationj3 don't send a response
        page
asldir:normal2 ; we should send soon if not immediately
        ldx     asldv:interruptNRB     find NRB again
        ldd     nrb:ringoutroom,x      any output ring space available ?
        beqd    asldir:normal3         b/ no, we should send NOW
        ldaa    nrb:xbuflk,x           yes, is new data for xmit arriving ?
; there are two references to xbuflk in this code because it appear twice
; in the comment describing this code above
        bne     asldir:normalsoon      b/ yes, don't send right away
asldir:normal3 ; transmit buffer contents aren't changing...
; We time-stamped the arrival in ringoutdata of the latest stuff,
; so we could tell if new data had arrived in the last clock tick or so.
; If not, assume no more is coming and simply send the stuff,
; efficient or not.  This helps small fragments at the tail end
; of large transmissions get through.
        ldd     sdos+sdos:clock+1      current time in 60Hz ticks
        subd    nrb:datasenttimestamp,x when was data last sent ?
        bned    asldir:instant         b/ a long time ago, just send this stuff
        ldd     nrb:ringoutdata,x      do we have enough data to send...
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbasc:efficientmsgthreshold,x an efficient message ?
; Assume we have no data or only a little bit of data to send.
; If there was excess data, we don't have any buffer space right now,
; so there would be no point in sending an ACK [a message with an
; up-to-date Receive Virtual Position] now, as it would only convince
; the remote node that we have no buffer space, whereas if we wait just a bit,
; some of buffer space might become available, which would make
; the remote node a whole lot happier.  If there was no
; excess data, then we have captured everything the remote wanted
; to send to us, so he does not need an immediate reply to make
; progress (unless his buffer was full and he needs some room)
; So whether or not there was excess data, we
; need not send a message right away.
        blo     asldir:normalsoon      b/ no, don't send right away
        ldx     asldv:interruptNRB     find NRB again
        ldd     nrb:ringoutdata,x      we can send an efficient message.
        subd    nrb:rcvdrdy,x          does remote node have room for it ?
        blo     asldir:instant         b/ remote has plenty of room
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        subd    plcbasc:efficientmsgthreshold,x would message be efficient anyway ?
        bhs     asldir:instant         b/ yes, send anyway
? ; the following idea is stupid. Even if we wait for him to have some more
; room, his RCVDRDY count won't increase, and the number of bytes we can
; send will not grow, so we have gained nothing. Think about this!
;       bra     asldir:normalsoon      no, wait for remote to have more room
        page
asldir:normalsoon ; respond to other node's transmission after suitable delay
        ldx     asldv:interruptNRB     find NRB again
        ldaa    #nodestate:converses   remember we are "sposd to send" next
        staa    nrb:state,x
        ldx     ASLD+asld:interruptPLCB fetch pointer to Physical Link Control Block
        ldd     plcbasc:respondtime,x  delay a little to let transmit buffer fill
        jsr     asldi:putnrbinxmitq    shortening "respondtime"...
asldi:decidenextoperationj3
        jmp     asldi:decidenextoperation
        page
; Q: What holds a transmitting NRB at the front of the xmit queue ?
; Ans: New NRBs inserted into xmitq are ALWAYS placed after NRBs of equal
; delay, so even new NRBs with delay zero will end up 2nd in the queue
; if the first NRB in the queue has zero delay.

asldi:desirekill ; socket manager does STARTIO to here to kill NRB (D)
        std     asldv:interruptNRB      remember which node should be killed
        if      m6809
        tfr     d,x
        else    m6800!m6801!m6811
        ldx     asldv:interruptNRB
        fin
; What if an NRB dies or is killed in the middle of xmit or receive ?
; Ans: It can't die by itself in the middle of such a transaction.
; The link driver might kill the NRB at the END of a receive (if it got
; an INITIATING message) or the beginning of a xmit (if the retry count
; were zero).
; An NRB can only be killed by a DESIREKILL call, which occurs only
; if the link driver interrupt routine is not busy. So it can't happen!
        ldaa    plcbas:state,x          is ASLD interrupt code doing something?
        bne     asldi:desirekillnotsafe b/ yes, NRB might be in use!
        jsr     asldi:removenrbfromxmitq so he becomes very uninteresting
        ldaa    #nodestate:dead        mark the node state appropriately
        staa    nrb:state,x
; We let the listener task disconnect NRB from NRBINDEX.
        jsr     asldi:signalkilled     tell any waiting tasks what happened.
asldi:desirekillnotsafe ; not safe to kill NRB, just exit
; (killing task will notice NRB is not dead and will try to kill it again)
asldi:desirequitdone ; re-enter here from asldi:desirequit
        jmp     asldi:decideifxmitreqd see what there is to do next

asldi:signalkilled ; signal that we killed NRB(X)
        clr     sdos+sdos:surprise     force scheduler to run
? ; see Z80 version for ECB management
        rts
        page
asldi:desirequit ; do STARTIO to here to disconnect from a node
        std     asldv:interruptNRB      remember which node should be killed
        if      m6809
        tfr     d,x
        else    m6800!m6801!m6811
        ldx     asldv:interruptNRB
        fin
        ldaa    plcbas:state,x         is ASLD interrupt code doing something?
        bne     asldi:desirekillnotsafe b/ yes, NRB might be in use!
        inc     nrb:closelink,x        set "close link when convenient" flag
        ldaa    nrb:state,x
        cmpa    #nodestate:conversei   is NRB idle ?
        bne     asldi:desirequitdone   b/ no, so we will take action soon
; assert: there is no data to send (if there were, we would have been
; in NODESTATE:CONVERSES or we would be impatiently awaiting a reply)
        ldaa    #nodestate:wantquit    yes, act now, switch states
        jmp     asldir:newstate        record new state and send instantly
        page    ASLD: NRB Timer Queue management routines
*       asldi:removenrbfromxmitq -- removes NRB selected by INTERRUPTNRB from XMITQ
*       If node is not in XMITQ, then Receive TOB is killed off before exit.
*       There is a XMITQ for each PLCB. The XMITQ contains a list of the
*       NRBs which are ready to transmit now via the associated PLCB.
*       NRBs which are active but not ready to transmit have a timeout block
*       activated; the timeout will eventually go off and cause the NRB
*       to get placed into the XMITQ.
*       NRBs not in XMITQ have NRB:XMITQFLINK zeroed.
*       NRBs in XMITQ have disabled NRB:RECVTOB so it can't time out
*
*       Must be called with interrupts disabled to ensure atomic queue update
*       Exits with (X) pointing to NRB, and NRB:RECVTOB disabled.
*
*       Removal of one NRB from the XMITQ automatically brings forward
*       the next ready NRB.  Once an NRB has reached the front of the queue,
*       it stays there until it has successfully transmitted its message,
*       or it receives a message which kills it, or is killed locally.
*
*       If we remove the 1st NRB of queue, then we kill PLCBAS:XMITFAILTOB.
*       When we are done, if XMITQ is not empty, we start up XMITFAILTOB again.
*       Since we must muck with SDOSRT timer blocks, we require that
*       stacks be switched when this routine is called.
*
asldi:removenrbfromxmitq ; remove NRB specified by ASLDV:INTERRUPTNRB from XMITQ
        ldx     asldv:interruptNRB     which NRB needs to twiddled
        ldx     nrb:plcb,x             find PLCB related to NRB
        ldd     plcbas:xmitqhead,x     is this NRB the first NRB in XMITQ ?
        cmpd    asldv:interruptNRB     ...?
        bned    asldi:removenrbfromxmitq1 b/ no, leave XMITFAILTOB alone
        if      m6800!m6801!m6811
        stx     asldv:tempx            form pointer to XMITFAILTOB
        ldd     asldv:tempx
        addd    #plcbas:xmitfailtob
        std     asldv:tempx
        ldx     asldv:tempx
        page
        else    (m6809)
        page
        leax    plcbas:xmitfailtob,x
        fin
        jsr     SDOSRT+rt:rtmo         kill XMITFAILTOB
asldi:removenrbfromxmitq1 ; XMITFAILTOB has been disabled if necessary
        ldx     asldv:interruptNRB     which NRB needs to twiddled
        ldd     nrb:xmitqflink,x       find nrb that follows one to be removed
        beq     asldi:removenrbkilltob b/ not in XMITQ, perhaps TOB active
        if      m6800!m6801
        ldx     nrb:xmitqblink,x       find nrb preceding one to be removed
        stx     asldv:tempx            save preceding nrb's address
        std     nrb:xmitqflink,x       make preceding nrb point to next nrb
        ldx     nrb:xmitqflink,x       grab address of following nrb
        ldd     asldv:tempx            make following nrb point backwards to preceding nrb
        std     nrb:xmitqblink,x
        else    (m6811!m6809)
        ldy     nrb:xmitqblink,x       find nrb preceding one to be removed
        std     nrb:xmitqflink,y       make preceding nrb point to next nrb
        ldx     nrb:xmitqflink,x       grab address of following nrb
        sty     nrb:xmitqblink,x       make following nrb point backwards to preceding nrb
        fin
        ldd     nrb:plcb,x             form address of PCLB:XMITQHEAD
        addd    #plcbas:xmitqhead
        ldx     nrb:plcb,x             find PLCB related to NRB
        cmpd    plcbas:xmitqhead,x     is PLCB's XMITQ empty now?
        beqd    asldi:markNRBasnotinXMITQ b/ yes, leave XMITFAILTOB disabled
        ldd     plcbasc:xmitpatiencetime,x fetch timeout on message transmission
        pshd                           save desired delay
        if      m6800!m6801!m6811
        ldd     ASLD+asld:interruptPLCB form address of XMITFAILTOB
        addd    #plcbas:xmitfailtob
        std     asldv:tempx
        puld                           restore desired delay
        ldx     asldv:tempx            get address of XMITFAILTOB
        else    (m6809)
        ldd     plcbasc:xmitpatiencetime,x fetch timeout on message transmission
        leax    plcbas:xmitfailtob,x
        fin
        jsr     SDOSRT+rt:stmo         set timeout running
        page
asldi:markNRBasnotinXMITQ ; entry point used by initz routines
        ldx     asldv:interruptNRB     which NRB needs to be marked
        clr     nrb:xmitqflink,x       mark NRB as "not in XMITQ"
        clr     nrb:xmitqflink+1,x
        rts

asldi:removenrbkilltob ; reason NRB is not in XMITQ is his TOB might be active
        if      m6800!m6801!m6811
        ldd     asldv:interruptNRB     form pointer to Receive TOB
        addd    #nrb:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        else    (m6809)
        ldx     asldv:interruptNRB
        leax    nrb:recvtob,x
        fin
        jsr     SDOSRT+rt:rtmo         remove timeout block
; Note that since we didn't actually fiddle with XMITQ, we do not need
; to adjust XMITFAILTOB.
        ldx     asldv:interruptNRB     set index register properly at exit
        rts
        page
*       asldi:putnrbinxmitq -- places NRB into XMITQ (time to send msg Q)
*       after delay specified by (D).
*       If delay is nonzero, the NRB's TOB is set up with the delay,
*       and rigged to put the NRB into the queue when the time is up.
*       If delay is zero, the NRB is placed into the queue immediately.
*
*       Newly ready NRBs are inserted at the end of XMITQ, that is,
*       AFTER older NRBs. This ensures that it is safe to add NRBs to
*       XMITQ even if first NRB in XMITQ is actually busy transmitting
*       or something.
*
*       NRB specified must not be in XMITQ queue already,
*       nor must it have NRB:RECVTOB active.
*       Must be called with interrupts disabled to ensure atomic queue update
*
*       If XMITQ was empty before inserting this NRB, XMITFAILTOB is activated
*
asldi:putnrbinxmitq ; place NRB specified by ASLDV:INTERRUPTNRB into XMITQ
; Does NOT require that ASLD:INTERRUPTPLCB be set up.
;       ldx     asldv:interruptNRB     which NRB needs to be inserted
        tstd                           zero delay ?
        beqd    asldi:putnrbinxmitqnow b/ yes, hop to it, Bub...
        if      m6800!m6801!m6811
        pshd                           save delay
        ldd     asldv:interruptNRB     form pointer to Timeout block
        addd    #nrb:recvtob
        std     asldv:tempx
        ldx     asldv:tempx
        puld                           restore delay desired
        else    (m6809)
        leax    nrb:recvtob,x
        fin
        jmp     sdosrt+rt:stmo         set timeout block running
        page
asldi:putnrbinxmitqnow ; add NRB to XMITQ associated with its PLCB
        ldd     nrb:plcb,x             form address of PCLB:XMITQHEAD
        addd    #plcbas:xmitqhead
        ldx     nrb:plcb,x             find PLCB related to NRB
        cmpd    plcbas:xmitqhead,x     is PLCB's XMITQ empty now?
        bned    asldi:putnrbinxmitqnow1 b/ no, XMITFAILTOB already enabled
        if      m6800!m6801!m6811
        ldd     plcbasc:xmitpatiencetime,x fetch timeout on message transmission
        pshd                           save desired delay
        ldd     ASLD+asld:interruptPLCB form address of XMITFAILTOB
        addd    #plcbas:xmitfailtob
        std     asldv:tempx
        puld                           restore desired delay
        ldx     asldv:tempx            get address of XMITFAILTOB
        else    (m6809)
        ldd     plcbasc:xmitpatiencetime,x fetch timeout on message transmission
        leax    plcbas:xmitfailtob,x
        fin
        jsr     SDOSRT+rt:stmo         set timeout running
asldi:putnrbinxmitqnow1 ; XMITFAILTOB is enabled
        ldx     asldv:interruptNRB     which NRB needs to be inserted
        if      m6800!m6801!m6811
        ldd     nrb:plcb,x             find PLCB containing appropriate XMITQ
        addd    #plcbas:xmitqhead-nrb:xmitqflink,x compute pointer to dummy NRB...
        std     asldv:tempx            in this PLCB
        ldx     asldv:tempx            insert new NRB after last NRB in queue
        ldd     nrb:xmitqblink,x       find nrb previous to insert point
        stx     asldv:tempx            save insert point address
        ldx     asldv:interruptNRB     make inserted node point backward to prev nrb
        std     nrb:xmitqblink,x
        ldd     asldv:tempx            make inserted node point to insert point nrb
        std     nrb:xmitqflink,x
        ldx     nrb:xmitqblink,x       move back to prev nrb
        ldd     asldv:interruptNRB     make prev nrb point to newly inserted nrb
        std     nrb:xmitqflink,x
        ldx     asldv:tempx            make insert point back link =...
        std     nrb:xmitqblink,x       newly inserted node
        else    (m6809)
        ldx     nrb:plcb,x             find PLCB containing appropriate XMITQ
        leax    plcbas:xmitqhead-nrb:xmitqflink,x compute pointer to dummy NRB...
        ; in this PLCB; then insert new NRB after last NRB in queue
        ldy     nrb:xmitqblink,x       find nrb previous to insert point
        ldu     asldv:interruptNRB     make inserted node...
        sty     nrb:xmitqblink,u       point backward to previous nrb
        stx     nrb:xmitqflink,u       make inserted node point to insert point nrb
        stu     nrb:xmitqflink,y       make prev nrb point to newly inserted nrb
        stu     nrb:xmitqblink,x       make insert point back line be newly inserted node
        fin
        rts                            whew! all done

asldi:putnrbinxmitqwhentimeout ; come here when NRB delay is complete
        std     asldv:interruptNRB     save which NRB is ready
        ldx     asldv:interruptNRB     set to access NRB
        ldaa    nrb:state,x            do we expect a timeout ?
        cmpa    #nodestate:converses   (were we expected to send next ?)
        beq     asldi:putnrbinxmitqwhentimeout1 b/ yes, just go transmit
        ldx     nrb:plcb,x             find PLCB so we can record error
        incd    plcbas:timedoutcount,x count the timeout as an error
        ldx     asldv:interruptNRB     set to access NRB
asldi:putnrbinxmitqwhentimeout1 ; this NRB is suddenly ready to send
        bsr     asldi:putnrbinxmitqnow stuff NRB into XMITQ
        jmp     asldi:decideifxmitreqd go see if we should try to transmit
        page    ASLD: Static Data storage area
; Note: the following variables are SHARED by all incarnations of ASLD:
; that are alive at one instant.  Useage must be CAREFUL to prevent splinters.

asldv:dataacknowledgedcount fdb changed amount of data acknowledged in most recent message received

asldv:tempx        fdb  changed        scratch storage for any purpose

asldv:interruptNRB fdb  changed        holds NRB of current interest
        page    ASLD: Tables
        if      m6800!m6801!m6811
asldv:statecontrol1 fdb asldv:statecontrol&$ff00 used to index asldv:statecontrol
        fin
*
*
asldv:statecontrol ; table used to convert nrb:state into sdlpcontrol:xxxx
        fcb     sdlpcontrol:quit       nodestate:dead
        fcb     sdlpcontrol:agreequit  nodestate:agreequit
        fcb     sdlpcontrol:wantquit   nodestate:wantquit
        fcb     sdlpcontrol:initiating nodestate:initiating
        fcb     sdlpcontrol:resynched  nodestate:resynched
        fcb     sdlpcontrol:acksoon    nodestate:initiated
        fcb     sdlpcontrol:normal     nodestate:converser
        fcb     sdlpcontrol:normal     nodestate:converses
        fcb     sdlpcontrol:normal     nodestate:conversei
        fcb     sdlpcontrol:quit       nodestate:quit
        fcb     sdlpcontrol:die        nodestate:die

        if      m6800!m6801
asldv:control1 fdb asldv:control&$ff00
        fin

asldv:control ; branch table used to branch on received message type
        fdb     asldir:normal          sdlpcontrol:normal
        fdb     asldir:acksoon         sdlpcontrol:acksoon
        fdb     asldir:initiatingmsg   sdlpcontrol:initiating
        fdb     asldir:resynchedmsg    sdlpcontrol:resynched
        fdb     asldir:wantquitmsg     sdlpcontrol:wantquit
        fdb     asldir:agreequitmsg    sdlpcontrol:agreequit
        fdb     asldir:quitmsg         sdlpcontrol:quit
        fdb     asldir:diemsg          sdlpcontrol:die
        fdb     asldir:croak           sdlpcontrol:initiatequit
        page    ASLD: Fast CRC generation routines
gencrctable ; generate CRC table for efficient operation
; This routine initializes the CRCTABLE for use by GENCRC above.
; It pre-computes the crc divisor for each of the 256 possible cases
; (1 per possible byte pattern).
; The crc polynomial used is crc-ccit = x^16+x^12+x^5+x^0
; We need not worry about table contents being ruined by a program bug
; or memory fault, as remote node will then simply refuse to process
; the messages, so we don't need to compute a checksum over the table.
         clra                          byte to perform CRC upon
         if    m6800!m6801!m6811
         ldx   #crctable               where table is
gencrctableloop ; compute 8 bits of CRC on (A)
         psha                          save counter
         clrb                          extend (A) to 16 bits
         bsr   gencrctable4bits
         bsr   gencrctable4bits
         sta   ,x+                     upper 8 bits of pre-computed CRC
         stab  255,x                   lower 8 stored 256 bytes further down in table
         else  (m6809)
gencrctableloop ; compute 8 bits of CRC on (A)
         psha                          save counter
         ldx   #crctable               where table is
         leax  a,x                     form pointer to CRC byte pair
         leax  a,x                     (byte pair requires 2 bytes)
         clrb                          extend (A) to 16 bits
         bsr   gencrctable4bits
         bsr   gencrctable4bits
         std   ,x                      store all 16 bits pre-computed CRC
         fin
         pula                          restore counter
         inca                          generate next byte code to do CRC on
         bne   gencrctableloop
         rts
         page
gencrctable4bits
         bsr   gencrctable2bits
;        bsr   gencrctable2bits
;        rts

gencrctable2bits
         bsr   gencrctable1bit
;        bsr   gencrctable1bit
;        rts

gencrctable1bit ; generate 1 CRC table bit
         asld                          shift MSB into carry
         bcc   gencrctablerts          b/ MSB is zero, divisor won't go in
         eora  #$10                    CRC-CCIT polynomial (:11021)...
         eorb  #$21                    will go in, subtract it mod 2
gencrctablerts
         rts

crctable ; holds 8-step pre-computed CRC divisor polynomial for fast CRCs
         rpt   512
         fcb   $3F                     dummy filler; will be replaced
         page Patch space and oversize checks
         pca   1
asldv:patch  ; ASLD driver patch space (heaven forbid it should get used...)
         rpt   100
         swi

asld:driverend equ *

asld:driversize equ asld:driverend-asld

         if    asld:driversize>>asld:driverestimatedsize
? ; Estimated size of ASLD Driver is too small ?
         fin   asld:driversize>>asld:driverestimatedsize

         if    asld:driversize+128<<asld:driverestimatedsize
? ; Estimated size of ASLD Driver is too big ?
         fin   asld:driversize+128<<asld:driverestimatedsize

         if    asld:driverend>>VTDriver
? ; ASLD Driver overlaps SDOS
         fin   asld:driverend>>VTDriver

         end   asld
