RING    TITLE   "Z80 BCPL Run Time System  -  Section RING"
        GET     "HEADER"
        GET     "SCBHDR"
        LAYOUT
        REF     LKUPNAME
        REF     SSSP
        REF     SSPOPEN
        REF     RFSHSSP
        REF     SENDSSP
        REF     SENDTSSP
        REF     TESTSSP
        REF     SSPNPORT

        REF     BSPOPEN
        REF     RCVOPEN
        REF     ACKOPEN
        REF     BSPDEST

        REF     NEXTCO

        REF     GETVEC
        REF     FREEVEC
        REF     UNCHAIN
        
        REF     RXANCHOR

        REF     B.FNDI
        REF     V.FNDI
        REF     V.FNDO


G.LKUP  EQU     2*100                   ;  Global number of "lookupname"
G.SSPX  EQU     2*101                   ;  Global number of "ssp"
G.RCVS  EQU     2*102                   ;  Global number of "rcvssp"
G.SSPR  EQU     2*103                   ;  Global number of "sspreply"
G.BSPO  EQU     2*104                   ;  Global number of "bspopen"
G.RCVO  EQU     2*105                   ;  Global number of "rcvopen"
G.MVTP  EQU     2*106                   ;  Global number of "makevtp"
G.RCVC  EQU     2*107                   ;  Global number of "rcvcancel"
G.GET2  EQU     2*108                   ;  Global number of "get2bytes"
G.PUT2  EQU     2*109                   ;  Global number of "put2bytes"
G.SSPO  EQU     2*110                   ;  Global number of "sspopen"
G.RFSP  EQU     2*111                   ;  Global number of "rfshssp"
G.SESP  EQU     2*112                   ;  Global number of "sendssp"
G.STSP  EQU     2*113                   ;  Global number of "sendtssp"
G.TESP  EQU     2*114                   ;  Global number of "testssp"
G.SSPN  EQU     2*115                   ;  Global number of "sspnport"
G.BSPD  EQU     2*116                   ;  Global number of "bspdest"
G.MAXG  EQU     2*150                   ;  Highest referenced global
        LAYOUT
        RELOCATABLE

        ORG     0

RING    DEFB    'BCPL'                  ;  Entry flag for BCPL module
        DEFW    RINGE-RING              ;  Length of module in bytes
        LAYOUT
;  BSPROOT
;  -------
;
;    Root of the Byte Stream system.  Define the sizes of the various buffers
;    in use by the byte streams, and then get the standard "BSPROOT" header.


RXBLKSIZ EQU    69                      ;  Size of reception buffer
TXBLKSIZ EQU    69                      ;  Size of transmission buffer
TBUFSIZE EQU    64                      ;  Open data buffer size

        GET     "BSPROOT"
        LAYOUT
;  LOOKUPNAME
;  ----------
;
;    bool  :=  lookupname( name, nsv )


        ALIGN

LKUP    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        ADD     HL,HL                   ;  Get byte pointer to name
        PUSH    DE                      ;  Save pointer to nsv

;  Now, call the system routine to do the name look up for us.  Since IX and
;  IY are clobbered by the call, we should save them now.

        PUSH    IX                      ;  Save IX
        PUSH    IY                      ;  Save IY
        CALL    LKUPNAME                ;  Call the system routine
        POP     IY                      ;  Restore IY
        POP     IX                      ;  Restore IX
        JP      Z,LKUP0                 ;  Success return code, so get values

;  If we drop through here, then the name lookup failed, and there is a
;  return code in A.  We set "result2" to this return code, and return
;  FALSE back as the result.

        LD      (IY-108),A              ;  Low byte of result2
        LD      (IY-107),0              ;  High byte of result2
        POP     DE                      ;  Restore pointer to nsv
        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then the name lookup has succeeded, and we should fill in
;  the nsv.

LKUP0   EX      (SP),IX                 ;  Swap stack pointer and nsv
        ADD     IX,IX                   ;  Get byte pointer

        LD      (IX+0),B                ;  Low byte of machine.id
        LD      (IX+1),0                ;  High byte of machine.id
        LD      (IX+2),C                ;  Low byte of flags
        LD      (IX+3),0                ;  High byte of flags
        LD      (IX+4),L                ;  Low byte of port
        LD      (IX+5),H                ;  High byte of port
        LD      (IX+6),E                ;  Low byte of func
        LD      (IX+7),D                ;  High byte of func

        POP     IX                      ;  Restore stack pointer
        LD      HL,#XFFFF               ;  TRUE return code
        RET                             ;  And return
        LAYOUT
;  SSP
;  ---
;
;    bool  :=  ssp( service, txbuff, rxbuff )


        ALIGN

SSPX    PUSH    HL                      ;  Save return code
        EXX                             ;  Get argument register set

        ADD     HL,HL                   ;  Get byte pointer to service name
        SLA     E                       ;  Get byte pointer ...
        RL      D                       ;  ... to tx buffer
        SLA     C                       ;  Get byte pointer ...
        RL      B                       ;  ... to rx buffer

        PUSH    IX                      ;  Save IX
        PUSH    IY                      ;  Save IY
        CALL    SSSP                    ;  Call the system routine
        POP     IY                      ;  Restore IY
        POP     IX                      ;  Restore IX
        JP      Z,SSPX0                 ;  Successful return

;  If we drop through here, then all has failed.  Return the error return
;  code in result2.

        LD      (IY-108),A              ;  Low byte of result2
        LD      (IY-107),0              ;  High byte of result2
        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then all has succeeded.  Return the value TRUE.

SSPX0   LD      HL,#XFFFF               ;  TRUE return code
        RET                             ;  And return
        LAYOUT
;  RCVSSP
;  ------
;
;    bool  :=  rcvssp( port, sspmsg, @station )


        ALIGN

RCVS    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        PUSH    BC                      ;  Save pointer to station
        PUSH    DE                      ;  Save pointer to sspmsg
        PUSH    HL                      ;  Save port number
        CALL    FPORT                   ;  Locate the port number in the RLIST

;  When we drop through here, HL has one of the following values:
;  If the table is full, and the port number is not found, then HL will be
;  zero.  If the table is not full, then HL will either point to an entry for
;  this port number, or an empty entry.

        LD      A,H                     ;  High byte of result
        OR      L                       ;  Is it zero ?
        JP      NZ,RCVS0                ;  No, so we have a pointer

;  If we drop through here, then HL contains zero, and we must return FALSE
;  as the result of the function.

        POP     DE                      ;  Restore port number
        POP     DE                      ;  Restore pointer to sspmsg
        POP     DE                      ;  Restore pointer to station
        RET                             ;  And return  (HL is already FALSE)

;  If we come here, then we have found an entry in the table.  If the port
;  field is zero, then this is an empty slot, and all is well.  Otherwise,
;  we are already receiving on this port, and so we should return an error.

RCVS0   PUSH    HL                      ;  Save pointer to entry
        EX      (SP),IX                 ;  Get pointer in an index register

        LD      L,(IX+0)                ;  Low byte of port number
        LD      H,(IX+1)                ;  High byte of port number

        LD      A,H                     ;  High byte of port number
        OR      L                       ;  Is it zero ?
        JP      Z,RCVS1                 ;  Yes, so we can install the new entry

;  If we drop through here, then this is an entry for a port number which is
;  already claimed.  We should therefore return an error.

        POP     IX                      ;  Restore IX
        POP     HL                      ;  Restore port number
        POP     HL                      ;  Restore pointer to sspmsg
        POP     HL                      ;  Restore pointer to station

        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then this is an empty slot, and we should claim it.  Call
;  RCVOPEN to set up the reception request, and then fill in the fields of
;  the entry.

RCVS1   POP     DE                      ;  Restore IX value into DE

        POP     HL                      ;  Restore port number
        LD      (IX+0),L                ;  Low byte of port number
        LD      (IX+1),H                ;  High byte of port number

        PUSH    DE                      ;  Save IX value again
        LD      DE,RCVRTNS              ;  Load address of routine
        CALL    RCVOPEN                 ;  Set up the reception request
        JP      Z,RCVS2                 ;  Successful return

;  If we drop through here, then we have failed to set up the reception
;  request, and so we should return an error value.

        LD      (IX+0),0                ;  Low byte of port field
        LD      (IX+1),0                ;  High byte of port field

        POP     IX                      ;  Restore IX value
        POP     HL                      ;  Restore pointer to sspmsg
        POP     HL                      ;  Restore pointer to station

        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then we have set up the reception request, and so we should
;  fill in the rest of the entries.

RCVS2   POP     DE                      ;  Restore IX value into DE

        POP     HL                      ;  Restore pointer to sspmsg
        ADD     HL,HL                   ;  Get byte pointer to sspmsg
        LD      (IX+2),L                ;  Low byte of sspmsg
        LD      (IX+3),H                ;  High byte of sspmsg

        POP     HL                      ;  Restore pointer to station
        ADD     HL,HL                   ;  Get byte pointer to station
        LD      (IX+4),L                ;  Low byte of station
        LD      (IX+5),H                ;  High byte of station

        PUSH    DE                      ;  Save IX value again
        POP     IX                      ;  And restore into IX this time!

        LD      HL,#XFFFF               ;  TRUE return code
        RET                             ;  And return
        LAYOUT
;  SSPREPLY
;  --------
;
;    bool  :=  sspreply( buffer, station, port )


        ALIGN

SSPR    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        ADD     HL,HL                   ;  Get byte pointer to buffer
        PUSH    IX                      ;  Save value in IX
        PUSH    IY                      ;  Save value in IY

        LD      IX,TXCB                 ;  Pointer to TXCB
        LD      (IX+6),L                ;  Low byte of buffer pointer
        LD      (IX+7),H                ;  High byte of buffer pointer
        LD      (IX+4),E                ;  Low byte of station address
        LD      (IX+2),C                ;  Low byte of port address
        LD      (IX+3),B                ;  High byte of port address
        
        LD      DE,SSPREP               ;  SSP Reply tag
        LD      BC,0                    ;  Reply port

        INC     HL                      ;  Increment buffer pointer
        INC     HL                      ;  And again
        LD      (HL),D                  ;  Store high byte
        INC     HL                      ;  Increment pointer
        LD      (HL),E                  ;  Store low byte
        INC     HL                      ;  Increment pointer
        LD      (HL),B                  ;  Store high byte
        INC     HL                      ;  Increment pointer
        LD      (HL),C                  ;  Store low byte

        TXBLK                           ;  Attempt to transmit the block
        JP      Z,SSPR0                 ;  Success, so return

;  If we drop through here, then the transmission failed, and we must return
;  the error condition to the caller.

        POP     IY                      ;  Restore old value of IY
        POP     IX                      ;  Restore old value of IX

        LD      (IY-108),A              ;  Low byte of result2
        LD      (IY-107),0              ;  High byte of result2

        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then the transmission worked, and so we can return
;  a TRUE result.

SSPR0   POP     IY                      ;  Restore old value of IY
        POP     IX                      ;  Restore old value of IX

        LD      HL,#XFFFF               ;  TRUE result
        RET                             ;  And return
        LAYOUT
;  BSPOPEN
;  -------
;  
;    scb  :=  bspopen( name, openmsg )


        ALIGN

BSPO    PUSH    HL                      ;  Save return link
        EXX                             ;  Get argument register set
        PUSH    HL                      ;  Save the name
        PUSH    DE                      ;  Save the openmsg

;  Before we attempt to send the OPEN message, we should get a stream control
;  block which we are going to return eventually.

        LD      HL,S.UPB                ;  Size of SCB
        CALL    GETVEC                  ;  Allocate the store
        LD      A,H                     ;  High byte of returned SCB
        OR      L                       ;  Is the returned value zero ?
        JP      NZ,BSPO0                ;  No, so all is well

;  If we drop through here, then the SCB could not be allocated. Return this
;  fact the the outside world.

        POP     DE                      ;  Restore openmsg
        POP     DE                      ;  Restore name
        RET                             ;  And return

;  If we come here, then the SCB has been obtained, and so we can attempt to
;  send the BSP open message.

BSPO0   PUSH    HL                      ;  Save pointer to SCB
        EX      (SP),IX                 ;  Get pointer to it in IX
        CALL    B.FNDI                  ;  Fill in the SCB
        POP     IX                      ;  And restore IX

        POP     DE                      ;  Restore openmsg
        EX      (SP),HL                 ;  Swap name and SCB pointer
        ADD     HL,HL                   ;  Get byte pointer to name

        EX      DE,HL                   ;  Get openmsg in HL
        ADD     HL,HL                   ;  Get byte pointer to openmsg
        LD      A,H                     ;  Check to see if ...
        OR      L                       ;  ... the openmsg is zero
        JP      NZ,BSPO1                ;  If not, then use the value given

        LD      HL,OPENMSG              ;  Otherwise, use the default
        JP      BSPO3                   ;  Join common code

;  If we have been given a buffer, then we still have to fill in the first
;  few words, since only the "data" part has so far been filled in.

BSPO1   PUSH    HL                      ;  Save pointer to buffer
        PUSH    DE                      ;  Save pointer to name

        EX      DE,HL                   ;  Get pointer in DE
        INC     DE                      ;  + 1
        INC     DE                      ;  + 2
        LD      HL,OPENMSG              ;  Default open msg
        LD      C,(HL)                  ;  Low byte of length
        INC     HL                      ;  Increment pointer
        LD      B,(HL)                  ;  High byte of length
        INC     HL                      ;  Increment pointer
        SLA     C                       ;  Get byte count ...
        RL      B                       ;  ... of default block length
        LDIR                            ;  And copy the relevant bytes

        POP     DE                      ;  Restore pointer to name
        POP     HL                      ;  Restore pointer to buffer

;  By now, we have a valid "openmsg" pointed to by HL, and "name" pointed
;  to by DE.  Call "BSPOPEN" in order to open the stream.

BSPO3   EX      DE,HL                   ;  Restore argument registers
        PUSH    IX                      ;  Save IX
        PUSH    IY                      ;  Save IY
        CALL    BSPOPEN                 ;  Attempt to open the stream
        JP      Z,BSPO2                 ;  Open successful, so continue

;  If we drop through here, then the open failed, and so we should return the
;  error code to the user.

        POP     IY                      ;  Restore IY
        POP     IX                      ;  Restore IX
        LD      (IY-108),A              ;  Low byte of error code
        LD      (IY-107),0              ;  High byte of error code
        POP     HL                      ;  Restore pointer to SCB
        CALL    FREEVEC                 ;  Free the SCB storage

        LD      HL,0                    ;  Return value
        RET                             ;  And return

;  If we come here, then the OPEN succeeded, and IY is pointing to the relevant
;  byte stream control block.  We should update the SCB with this value, and
;  return the SCB pointer.

BSPO2   EX      (SP),IY                 ;  Restore IY
        POP     HL                      ;  Restore BSCB
        POP     IX                      ;  Restore IX
        EX      (SP),IX                 ;  And get SCB pointer in IX

        LD      (IX+S.ARG1+0),L         ;  Store low byte of BSCB
        LD      (IX+S.ARG1+1),H         ;  Store high byte of BSCB

        EX      (SP),IX                 ;  Restore IX
        POP     HL                      ;  Get SCB pointer in HL
        RET                             ;  And return
        LAYOUT
;  RCVOPEN
;  -------
;
;    bool  :=  rcvopen( port, openmsg, @scb )


        ALIGN

RCVO    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        PUSH    BC                      ;  Save pointer to SCB
        PUSH    DE                      ;  Save pointe to openmsg
        PUSH    HL                      ;  Save port number
        CALL    FPORT                   ;  Locate the port number in the RLIST

;  When we drop through here, HL has one of the following values:
;  If the table is full, and the port number is not found, then HL will be
;  zero.  If the table is not full, then HL will either point to an entry for
;  this port number, or an empty entry.

        LD      A,H                     ;  High byte of result
        OR      L                       ;  Is it zero ?
        JP      NZ,RCVO0                ;  No, so we have a pointer

;  If we drop through here, then HL contains zero, and we must return FALSE
;  as the result of the function.

        POP     DE                      ;  Save port number
        POP     DE                      ;  Restore pointer to openmsg
        POP     DE                      ;  Restore pointer to SCB
        RET                             ;  And return  (HL is already FALSE)

;  If we come here, then we have found an entry in the table.  If the port
;  field is zero, then this is an empty slot, and all is well.  Otherwise,
;  we are already receiving on this port, and so we should return an error.

RCVO0   PUSH    HL                      ;  Save pointer to entry
        EX      (SP),IX                 ;  Get pointer in an index register

        LD      L,(IX+0)                ;  Low byte of port number
        LD      H,(IX+1)                ;  High byte of port number

        LD      A,H                     ;  High byte of port number
        OR      L                       ;  Is it zero ?
        JP      Z,RCVO1                 ;  Yes, so we can install the new entry

;  If we drop through here, then this is an entry for a port number which is
;  already claimed.  We should therefore return an error.

        POP     IX                      ;  Restore IX
        POP     HL                      ;  Restore port number
        POP     HL                      ;  Restore pointer to openmsg
        POP     HL                      ;  Restore pointer tp SCB

        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then this is an empty slot, and we should claim it.  Call
;  RCVOPEN to set up the reception request, and then fill in the fields of
;  the entry.

RCVO1   POP     DE                      ;  Restore IX value into DE

        POP     HL                      ;  Restore port number
        LD      (IX+0),L                ;  Low byte of port number
        LD      (IX+1),H                ;  High byte of port number

        PUSH    DE                      ;  Save IX value again
        LD      DE,RCVRTNO              ;  Load address of routine
        CALL    RCVOPEN                 ;  Set up the reception request
        JP      Z,RCVO2                 ;  Successful return

;  If we drop through here, then we have failed to set up the reception
;  request, and so we should return an error value.

        LD      (IX+0),0                ;  Low byte of port field
        LD      (IX+1),0                ;  High byte of port field

        POP     IX                      ;  Restore IX value
        POP     HL                      ;  Restore pointer to openmsg
        POP     HL                      ;  Restore pointer to SCB

        LD      HL,0                    ;  FALSE return code
        RET                             ;  And return

;  If we come here, then we have set up the reception request, and so we should
;  fill in the rest of the entries.

RCVO2   POP     DE                      ;  Restore IX value into DE

        POP     HL                      ;  Restore pointer to openmsg
        ADD     HL,HL                   ;  Get byte pointer to openmsg
        LD      (IX+2),L                ;  Low byte of openmsg
        LD      (IX+3),H                ;  High byte of openmsg

        POP     HL                      ;  Restore pointer to SCB
        ADD     HL,HL                   ;  Get byte pointer to SCB
        LD      (IX+4),L                ;  Low byte of SCB
        LD      (IX+5),H                ;  High byte of SCB

        PUSH    DE                      ;  Save IX value again
        POP     IX                      ;  And restore into IX this time!

        LD      HL,#XFFFF               ;  TRUE return code
        RET                             ;  And return
        LAYOUT
;  MAKEVTP
;  -------
;
;      inscb   :=  makevtp( bscb )
;      outscb  :=  result2


        ALIGN

MVTP    PUSH    HL                      ;  Save return link
        EXX                             ;  Get argument register set
        PUSH    HL                      ;  Save pointer to BSCB

        LD      HL,S.UPB                ;  Size of SCB
        CALL    GETVEC                  ;  Allocate the storage
        PUSH    HL                      ;  Save the result
        LD      HL,S.UPB                ;  Size of SCB
        CALL    GETVEC                  ;  Allocate the storage
        POP     DE                      ;  Restore previous result

        LD      A,H                     ;  High byte of scb
        OR      L                       ;  Is is zero ?
        JP      Z,MVTP0                 ;  Yes, so error

        LD      A,D                     ;  High byte of scb
        OR      E                       ;  Is it zero ?
        JP      Z,MVTP0                 ;  Yes, so error

;  Otherwise, if we drop through here, then we have allocated the storage,
;  and we should call the VTP "find" routines to copy relevant values into
;  the SCBs.

        PUSH    DE                      ;  Save one SCB

        PUSH    HL                      ;  Save scb pointer
        EX      (SP),IX                 ;  Get pointer in IX
        CALL    V.FNDO                  ;  Get the output stream
        POP     IX                      ;  Restore IX

        EX      (SP),HL                 ;  Swap input stream and other SCB

        PUSH    HL                      ;  Save the SCB
        EX      (SP),IX                 ;  Get pointer in IX
        CALL    V.FNDI                  ;  Get the input stream
        POP     IX                      ;  Restore IX

        POP     DE                      ;  Restore pointer to output SCB
        POP     BC                      ;  Restore pointer to BSCB

        PUSH    HL                      ;  Save input stream
        EX      (SP),IX                 ;  And get pointer in IX
        LD      (IX+S.ARG1+0),C         ;  Low byte of BSCB
        LD      (IX+S.ARG1+1),B         ;  High byte of BSCB
        LD      (IX+S.ARG2+0),E         ;  Low byte of other scb
        LD      (IX+S.ARG2+1),D         ;  High byte of other scb
        POP     IX                      ;  Restore IX

        PUSH    DE                      ;  Save output stream
        EX      (SP),IX                 ;  And get pointer in IX
        LD      (IX+S.ARG1+0),C         ;  Low byte of BSCB
        LD      (IX+S.ARG1+1),B         ;  High byte of BSCB
        LD      (IX+S.ARG2+0),L         ;  Low byte of other scb
        LD      (IX+S.ARG2+1),H         ;  High byte of other scb
        POP     IX                      ;  Restore IX

        LD      (IY-108),E              ;  Save low byte of output scb
        LD      (IY-107),D              ;  Save high byte of output scb

        RET                             ;  And return   

;  If we come here, then we have been unable to allocate storage for the SCBs.
;  Free what we have, and return zero.

MVTP0   PUSH    DE                      ;  Save other SCB
        CALL    FREEVEC                 ;  Free the store
        POP     HL                      ;  Get other scb
        CALL    FREEVEC                 ;  Free the store
        POP     HL                      ;  Restore BSCB

        LD      HL,0                    ;  Error result
        LD      (IY-108),L              ;  Low byte of result2
        LD      (IY-107),H              ;  High byte of result2
        RET                             ;  And return
        LAYOUT
;  RCVCANCEL
;  ---------
;
;    bool  :=  rcvcancel( port )


        ALIGN

RCVC    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set

;  There are two things to do here to ensure a reasonable cancellation of
;  the reception request.  We must remove the entry from the RLIST first of
;  all, so that the slot can be reused.  We must then chain down the list
;  of reception requests maintained by the system, and find the one referring
;  to this port.  Having found it, we must unchain it.

        CALL    FPORT                   ;  Find the entry in the RLIST
        LD      A,H                     ;  Zero pointer returned ?
        OR      L                       ;  Yes, so error
        JP      Z,RCVC0                 ;  Port not found

        LD      E,(HL)                  ;  Low byte of port
        INC     HL                      ;  Increment pointer
        LD      D,(HL)                  ;  High byte of port
        LD      A,D                     ;  Is the port ...
        OR      E                       ;  ... number zero ?
        JP      Z,RCVC0                 ;  Yes, so error

;  If we drop through here, then HL points to the entry in the RLIST which
;  must be freed, and DE contains the port for which the reception request
;  should be cancelled.

        LD      (HL),0                  ;  Clear high byte
        DEC     HL                      ;  Decrement pointer
        LD      (HL),0                  ;  Clear low byte

;  Now, load the base of the RX chain, and look for the relevant entry.

        LD      HL,RXANCHOR             ;  Base of the RX chain

RCVC1   LD      A,H                     ;  Top byte of pointer
        OR      L                       ;  Is it zero ?
        JP      Z,RCVC2                 ;  Yes, so entry not found

        PUSH    HL                      ;  Save pointer to block
        INC     HL                      ;  Increment pointer
        INC     HL                      ;  Increment pointer

        LD      A,(HL)                  ;  Low byte of port
        CP      E                       ;  Does it match ?
        JP      NZ,RCVC3                ;  No, so next entry

        INC     HL                      ;  Increment pointer
        LD      A,(HL)                  ;  High byte of port
        CP      D                       ;  Does it match ?
        JP      NZ,RCVC3                ;  No, so next entry

;  If we drop through here, then this entry is the relevant one.  We should
;  call the UNCHAIN function to get rid of it.

        POP     HL                      ;  Restore pointer to block
        LD      DE,RXANCHOR             ;  Load pointer to chain
        CALL    UNCHAIN                 ;  Unchain the entry

;  All that remains now is to return a TRUE result to the caller

        LD      HL,#XFFFF               ;  TRUE result
        RET                             ;  And return

;  If we come here, then the port address did not match.  No matter, since
;  all we must do is chain down the list once more, and try again.

RCVC3   POP     HL                      ;  Restore pointer to entry
        LD      A,(HL)                  ;  Low byte of link
        INC     HL                      ;  Increment pointer
        LD      H,(HL)                  ;  High byte of link
        LD      L,A                     ;  Set the low byte
        JP      RCVC1                   ;  Enter the main loop again

;  If we come here, then we have been able to cancel the entry in the RLIST,
;  but not been able to remove the entry from the reception chain.

RCVC2   ERRTRAP                         ;  Call the debugger

;  If we come here, then we have been unable to remove the entry from the
;  RLIST, implying that this is a bum port number.  Return FALSE as the
;  result.

RCVC0   LD      HL,0                    ;  FALSE result
        RET                             ;  And return
        LAYOUT
;  GET2BYTES
;  ---------
;
;    word  :=  get2bytes( block, offset )


        ALIGN

GET2    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        ADD     HL,DE                   ;  Add in BCPL offset
        ADD     HL,HL                   ;  Get byte pointer
        LD      A,(HL)                  ;  Load high byte
        INC     HL                      ;  Increment pointer
        LD      L,(HL)                  ;  Load low byte
        LD      H,A                     ;  Set high byte
        RET                             ;  And return
        LAYOUT
;  PUT2BYTES
;  ---------
;
;    put2bytes( block, offset, word )


        ALIGN

PUT2    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        ADD     HL,DE                   ;  Add in BCPL offset
        ADD     HL,HL                   ;  Get byte pointer
        LD      (HL),B                  ;  Store high byte
        INC     HL                      ;  Increment pointer
        LD      (HL),C                  ;  Store low byte
        RET                             ;  And return
        LAYOUT
;  SSPOPEN
;  -------
;
;    sspcb  :=  sspopen( name, txsize, rxsize )


        ALIGN

SSPO    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        ADD     HL,HL                   ;  Get byte pointer to name

        PUSH    IX                      ;  Save IX
        PUSH    IY                      ;  Save IY
        CALL    SSPOPEN                 ;  Attempt to open the channel
        JP      Z,SSPO0                 ;  Successful return

;  If we drop through here, then we have failed to open the channel.  Return
;  a zero result, and set "result2" equal to the error code.

        POP     IY                      ;  Restore IY
        POP     IX                      ;  Restore IX

        LD      (IY-108),A              ;  Low byte of result2
        LD      (IY-107),0              ;  High byte of result2

        LD      HL,0                    ;  Error result
        RET                             ;  And return

;  If we come here, then the SSPOPEN succeeded, and the sspcb is in the IY
;  register.

SSPO0   EX      (SP),IY                 ;  Restore IY and push sspcb
        POP     HL                      ;  Set result value
        POP     IX                      ;  Restore IX
        RET                             ;  And return
        LAYOUT
;  RFSHSSP
;  -------
;
;    bool  :=  rfshssp( sspcb, name )


        ALIGN

RFSP    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        EX      DE,HL                   ;  Get name pointer in HL
        ADD     HL,HL                   ;  And get byte pointer to name

        PUSH    IX                      ;  Save IX
        PUSH    DE                      ;  Store sspcb
        EX      (SP),IY                 ;  And set IY to sspcb
        CALL    RFSHSSP                 ;  Attempt to refresh the ssp channel
        POP     IY                      ;  Restore IY
        POP     IX                      ;  Restore IX
        JP      Z,RFSP0                 ;  Successful return

;  If we drop through here, the refresh failed, and we should set "result2"
;  to the error code, and return FALSE.

        LD      (IY-108),A              ;  Low byte of result2
        LD      (IY-107),0              ;  High byte of result2

        LD      HL,0                    ;  FALSE result
        RET                             ;  And return

;  If we come here, then the refresh succeeded, and so we should return the
;  TRUE value.

RFSP0   LD      HL,#XFFFF               ;  TRUE result
        RET                             ;  And return
        LAYOUT
;  SENDSSP
;  -------
;
;    sendssp( sspcb, txbuff, rxbuff )


        ALIGN

SESP    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set

        PUSH    HL                      ;  Save pointer to sspcb
        EX      (SP),IY                 ;  And store in IY

        EX      DE,HL                   ;  Get txbuff in HL
        ADD     HL,HL                   ;  Get byte pointer to txbuff
        EX      DE,HL                   ;  And store back in DE

        LD      L,C                     ;  Low byte of rxbuff
        LD      H,B                     ;  High byte of rxbuff
        ADD     HL,HL                   ;  Get byte pointer to rxbuff

        CALL    SENDSSP                 ;  Start the operation going

        POP     IY                      ;  Restore IY
        RET                             ;  And return
        LAYOUT
;  SENDTSSP
;  --------
;
;    sendtssp( sspcb, txbuff, rxbuff, timeout )


        ALIGN

STSP    SETLINK                         ;  Set up linkage information
        EXX                             ;  Get argument register set

        PUSH    HL                      ;  Save pointer to sspcb
        EX      (SP),IY                 ;  And store in IY

        EX      DE,HL                   ;  Get txbuff in HL
        ADD     HL,HL                   ;  Get byte pointer to txbuff
        EX      DE,HL                   ;  And store back in DE

        LD      L,C                     ;  Low byte of rxbuff
        LD      H,B                     ;  High byte of rxbuff
        ADD     HL,HL                   ;  Get byte pointer to rxbuff

        LD      A,(IX-118)              ;  Low byte of timeout value

        CALL    SENDTSSP                ;  Start the operation going

        POP     IY                      ;  Restore IY
        RETURN                          ;  And return
        LAYOUT
;  TESTSSP
;  -------
;
;    rc  :=  testssp( sspcb )


        ALIGN

TESP    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        PUSH    HL                      ;  Save pointer to sspcb
        EX      (SP),IY                 ;  And store in IY

        CALL    TESTSSP                 ;  Test the ssp channel

        POP     IY                      ;  And restore IY

        LD      (IY-108),C              ;  Low byte of result2
        LD      (IY-107),B              ;  High byte of result2

        LD      L,A                     ;  Low byte of result
        LD      H,0                     ;  High byte of result
        RET                             ;  And return
        LAYOUT
;  SSPNPORT
;  --------
;
;    sspnport( sspcb )


        ALIGN

SSPN    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set
        PUSH    HL                      ;  Save pointer to sspcb
        EX      (SP),IY                 ;  And store in IY

        CALL    SSPNPORT                ;  Get new port for channel

        POP     IY                      ;  Restore IY
        RET                             ;  And return
        LAYOUT
;  BSPDEST
;  -------
;
;    station  :=  bspdest( bscb )
;    port     :=  result2


        ALIGN

BSPD    PUSH    HL                      ;  Save return address
        EXX                             ;  Get argument register set

        PUSH    HL                      ;  Save pointer to byte stream scb
        EX      (SP),IY                 ;  Store pointer in IY
        LD      L,(IY+S.ARG1+0)         ;  Low byte of bscb
        LD      H,(IY+S.ARG1+1)         ;  High byte of bscb
        PUSH    HL                      ;  Save pointer on the stack
        POP     IY                      ;  And store it in IY

        CALL    BSPDEST                 ;  Get the station and port

        POP     IY                      ;  Restore IY
        LD      (IY-108),L              ;  Low byte of port
        LD      (IY-107),H              ;  High byte of port
        LD      L,A                     ;  Low byte of station
        LD      H,0                     ;  High byte of station

        RET                             ;  And return
        LAYOUT
;  RCVRTNS
;  -------
;
;    Asynchronous "receive" routine, called from RXBLK when an ssp block for
;    one of our ports has been received.  We must search the RLIST for an
;    entry for this port, and then service it.  This version is for SSP
;    requests.


RCVRTNS LD      L,(IX+2)                ;  Low byte of port
        LD      H,(IX+3)                ;  High byte of port
        CALL    FPORT                   ;  Find the port entry

;  If HL is zero, or points to a zero entry, then this is an internal error,
;  and hence we should enter the debugger.

        LD      A,H                     ;  High byte of result
        OR      L                       ;  Is the result zero ?
        JP      Z,RCVRS0                ;  Yes, so error

        PUSH    HL                      ;  Save pointer to entry
        EX      (SP),IX                 ;  Get pointer in IX

        LD      L,(IX+0)                ;  Low byte of port
        LD      H,(IX+1)                ;  High byte of port

        LD      A,H                     ;  Make sure that the entry ...
        OR      L                       ;  ... is not zero
        JP      Z,RCVRS0                ;  Erroneous if it is!

;  If we drop through here, then we have found the entry for this port number.
;  we should do the following things:
;
;    a)  Look at the STATION field, and if it is not zero, then we should drop
;        this block on the ground.
;
;    b)   Otherwise, we should copy the "sspmsg" across to the user buffer
;         (if he has given us one), and update the STATION field.

        LD      L,(IX+4)                ;  Low byte of STATION pointer
        LD      H,(IX+5)                ;  High byte of STATION pointer

        LD      A,(HL)                  ;  Low byte of STATION value
        INC     HL                      ;  Increment pointer
        OR      (HL)                    ;  Is the entry zero
        JP      Z,RCVRS1                ;  Yes, so we can continue

;  If we drop through here, then the "rxcb" field has not been cleared by the
;  user, and so we should drop this block on the ground.

        POP     IX                      ;  Restore old value of IX

        XOR     A                       ;  Set return code (from RXBLK)
        RET                             ;  And return

;  If we come here, then we can go ahead with the copying of the block, and
;  the updating of the RXCB field.

RCVRS1  EX      (SP),IX                 ;  Swap RXCB pointer and RLIST entry
        LD      A,(IX+4)                ;  Station address
        LD      (HL),0                  ;  High byte of STATION
        DEC     HL                      ;  Decrement pointer
        LD      (HL),A                  ;  Low byte of STATION
        POP     IX                      ;  Restore pointer to RLIST entry

        LD      E,(IX+2)                ;  Low byte of sspmsg pointer
        LD      D,(IX+3)                ;  High byte of sspmsg pointer
        LD      A,D                     ;  Look to see if the ...
        OR      E                       ;  ... value is zero
        JP      Z,RCVRS2                ;  And if it is, do not copy

;  If we drop through here, then we must copy the SSPMSG from the temporary
;  buffer in which it has been stored, into the user buffer.

        LD      HL,TEMPBUF              ;  Address of temporary buffer
        LD      C,(HL)                  ;  Low byte of length
        INC     HL                      ;  Increment pointer
        LD      B,(HL)                  ;  High byte of length
        DEC     HL                      ;  Restore pointer

        INC     BC                      ;  Get number of words to copy
        SLA     C                       ;  And calculate the ...
        RL      B                       ;  ... number of bytes
        LDIR                            ;  And copy them over

;  Now, all that remains is to restore the value if IX given to us, and set the
;  return code for RXBLK.

RCVRS2  XOR     A                       ;  Set return code for RXBLK
        RET                             ;  And return

;  If we come here, then we have detected an internal error, and hence we
;  should call the debugger.

RCVRS0  ERRTRAP                         ;  Call the debugger
        LAYOUT
;  RCVRTNO
;  -------
;
;    Asynchronous "receive" routine, called from RXBLK when an open block for
;    one of our ports has been received.  We must search the RLIST for an
;    entry for this port, and then service it.  This version is for OPEN
;    requests.


RCVRTNO LD      L,(IX+2)                ;  Low byte of port
        LD      H,(IX+3)                ;  High byte of port
        CALL    FPORT                   ;  Find the port entry

;  If HL is zero, or points to a zero entry, then this is an internal error,
;  and hence we should enter the debugger.

        LD      A,H                     ;  High byte of result
        OR      L                       ;  Is the result zero ?
        JP      Z,RCVRO0                ;  Yes, so error

        PUSH    HL                      ;  Save pointer to entry
        EX      (SP),IX                 ;  Get pointer in IX

        LD      L,(IX+0)                ;  Low byte of port
        LD      H,(IX+1)                ;  High byte of port

        LD      A,H                     ;  Make sure that the entry ...
        OR      L                       ;  ... is not zero
        JP      Z,RCVRO0                ;  Erroneous if it is!

;  If we drop through here, then we have found the entry for this port number.
;  we should do the following things:
;
;    a)  Look at the SCB field, and if it is not zero, then we should return
;        this OPEN block with an error code.
;
;    b)   Otherwise, we should copy the "openmsg" across to the user buffer
;         (if he has given us one), and then send back a successful OPENACK
;         back to the caller.  If all that succeeds, then we should make a
;         byte stream control block from the result, and store that in the
;         user SCB field.

        LD      L,(IX+4)                ;  Load byte of SCB pointer
        LD      H,(IX+5)                ;  High byte of SCB pointer

        LD      A,(HL)                  ;  Low byte of SCB value
        INC     HL                      ;  Increment pointer
        OR      (HL)                    ;  Is the entry zero
        JP      Z,RCVRO1                ;  Yes, so we can continue

;  If we drop through here, then the "scb" field has not been cleared by the
;  user, and so we should return this OPEN block with an error return code.

        POP     IX                      ;  Restore old value of IX
        LD      HL,#X1234               ;  Random return code
        LD      DE,OACKMSG              ;  Pointer to OPENACK message
        CALL    ACKOPEN                 ;  Send the OPENACK block

        XOR     A                       ;  Set return code (from RXBLK)
        RET                             ;  And return

;  If we come here, then we can go ahead with the successful "openack", since
;  the user's scb field is ready to be updated.

RCVRO1  EX      (SP),IX                 ;  Restore old value of IX
        LD      HL,0                    ;  Success return code
        LD      DE,OACKMSG              ;  Pointer to OPENACK message
        CALL    ACKOPEN                 ;  Send the OPENACK block
        JP      Z,RCVRO2                ;  Success return code, so continue

;  If we drop through here, then the transmission of the openack failed, and
;  so we should go no further.

        POP     HL                      ;  Restore pointer to entry
        XOR     A                       ;  Set return code (from RXBLK)
        RET                             ;  And return

;  If we come here, then we have successfully transmitted the "openack", and
;  we have a BSCB pointer in IY.  We should now update the user scb location,
;  and copy across the OPEN message if required.

RCVRO2  EX      (SP),IX                 ;  Get pointer to entry
        LD      E,(IX+2)                ;  Low byte of openmsg pointer
        LD      D,(IX+3)                ;  High byte of openmsg pointer
        LD      A,D                     ;  Look to see if the ...
        OR      E                       ;  ... value is zero
        JP      Z,RCVRO3                ;  And if it is, do not copy

;  If we drop through here, then we must copy the OPENMSG from the temporary
;  buffer in which it has been stored, into the user buffer.

        LD      HL,TEMPBUF              ;  Address of temporary buffer
        LD      C,(HL)                  ;  Low byte of length
        INC     HL                      ;  Increment pointer
        LD      B,(HL)                  ;  High byte of length
        DEC     HL                      ;  Restore pointer

        INC     BC                      ;  Get number of words to copy
        SLA     C                       ;  And calculate the ...
        RL      B                       ;  ... number of bytes
        LDIR                            ;  And copy them over

;  When we come here, all that remains to be done is to turn the BSCB given to
;  us into a BCPL stream control block, and update the user SCB field with
;  the scb.

RCVRO3  LD      HL,S.UPB                ;  Size of stream control block
        CALL    GETVEC                  ;  Allocate the storage
        JP      NZ,RCVRO4               ;  Failed at the last moment - damn!

        PUSH    HL                      ;  Save pointer to storage
        EX      (SP),IX                 ;  Get pointer in IX
        CALL    B.FNDI                  ;  Call byte stream FIND routine
        PUSH    IY                      ;  Save pointer to BSCB
        POP     DE                      ;  And store in DE
        LD      (IX+S.ARG1+0),E         ;  Store low byte of BSCB
        LD      (IX+S.ARG1+1),D         ;  Store high byte of BSCB
        POP     IX                      ;  Restore IX

RCVRO4  LD      E,(IX+4)                ;  Low byte of SCB pointer
        LD      D,(IX+5)                ;  High byte of SCB pointer
        EX      DE,HL                   ;  Get pointer in HL
        LD      (HL),E                  ;  Store low byte of SCB
        INC     HL                      ;  Increment pointer
        LD      (HL),D                  ;  Store high byte of SCB

;  Now, all that remains is to restore the value if IX given to us, and set the
;  return code for RXBLK.

        POP     IX                      ;  Restore IX
        XOR     A                       ;  Set return code for RXBLK
        RET                             ;  And return

;  If we come here, then we have detected an internal error, and hence we
;  should call the debugger.

RCVRO0  ERRTRAP                         ;  Call the debugger
        LAYOUT
;  FPORT
;  -----
;
;    Find the entry in the RLIST which corresponds to the port number given
;    in HL.  The result is zero, if the table is full and the port is not
;    found, or a pointer to the entry for the port (on success), or a
;    pointer to the first empty slot (on failure).


FPORT   EX      DE,HL                   ;  Get port number in DE
        LD      HL,RLIST                ;  Pointer to list
        LD      B,RSIZE                 ;  Loop counter

FPORT0  LD      A,(HL)                  ;  Low byte of port number
        CP      E                       ;  Equal to the one given ?
        JP      NZ,FPORT1               ;  No, so look at next entry

        INC     HL                      ;  Increment pointer
        LD      A,(HL)                  ;  High byte of port number
        CP      D                       ;  Equal to the one given ?
        JP      NZ,FPORT2               ;  No, so look at next entry

        DEC     HL                      ;  Restore entry pointer
        RET                             ;  And return

FPORT1  INC     HL                      ;  + 1
FPORT2  INC     HL                      ;  + 2
        INC     HL                      ;  + 3
        INC     HL                      ;  + 4
        INC     HL                      ;  + 5
        INC     HL                      ;  + 6

        DJNZ    FPORT0-$                ;  Loop until end of list

;  If we drop through here, then the given port number was not found in the
;  list.  We should look for port zero now, because this will give us the
;  address of the first unoccupied slot.

        EX      DE,HL                   ;  Get port number in HL
        LD      A,H                     ;  High byte of port number
        OR      L                       ;  Is it already zero ?
        RET     Z                       ;  Yes, so return

        LD      HL,0                    ;  New port number
        JP      FPORT                   ;  Try again
        LAYOUT
;  RLIST
;  -----
;
;    Table of entries defining the "OPEN" blocks received.  Each entry is of
;    the form:
;
;        Port number
;        Pointer to OPENACK block
;        Pointer to BSCB word


RSIZE   EQU     20                      ;  Maximum number of table entries

RLIST   DEFW    0,0,0,  0,0,0,  0,0,0,  0,0,0
        DEFW    0,0,0,  0,0,0,  0,0,0,  0,0,0
        DEFW    0,0,0,  0,0,0,  0,0,0,  0,0,0
        DEFW    0,0,0,  0,0,0,  0,0,0,  0,0,0
        DEFW    0,0,0,  0,0,0,  0,0,0,  0,0,0
        LAYOUT
;  TXCB
;  ----
;
;    Transmit control block for use in synchronous transmissions (e.g. from
;    SSPREPLY).


TXCB    DEFW    0                       ;  Link field
        DEFW    0                       ;  Port field
        DEFB    0                       ;  Device field
        DEFB    1                       ;  Flags field
        DEFW    0                       ;  Buffer field
        DEFW    64                      ;  Maxleng field
        DEFW    0                       ;  Routine field
        LAYOUT
        ALIGN

        DEFW    0                       ;  End of global list
        DEFW    G.LKUP,LKUP             ;  Entry of LOOKUPNAME
        DEFW    G.SSPX,SSPX             ;  Entry of SSP
        DEFW    G.RCVS,RCVS             ;  Entry of RCVSSP
        DEFW    G.SSPR,SSPR             ;  Entry of SSPREPLY
        DEFW    G.BSPO,BSPO             ;  Entry of BSPOPEN
        DEFW    G.RCVO,RCVO             ;  Entry of RCVOPEN
        DEFW    G.MVTP,MVTP             ;  Entry of MAKEVTP
        DEFW    G.RCVC,RCVC             ;  Entry of RCVCANCEL
        DEFW    G.GET2,GET2             ;  Entry of GET2BYTES
        DEFW    G.PUT2,PUT2             ;  Entry of PUT2BYTES
        DEFW    G.SSPO,SSPO             ;  Entry of SSPOPEN
        DEFW    G.RFSP,RFSP             ;  Entry of RFSHSSP
        DEFW    G.SESP,SESP             ;  Entry of SENDSSP
        DEFW    G.STSP,STSP             ;  Entry of SENDTSSP
        DEFW    G.TESP,TESP             ;  Entry of TESTSSP
        DEFW    G.SSPN,SSPN             ;  Entry of SSPNPORT
        DEFW    G.BSPD,BSPD             ;  Entry of BSPDEST
        DEFW    G.MAXG                  ;  HRG
        LAYOUT
RINGE   END


