         title Software Dynamics Net Device Driver (C) Copyright 1984
sktmgr   equ   0                       ; since we don't have socket manager
comment  equ   0                       ; so " IF COMMENT" is "IF 0"
         if    comment
*** Things to do:
*** S/U SDOS: modify I/O package to record NetID and NodeID
***     Add CNFG:BOOTDEFAULTDISK to specify boot time-default
***     Add CNFG:BOOTCONSOLE to specify console device at boot
***     Add Syscall to change CONSOLE device?
***     Make CONSOLE: a logical device name, as under SDOS/MT
***     SDOS:ERRORINX entry point needed in SDOS
*** S/U SDOS Interlock: match NetID and NodeID of capability to that
***     specified by I/O package.  If identical, do NOP.
***     Else give to NET:. On a create call, return garbage except in
***     in NetID/NodeID bytes, which return local NodeID/NetId.
***     Add "Decrypt region" syscall to SDOS.?
***     Add "Encrypt region" syscall to SDOS (prepare for encrypted files).
***     Add "Do capability operation" to SDOS.
***
*** /MT: call S/U SDOS to get NetID and NodeId (see discussion of CREATE
***     below).   Compare with ID of supplied capability; if wrong,
***     complain and reject; else do interlock operation.
***     Ask SDOS/SU to create interlock object if create call (this tags
***     the interlock object with machine ID); fill rest of interlock
***     object with randomness.
***     Make /MT use SDOS "decrypt region" syscall (makes /MT smaller!)?
*
* Figure out way for User job to wait on multiple Interlocks with I/O devices.
* For now, access by NETSERVER program to NETSERVER: is by repeated calls on
* NETSERVER: polling for CC:ANYSOCKETREADY with a delay between calls.
* The delay can be adjusted to make NETSERVER compute bound with good
* net service, or I/O bound with bad service.
         fin   comment
         page
         if    comment
Software Dynamics Network Device Driver

This module is one of 4 modules required to allow SDOS to operate in a local
area network.  The modules required are:
      Network Device Driver (this module, supplied by SD)
      Network Server        (supplied by SD)
      Socket Manager        (supplied by SD)
      Link Driver           (installation-specific)

The Network Device Driver provides access to remote files in a fashion
transparent to the application program, by use of sysgen-time specified
filenames of the form:
       NNNNN:[<remotedevice:>][<remotefilename>]

The device name to which this driver responds are specified by DCBs which are
configuration specific. Each DCB ties the device name to a specific remote
node. Thus, the configuration of the network must be explained to the the I/O
package by installing DCBs for each node with which this node will need to
INITIATE a conversation. The implications are that the network configuration
must be relatively static. This limitation is due to the fact that SDOS cannot
resolve an unknown device name by assuming that it is, in fact, a remote node
name, and issuing a broadcast to determine what the remote node number is. It
is expected that SDOS will eventually treat unknown device names as though they
implicitly referenced this network device, so that the name can be resolved
over the network.

This device driver can be used in three different ways: LINK mode,
REMOTEFILEACCESS, and NETWORKSERVER mode, depending on the intention of the
user.

REMOTEFILEACCESS mode: This is the mode that allows an application program
to access a remote file or device by specifying not only the node name, but
also a non-null file name following it (i.e., DATABASE:DRIVE3:FRED.DATA). 
Any number of files may be opened to any number of remote nodes, subject
only to the number of I/O Channels configured into the local system and the
number of available Sockets and/or Node Representative Blocks. Remote files
are fundamentally handled by simply transmitting the desired system call
from the user node to a Network Server in a remote node; the server executes
the system call in the remote node, and sends the results back. To handle
multiple data streams to a single remote server from multiple users in the
local node, the Socket manager module must be present to handle the
multiplexing and de-multiplexing of those data streams.  If only a single
user is present on the local system, the Socket manager is unnecessary, as
calls which must wait for buffer space cause the invoking process to wait
until the buffer space is available, as with conventional device drivers.
RemoteFileAccess mode is automatically used indirectly by SDOS when
reference to a non-local Interlock is made; the socket used for the transfer
is the one associated with the user making the call for communicating with
the node specified in the Interlock capability (more on this later).
The socket used indirectly specifies the NRB required.

NETWORKSERVER mode: The Network Server program (an SDNET component supplied
by SD) must be running as a user in any node which allows its files to be
accessed remotely. This either means dedicating the node (if running
single-user) or dedicating a job under SDOS/MT. The server needs access to
the Socket Manager, which it gets by making specialized control and status
Syscalls through a dummy device named "NETSERVER:".  The DCB for NETSERVER:
is built into this module, but must be linked by the I/O package builder
into the non-disk device chain at ASSEMBLY (not BOOT!) time.
 HMMMM!!!??? --> To allow operation of the
server under SDOS/MT, and to remove the need to modify SDOS/MT to switch
users when the NETSERVER: device is not ready, the NETSERVER: device is
implemented
(cheap trick!) as a Virtual Terminal device (for which SDOS/MT knows how to
handle wait conditions).  The server executes a SYSCALL:READA on NET: to
wait for the next "Socket has ready data" event.  When the socket manager
discovers something of interest to the server, it feeds NET: an ASCII:ESC
character, which causes the Virtual Terminal driver to notify SDOS/MT, which
in turn wakes the network server. The server performs a series of operations
on NET: to read the message arriving from the ready socket, does the desired
operation, and sends the result back to the socket.  Finally, the server
waits again. The only big read/write operations are for WRBUF or RDBUF on
syscall blocks. The Server knows when it is transferring data of large size
chunks, and always limits its transfers to 256 bytes, thus getting past
SDOS/MT. Other data communicated thru the sockets is always less than 256
bytes, thus it is easy to get that data past SDOS/MT. When requests for
socket data, or to write socket data, cause the system to hang due to lack
of data or room, a hard wait occurs on the grounds that with high
probability, the problem will be rectified within a short period of time
(with ASLD, probably within 200mS).

>>>>>>think about a nice method of doing remote procedure
calls (this is really what you are trying to accomplish!)

LINK mode: The network device driver can be opened as a simple simple stream
of data bytes, by supplying only the device name (i.e., "FRED:" instead of
"FRED:FILE.EXTENSION"). In this mode, the driver will take data transferred
by SYSCALL:WRITEA or SYSCALL:WRITEB and reliably transfer it to the node
corresponding to the device name for reading via SYSCALL:READA or
SYSCALL:READB. This mode assumes that a program opens the network device in
LINK mode on the remote node. A SYSCALL:CLOSE will cause the remote reading
node to get an EndOfFile indication.  Thus this mode allows the network link
hardware to be treated as a single file (it can be used to accomplish a
simple file transfer). This mode of use is intended for diagnostic and/or
support purposes only, and is NOT SUPPORTED for use by application programs.
System programs should not count on this feature being permanent. This mode
interferes with operation of SDOS/MT because it will force the caller
to wait if the specified transfer cannot take place. This mode does not use
the Socket Manager at all; it has direct connections to the Link Driver.
This allows the Link Driver to be tested without the complications that
the Socket Manager brings.  Note that this mode is enabled ONLY if the
conditional assembly switches in this module are set to enable it, as this
code consumes considerable space.
         fin   comment
         page
         if    comment
This overview belongs someplace else.

OVERVIEW

The Socket Manager takes multiple logical streams from the network
file driver (or directly from application tasks) and multiplexes them
into single streams targeted for the various remote nodes for which they
are intended.  It likewise demultiplexes streams from remote nodes into
the multiple streams needed by the application tasks.  A Socket manager
in one node always communicates with an identical socket manager in other
nodes, so there must be Socket managers in all nodes that wish to perform
remote task-to-task communication or remote file access.  The Socket
Manager uses the Link Drivers to accomplish its function.  The Socket
Manager is a component supplied by SD.

The Link Driver moves data from one physical node to another, in an
error-free fashion.  Since it is link-technology dependent, a link driver
needs to be built for each instance of link-technology, just like a disk
driver.  A special link driver, ASLD (Asynchronous Serial Link Driver)
handles link hardware based on RS232 with (naturally) Asynch Serial
devices like the Motorola 6850 or the Synertek 6551.  ASLD can be obtained
in object form from SD.
         fin   comment
         page
         if    comment
DISCUSSION OF SOCKET HANDLING USED WITH REMOTE FILE I/O

Must use a single socket for all transactions issued by a job that are
directed to a particular target node. How many sockets should one use for
all transactions issued by a series of tasks working together to accomplish
the same effect ?

A socket, as well as dealing with flow control issues, seems to represent
the environment of the caller (i.e., contains an environment pointer)
(this forces the requirement for seperate sockets for each job). Thus the
number of sockets required depends on the number of environments needed to
accomplish the desired effect, as well as minimizing the interference
effects between different request. Actions requested via sockets can be
considered simply as function calls with environmental information
obtainable via the socket (consider the contour model of computation).
Thus requests for data from the environment flow back thru the socket to
the point of origin. Technically, each thread of execution would require a
seperate socket because each thread would have its own context. However, a
socket can be shared between two threads of execution if the references
made by the callee are only to portions of the enviroment shared by both
callers (i.e., high up in environment chain) and the interference between
execution of the two tasks is not a problem.  This does not seem like it
is worth the trouble to detect, and ensuring non-interference between two
tasks is difficult.

--> always allocate a socket when a task wishes service from a remote
node.  Sharing a socket between tasks is not worth the trouble.

If sockets were fast, one could allocate a socket for each individual
transaction that can occur at time of transaction; this requires no more
sockets than statically allocating a socket to task requires, and
statistically can require fewer than static allocation!. Furthermore, a
system can run with as few as ONE socket(s), albeit perhaps slowly.

--> the bias should be towards fast socket allocation. But we can't do
fast socket allocation unless we have idempotent operations, as in a true
capability system.

A problem with channel oriented I/O is that there is state information
(i.e., open channels) attached to the remote assigned socket, and the
remote file server wishes to release those channels should a requesting
task die.  The only way the remote system has of determining this is to
hold a socket to represent the requesting task. When the requesting task
dies, the socket dies, and the remote node immediately knows that all I/O
channels held for the requesting task can be released.  Thus we need a
socket to be held for the duration of an OPEN....CLOSE sequence from a
requesting task. We accomplish this by keeping a "Needs Socket" reference
counter, initialized to -1, which is incremented on SYSCALL:OPEN,
SYSCALL:CREATE, SYSCALL:DELETE, or SYSCALL:INTERLOCK, and decremented
after a SYSCALL:CLOSE, SYSCALL:INTERLOCK or SYSCALL:DELETE. If, after
incrementing, the counter is zero, then a socket is allocated to the task.
After decrementing, if the count is negative, the socket is deallocated.
Thus the socket stays allocated over the life of an OPEN...CLOSE
transaction, and is dynamically allocated for DELETE or INTERLOCK
transactions.

For SDOS 1.x, no job has more than one task, so we may allocate sockets
directly to jobs.  Since jobs are represented by user number, the user
number may be used to directly reference a socket. However, a particular
job may have several remote files open on different machines at the same
time, so several sockets may be allocated to a single job.  We handle this
by chaining all sockets related to a job together and hanging this chain
off of a logical User Control Block. Ideally, we would hang the chain from
a Task Control Block so that the sockets would be task-specific, but
SDOS/MT does not treat seperate users as seperate tasks and so a
task-specific solution would not work. Since SDOS/MT UCBs are not
accessible by the network driver, we simply use the logical bank number
last set by SELECTBANK (called by /MT) as the index of a list kept in this
driver.

If only a capability can be used to access a socket, then sockets cannot
be directly chained together ==> chain idea is blown unless we wish to
have a seperate pool of "chain-together" nodes, which is a real pain. We
could hang user-specific sockets from remote node DCBs; since there are
limited number of user, at most 8 slots per DCB would be required; but
this makes remote node DCB structure dependent on number of users that
SDOS/MT can handle.  It would seem to be more general to include a single
set of link pointers in each socket, and allow IOCBs to point to sockets.
We want the IOCB to contain socket reference so we don't have to search a
chain when IOCB reference is made.

In a real idempotent system, a task would need capability to reference
socket, and since we are moving towards that, we should implement using
capability scheme here.  With a true capability scheme, one need not
record the socket with the task at all! Since this implementation will
cause the invoking task to hang until a transaction is completed, it will
not be very useful for an SDOS/MT system to attempt use of this driver for
more than one user at a time; even for a single user under SDOS/MT
opening a remote file will probably cause severe performance degradation.
Even so, for generality, we implement the driver to keep a seperate chain
of sockets for each job, with an eye towards the future when socket chains
are hung from TCBs, should we ever have the chance to move to a true
idempotent system.

[[Note: We could make remote file accesses idempotent by storing the file
position locally, transmitting the file position as part of each read/write
request, and having the remote host ship the file position after the
operation back to us for storage.  For a true capability system, this would
be the correct thing to do.  For SDOS 1.x, this is not good, because the far
end would have to do a file OPEN for each transaction, making the file
transactions unnecessarily expensive.]]
         fin   comment
         page  Things To Do
         psr   0
         pca   0
         ifund m6800
m6800    equ   1
m6801    equ   0
m6809    equ   0
m6811    equ   0
         fin   m6800
         if    m6809
         with  wi=105,de=66
         else
         with  wi=105,de=66
         fin   m6809

version  equ   $09     Net Driver version 0.9
         ifund revision
 ? ; must specify REVISION on ASM OPTION (>) line
         fin
         if    revision<\'a
 ? ; REVISION specified predates this source file
         fin

; to assemble, refer to SDxxNET.do on the Components pack

; Things left to do:
;
;     Perhaps....
; add to DEFS file a conditional switch for each of the SDOS components:
;    file driver
;    MT
;    VT Driver
;    SDNET
         page Configuration parameters
         ifund sdos
         if    m6809
sdos     equ   $b200
         else
sdos     equ   $ae00
         fin   m6809
         fin   sdos

         if    m6809
netdevicedriverestimatedsize equ $700
         else  m6800!m6801!m6811
netdevicedriverestimatedsize equ $800
         fin   m6809

         page  Revision History
; Version 0.8 6/1/81 NCC demo version
; Version 0.9 2/1/85 Version for SDOS 1.1 using serial ports
         page Definitions
         tabs  11,19
         list  0
iopkdefs equ   1                       ; want all necessary definitions
         include sdos11defs.asm
         list  1
         pcc   1                       ; print control "cards"
         pca   1                       ; print conditional assembly
         psr   1                       ; print all skipped records
         include sdnet11adefs.asm
         page  I/O channel block equates for NET: usage
*        IOCB equates used by NET: (different layout than File system)
::       set   *
         org   iocb:dcb+2
iocb:processid rmb 2                   holds process id of task using channel
iocb:socket    rmb 2                   holds pointer to socket to use
iocb:netchannelcapability rmb capability:size holds capability for remote I/O channel
         if    net:test                for LINK driver mode
iocb:nrbpointer equ iocb:socket        points to NRB for remote physical node
         fin
         org   ::
         page   NET: device Entry Points
         ifund NetDeviceDriver
; Implementer did not specify where Network driver should go, so we suggest:
NetDeviceDriver equ VTDriver-netdevicedriverestimatedsize
         fin   NetDeviceDriver

******** Net Device Driver Entry points/Common Data vector *********

         org   NetDeviceDriver         ; specify at assembly time

; This vector is referenced by the network device DCBs
; and is used by SDOS to make read, write, control, and status
; requests of the Network driver.
; It is placed at the very front of the NET: driver to simply
; references to it from user-defined DCBs.

         fdb   netfile:open
         fdb   netfile:close
         fdb   netfile:reada
         fdb   netfile:readb
         fdb   netfile:writea
         fdb   netfile:writeb
         fdb   netfile:create
         fdb   netfile:rename
         fdb   netfile:delete
         fdb   netfile:control
         fdb   netfile:status
         fdb   netfile:reset

; Other sundry entry points to Network device driver follow this point

         jmp   netfile:interlock       entry point from SDOS for remote interlocks

netserver:dcb ; Device Control Block for NETSERVER: device
; This DCB is at a fixed offset from beginning of NET module...
; so that it can be linked to chain of I/O package devices
; This device is opened/used ONLY by NETSERVER module
         fcb   1                       dcb:doneflag
         fdb   0                       dcb:lasterror
         fdb   netserver:name          dcb:name
         rmb   2                       dcb:next (I/O pkg must supply this slot)
         fdb   netserver:driver        dcb:driver

FreeNRBs fdb   0                       points to list of available NRBs

******** End of Net Device Driver Entry points/Common Data vector *********
         page  Net Device Internal Data Area
netfile:remotesyscallerror rmb 2       holds error code returned by remote SDOS

NetFileUserSocketChains ; Socket chains used to do net file I/O for each job
; Each chain holds a list of sockets which are needed to continue an
; active conversation with a remote node.
; The list is indexed by LogicalBankNumber last selected by SelectBank
; Each slot in this table represents the head of a chain of sockets.
; The head is a "dummy" socket whose only valid content are the chain pointers.
; Each chain head is initialized to point at its own dummy socket chain head.
; The number of references to a socket is kept in the socket control block.
; A socket held on one of these chains has a reference count of at least one.
; Decrementing the reference count to zero causes the socket to be removed
;    from these chains.

         if    skt:userchainflink+2#skt:userchainblink
         ? ; User Socket chain implement requires link pointers to be contiguous
         fin
         rpt   8
         fdb   *-skt:userchainflink,*-2-skt:userchainflink ; initz to self-point


netserveropenflag fcb 0                <>0 --> NETSERVER: already opened

netserver:opensockets ; list of live sockets in use by NETSERVER:
         fdb netserver:opensockets-skt:userchainflink ; initially empty
         fdb netserver:opensockets-skt:userchainflink ; initially empty
         page
netfile:LocateUserSocketChain ; return socket chain pointer in (X) for current user
         ldb   sdos+sdos:logicalusernumber ; set by last call to SelectBank
         aslb                          ; (assert: 0 if single user system!)
         aslb                          ; 4 bytes per socket chain entry
         ldx   #NetFileUserSocketChains-skt:userchainflink ; ptr to 1st dummy socket
         abx                           ; get pointer to desired chain
         rts

netfile:releasesocket ; release socket (X) from user
; A socket represents conversation with file server in remote node.  If
; multiple files are open by this user to that remote node, then the socket
; is still needed.  We check this by decrementing the User Chain socket
; reference count. If, after decrementing, the reference count is zero,
; remove the socket from chain and throw it back into free pool, otherwise
; leave the socket alone.
         dec   skt:userchainreferencecount,x ; down count # references left
         bne   netfile:releasesocketrts ; b/ socket still needed by user
netfile:removesocketfromuserchain ; remove socket (X) from user chain
; Assert: socket IS in a user chain (or on the NETSERVER: chain)
; Note that socket can only be in one user chain, as it represents one end
; of a conversation-state between one user and the remote file server.
; We use a doubly-linked circular list to ensure simple removal from chain.
         if    m6800!m6801
         stx   tempx                   save pointer to node to remove
         fin
         ldd   skt:userchainflink,x    find socket that follows one to be removed
         if    m6800!m6801
         ldx   skt:userchainblink,x    find socket preceding one to be removed
         std   skt:userchainflink,x    make preceding socket point to next socket
         stx   tempx                   save preceding socket's address
         else  (m6809)
         ldu   skt:userchainblink,x    find socket preceding one to be removed
         std   skt:userchainflink,u    make preceding socket point to next socket
         fin
         ldx   skt:userchainflink,x    grab address of following socket
         if    (m6800!m6801)
         ldd   tempx                   make following socket point backwards...
         std   skt:userchainblink,x    to preceding socket
         else  (m6809)
         stu   skt:userchainblink,x    make following socket point backwards to preceding socket
         fin
         jsr   sktmgr+socket:destroy   tell socket manager this socket is dead
         ; this notifies remote file server that it need no longer hold its
         ; socket open.  It also places local socket back into free pool of
         ; sockets.
? ; DO WE NEED TO RE-ARM SOCKET TO LISTEN??
netfile:linksockettoself ; entry point used by initz routines
         if    m6800!m6801
         ldx   tempx
         fin
         txd                           make (D) point to socket
         std   skt:userchainflink,x    make forward link point to socket
         std   skt:userchainblink,x    make backward link point to socket
netfile:releasesocketrts ; re-enter here from netfile:releasesocket
         rts                           and exit
         page
netfile:DetermineSocketFromDCB ; find socket to use for this file request
; Entered with DCBPointer pointing to DCB to use
         ldx   dcbpointer              which DCB referenced
         lda   dcb:netid,x             selects network containing remote node
         ldb   dcb:nodeid,x            fetch identity of remote node desired
netfile:DetermineSocketFromID ; find socket according to Node identity
; Called to determine which socket to use if not already known
; Entered with (A) holding NetID for remote node
;              (B) holding NodeNumber for remote node in network (A)
;         Assert: contents of DCBPOINTER have already been saved. This is
;         necessary because SocketPointer is in same location as DCBpointer.
; Exits with (X) pointing to socket established for conversation
;       (also sets SocketPointer to point to desired socket)
;       or exits with error such as "No available sockets"
;
; A socket represents conversation with file server in remote node.  If
; multiple files are open by this user to that remote node, then only one
; socket is used. We check this by searching the User Socket chain
; of this user for a socket already connected to the remote node.
; If one exists, that socket is being used for file services (because
; only file service sockets are placed on the User Socket chain),
; and so we return that value after incrementing the socket user reference
; count.  If no such socket exists, we allocate a Transmit Socket
; looking for "SDOS File System Service", and add it to the chain
; with a reference count of one.
         pshd                          save socket parameters
         bsr   netfile:LocateUserSocketChain ; find head of User Socket chain
         puld                          restore remote node ID desired
         stx   tempx                   remember location of head of chain
         stx   SocketPointer           set up for ldx below
         ; >>> lock chain against access by other tasks here
netfile:DetermineSocketCheckChainLoop ; see if next chain entry is desired socket
         ; a loop down chain is sufficient because chain size usually short
         ldx   SocketPointer           fetch pointer to socket that doesn't match
         ldx   skt:userchainflink,x    follow pointer to next socket in chain
         cmpx  tempx                   end of chain with no match found ?
         beq   netfile:DetermineSocketNew b/ yes, need new socket
         stx   SocketPointer           assume this socket matches
         ldx   skt:nrbpointer,x        find NRB socket is using
         cmpa  nrb:nodeid,x            socket use NRB with matching node number ?
         bne   netfile:DetermineSocketCheckChainLoop b/ no, try next socket
         cmpa  nrb:netid,x             socket use NRB with matching network ID?
         bne   netfile:DetermineSocketCheckChainLoop b/ no, try next socket
         ; found matching socket!
         ; >>> unlock chain against access by other tasks here
         rts                           found matching Socket!
         page
netfile:SocketClass fcc "SDOS File Service" ; name of socket service desired

netfile:DetermineSocketNew ; no matching socket in User Socket chain
         ; unlock chain against access by other tasks here
         ldx   #netfile:SocketClass
         ; assert: (A) has NetID, (B) has NodeID
         jsr   sktmgr+socket:createtransmit create a transmit socket
         ; This implicitly creates an NRB if one does not already exist
         ; The socket address is returned in (X), SocketPointer
         ; now add socket to User Socket Chain
         ; >>> lock chain against access by other tasks here
         bsr   netfile:LocateUserSocketChain ; find head of User Socket chain
         bsr   netfile:insertsocketintouserchain stuff socket into user-specific chain
         ldx   SocketPointer           set up socket pointer for use
         ; >>> unlock chain against access by other tasks here
netfile:DetermineSocketDone ; (X) points to socket to use
         inc   skt:userchainreferencecount,x ; record # references to socket
         ldd   SocketPointer           return pointer to socket in (D)
         okrts

netfile:insertsocketintouserchain ; insert Socket at SocketPointer into chain (X)
         if    m6800!m6801!m6811
         stx   tempx                   remember location of head of chain
         ldd   skt:userchainflink,x    find 1st socket currently in chain
         ldx   SocketPointer           get pointer to new socket to insert
         std   skt:userchainflink,x    make new socket point to former 1st socket
         ldd   tempx                   find head of chain again
         std   skt:userchainblink,x    make new socket point to head of chain
         ldx   skt:userchainflink,x    pointer to former 1st socket
         ldd   SocketPointer           pointer to new socket
         std   skt:userchainblink,x    make former 1st socket point back to new
         ldx   tempx                   pointer to head of chain
         std   skt:userchainflink,x    make head of chain point to new socket
         else  (m6809)
         ldy   skt:userchainflink,x    find 1st socket currently in chain
         ldu   SocketPointer           address of new socket
         stu   skt:userchainblink,y    make former 1st socket point back to new socket
         stu   skt:userchainflink,x    make socket chain head point to new socket
         sty   skt:userchainflink,u    make new socket point to former 1st
         stx   skt:userchainblink,u    make new socket point back to chain head
         rts
         fin
         page  netfile:reset code

         fcb     version,revision      must immediately precede NETFILE:RESET

netfile:reset ; come here at power up time
; called once per DCB at boot time, and then never called again
         ldx   FreeNRBs                is network software already initialized?
         bne   netfile:resetokrts      b/ network software is running already
         ldx   sdos+sdos:configuration find list of NRBs
         ldx   cnfg:nrbchain,x         get pointer to list of NRBs
         bne   netfile:resetnrbsloop   b/ at least one NRB exists
         jsr   sdos+sdos:error
         #err:nofreeNRBs               there must be at least one!
netfile:resetnrbsloop ; loop here to reset individual NRBs
; We reset NRBs now so that when they are assigned (sometimes on demand
; by an interrupt routine) they are ready for use with no extra work.
         stx   NRBpointer              save pointer to next NRB
         jsr   ld:releaseNRB           reset NRB and place into free NRB chain
         ldx   NRBpointer              get pointer to released NRB
         ldx   nrb:nextnrb,x           find next NRB
         bne   netfile:resetnrbsloop   b/ more NRBs to reset

         ldx   sdos+sdos:configuration find list of Physical Link Control Blocks
         ldx   cnfg:NetPLCBChain,x     find address of list of PLCBs
         bne   netfile:resetPLCBsloop  b/ at least one PLCB exists
         jsr   sdos+sdos:error
         #err:noPLCBs                  must be at least one PLCB
netfile:resetPLCBsloop ; loop here to reset individual PLCBs
; This loop calls code to reset each individual piece of link hardware
; (represented by a PLCB).
; Once a piece of link hardware has been reset, it may immediately
; begin receiving messages from nodes connected to it.  Note that
; the NRBs have already been reset so they are immediately available.
         stx   PLCBpointer             save pointer to this PLCB
         ldx   PLCB:LinkDriver,x       find link driver that matches
         jsr   [lde:reset,x]           reset PLCB using this link driver
         ldx   PLCBpointer
         ldx   PLCB:nextPLCB,x         find next physical link control block
         bne   netfile:resetPLCBsloop  b/ more PLCBs to reset
netfile:resetokrts ; network is running
         okrts
         page  netfile: open and create
netfile:open ; come here after OPENing "nodename:" or "nodename:filename"
netfile:create ; come here after CREATEing "nodename:" or "nodename:filename"
; SDOS has checked to make sure this channel is not OPEN (so we need not)
         if    net:test
         jmp   link:open               which will decide if LINK or FILE mode
         fin
netfile:openremotefile ; user wants to access remote file by name
; Save DCBPointer here, because determining which socket to use destroys it
; (We must save DCBPointer, as it is not yet stored in IOCB:DCB)
; (A change to SDOS in the OPEN logic will eventually make above statement untrue)
         ldd   DCBpointer              fetch pointer to DCB to use
         pshd                          and save it
         bsr   netfile:openremoteguts  go do the hard part
         bcc   netfile:openremotedone  b/ success! go clean up and exit
         puld                          restore DCB pointer
         std   DCBpointer
         jmp   sdos+sdos:errorinx      and then signal the difficulty

netfile:openremotedone ; remote open was a success!
         puld                          restore DCB pointer
         std   DCBpointer
         okrts

netfile:openremoteguts ; do the hard work required
         jsr   netfile:DetermineSocketFromDCB ; find socket to use for this file request
         ldx   sdos+sdos:iocbpointer   which IOCB to update
         std   iocb:socket,x           remember which socket to use
         ldd   sdos+sdos:currentask    get process ID
         std   iocb:processid,x        pledge channel allegiance to this task
         jsr   netfile:filenamesyscall send syscall block content
         bcs   netfile:openfailed1     b/ remote node died while trying to open
         ldx   netfile:remotesyscallerror inspect error code received
         ; on error on OPEN, no channel capability is returned
         bne   netfile:openfailed2     b/ error occurred trying to open file
         if    m6800!m6801!m6811
         ldb   #iocb:netchannelcapability compute where to receive capability
         ldx   sdos+sdos:iocbpointer
         abx                           = where to receive capability
         else  (m6809)
         leax  iocb:netchannelcapability,x recieve and save channel capability
         fin
         ldd   #capability:size        how many bytes to collect
         jsr   netfile:receiveblock
         okrts

netfile:openfailed1 ; remote node died while trying to open
netfile:openfailed2 ; error occurred trying to open file
? ; what prevents an infinite forwarding process (i.e., SAM:FRED:SAM:FRED:...?)
; How about...No server in FRED:?
         jsr   sdos+sdos:errorsave     save the error code in (X)
         bsr   netfile:closecleanup    clean up after disaster
         jmp   sdos+sdos:errored       and propogate the error
         page
netfile:close ; come here to execute a remote SYSCALL:CLOSE
         bsr   netfile:commonguts      go do common net operation
         bcc   netfile:operationdone   b/ no error, clean up after closing
         jsr   sdos+sdos:errorsave     save the error code
         ; there is a problem here if the wrong task closes the channel!
         bsr   netfile:closecleanup    clean up after closing
         jmp   sdos+sdos:errored       and propogate the error

netfile:closecleanup
         ldx   SocketPointer           find socket address
         jsr   netfile:releasesocket   and let go of socket
;        jsr   netfile:operationdone   do completion cleanup
;        okrts

netfile:operationdone ; netfile operation completed, do common cleanup
         ldx   sdos+sdos:iocbpointer   restore the DCBpointer
         ldd   iocb:dcb,x              by pulling it out of the DCB
         std   DCBPointer
netfile:okrts ; signal success and exit
         okrts
         page  main net functions
netfile:reada ; come here to execute a remote SYSCALL:READA
netfile:readb ; come here to execute a remote SYSCALL:READB
netfile:writea ; come here to execute a remote SYSCALL:WRITEA
netfile:writeb ; come here to execute a remote SYSCALL:WRITEB
netfile:control ; come here to execute a remote SYSCALL:CONTROL
;                 We outlaw CC:WRITEANOWAIT over the network.
netfile:status ; come here to execute a remote SYSCALL:STATUS
; >>> Keyed file reads should be done with special CC:/SC: Syscalls
; to prevent unnecessary transfers over the network
         ldx   sdos+sdos:iocbpointer   make sure no other process is using IOCB
         ldd   iocb:processid,x
         subd  sdos+sdos:currentask
         beqd  netfile:common
         jsr   sdos+sdos:error         signal error
         #err:busyforanotherprocess

netfile:common ; come here to perform SYSCALL remotely
         bsr   netfile:commonguts      set up an error recovery context
         bcc   netfile:operationdone   b/ completed no error: finish up
netfile:commonerror ; come here with error code in (X)
         jsr   sdos+sdos:errorsave     save the error code
         bsr   netfile:operationdone   finish cleanup
         jmp   sdos+sdos:errored

netfile:commonguts ; come here to do the work of performing SYSCALL remotely
; While NETFILE: code is executing, location SocketPointer contains a pointer
; to the socket to use.  Since this location conflicts with DCBPointer,
; we save the DCBPointer so that we can restore it when we exit.
         ldx   sdos+sdos:iocbpointer   get place to store DCBpointer
;        ldd   DCBpointer              fetch DCBpointer
;        std   iocb:dcb                save DCBpointer so we can restore later
; (We need do nothing to save DCBPointer, as it is already stored in IOCB:DCB)
;
; The socket is valid until it is explicitly destroyed, and this cannot
; happen until all channel references to are erased, which can only happen
; immediately after a close operation.  Since this point in the execution path
; is not immediately a(fter) close operation, the socket is still exists.
         ldx   iocb:socket,x           fetch pointer to socket being used
         stx   SocketPointer           set up pointer for Socket Manager to use
         jsr   netfile:channelsyscall  process syscall block content
netfile:exit ; come here to check exit conditions
         ldx   netfile:remotesyscallerror inspect error returned by remote SDOS
         beq   netfile:okrts           b/ no error returned by remote SDOS
         jmp   sdos+sdos:errorinx      propogate the error

sdos:errorinx equ *-sdos ; this should go away when SDOS is modified
?        jsr   sdos+sdos:errorsave     save the error code
         jmp   sdos+sdos:errored       and then propogate it
         page  netfile:rename
netfile:rename ; come here when SYSCALL:RENAME applied to network file
; SDOS ensures that a file opened as SAM:XYZ... must be renamed to
; SAM:DEF... or it will cause a ERR:RENAMEDEVICE.
; TO DO THIS, MUST PATCH OUT "CPX #DISKFILEDRIVER\BNE RENAME1" in SDOS:RENAME
; NOTE: RENAME "SAM:FILE.EXT","FILE.ABC" is interpreted by SDOS as
;       RENAME "SAM:FILE.EXT","DISK:FILE.ABC" which will cause an error.
;       Perhaps SDOS:RENAME logic could simply assume same device, and
;          make an unnecessary check go away?
         bsr   netfile:renameguts      go do the hard work
         bcc   netfile:operationdone   b/ completed no error: finish up
         bra   netfile:commonerror     b/ operation compeleted with error

netfile:renameguts ; do the hard part of remote rename
; Save DCBPointer here, because determining which socket to use destroys it
         ldx   sdos+sdos:iocbpointer   get place to store DCBpointer
;        ldd   DCBpointer              fetch DCBpointer
;        std   iocb:dcb                save DCBpointer so we can restore later
; (We need do nothing to save DCBPointer, as it is already stored in IOCB:DCB)
;
         ldx   iocb:socket,x           fetch pointer to socket being used
         stx   SocketPointer           set up pointer for Socket Manager to use
         jsr   netfile:filenamesyscall process syscall block content
         bra   netfile:exit            all done
         page  netfile:delete
netfile:delete ; come here when SYSCALL:DELETE applied to network file
; i.e., a call of the form DELETE "SAM:FILE.EXT" has been made, and
; SAM: is a DCB marked as a network device.
; Note we DO NOT HAVE an IOCB! But we don't need channel capability.
; Save DCBPointer here, because determining which socket to use destroys it
         ldd   DCBpointer              fetch DCBpointer
         pshd                          save DCBpointer so we can restore later
         bsr   netfile:deleteguts      go do the hard work
         bcc   netfile:deletedone      b/ completed no error: finish up
         jsr   sdos+sdos:errorsave     save error code
         bsr   netfile:deletedone      clean up after delete
         jmp   sdos+sdos:errored       and propogate the error code

netfile:deletedone ; delete completed, restore DCBpointer
         puld                          recover DCBpointer from stack
         std   DCBpointer
         okrts

netfile:deleteguts ; do the hard part of remote deletion
         jsr   netfile:DetermineSocketFromDCB ; find socket to use for this file request
;        ldx   #0                      set IOCB to dummy IOCB
;        stx   sdos+sdos:iocbpointer
         jsr   netfile:filenamesyscall process syscall block content
; note: above call sends a garbage channel capability, but we don't care.
         jsr   sktmgr+socket:xmithint  signal that transaction is ready
         jsr   netfile:receiverdbuf    get reply buffer
         ldx   SocketPointer           get which socket to release
         jsr   netfile:releasesocket   release the socket
         bra   netfile:exit            all done
         page
netfile:interlock ; come here to handle a remote network interlock call
; SDOS has determined by inspecting Interlock object that it does
; not live on the local computer, and has passed control here.
; (via entry point vector placed at front of this driver)
; This routine sends the SYSCALL:INTERLOCK request to the remote node
; specified by the interlock capability, which will perform the operation,
; and awaits the response.
; ? what do we do if capability specifies another network ?
; Note: Interlocks do not use DCBpointer, so we need not save it.
         ldd   capability:netid,x      fetch NetID and NodeID from capability
         jsr   netfile:DetermineSocketFromID ; find socket to use for interlock
         jsr   netfile:nonchannelsyscall
         ldx   SocketPointer           get which socket to release
         jsr   netfile:releasesocket   release the socket
         bra   netfile:exit            all done
         page  SYSCALL transmission
netfile:filenamesyscall ; process system call with SCBLK:WRBUF holding file name
; This must be treated specially because SDOS has already scanned past the
; node name and adjusted SCBLK:RDBUF to hold the count.  What we must
; logically do here is to send the system call block as usual, but send a
; shortened version of SCBLK:WRBUF, minus the characters already scanned by
; SDOS. What we actually do is to send the system call block and WRBUF
; just as they are; we also send 2 bytes of SCBLK:RDBUF so the receiver
; knows how much of SCBLK:WRBUF to ignore.  Thus we make our work here
; simple by loading extra work on the network server.
; Note: Network server must send back reply buffer contents that
; count not only the bytes the local SDOS scanned, but also the bytes
; that were scanned at the server's end.  Thus we need do nothing at
; this end except receive the standard reply buffer.
         ldaa  #SDOSserverfilenamesyscall signal to NETSERVER what follows
         jsr   netfile:xmitbyte
         bsr   netfile:sendsyscallblock send the system call block itself
         bsr   netfile:sendsyscallwrbuf send SCBLK:WRBUF contents
         ldx   sdos+sdos:ioblockptr    send SCBLK:RDBUF for 2 bytes
         ldx   scblk:rdbuf,x           = length of scanned file name
         ldd   0,x                     fetch length of node name
         jsr   netfile:sendD           send length of node name
         jsr   netfile:sendchannelcapability ; send capability for remote I/O channel
         jsr   sktmgr+socket:xmithint  signal that transaction is ready
         jsr   netfile:receiverdbuf    get reply buffer
         okrts

netfile:channelsyscall ; come here to transmit I/O syscall and obtain result
; (I/O syscall blocks contain a channel number in SCBLK:PARAMS)
; On entry, SocketPointer must be set
         ldaa  #SDOSserverfileiosyscall    signal to NETSERVER what follows
         jsr   netfile:xmitbyte
         bsr   netfile:sendchannelcapability ; send capability for remote I/O channel
         bsr   netfile:sendsyscallblock    send syscall block content
         bsr   netfile:sendsyscallwrbuf    send syscall WRBUF content
         jsr   sktmgr+socket:xmithint      signal that transaction is ready
         bsr   netfile:receiverdbuf        get syscall RDBUF reply contents
         okrts

netfile:nonchannelsyscall ; come here to transmit a Non-channel syscall and obtain result
; (Non-channel syscall blocks don't contain a channel number in SCBLK:PARAMS)
; This entry point is called via SDOS, not via I/O channel call
; On entry, SocketPointer must be set
         ldaa  #SDOSservernoniosyscall     signal to NETSERVER what follows
         jsr   netfile:xmitbyte
         bsr   netfile:sendsyscallblock    send syscall block content
         bsr   netfile:sendsyscallwrbuf    send syscall WRBUF content
         jsr   sktmgr+socket:xmithint      signal that transaction is ready
         bsr   netfile:receiverdbuf        get syscall RDBUF reply contents
         okrts
         page
netfile:sendsyscallblock ; send body of syscall block to remote node for execution
; This routine sends the entire syscall block, including SCBLK:EXTENSION,
; even though SCBLK:WRBUF, SCBLK:RDBUF and SCBLK:RPLEN are useless, because
; it is more work to not NOT send them then it is to send and ignore them.
; This is in line with our strategy that this driver does as little as
; as possible, and the network server does as much as possible.
         ldx   sdos+sdos:ioblockptr    get pointer to syscall block
         ldab  scblk:wlen,x            get length of syscall block
         andb  #$7f                    (drop NOWAIT bit)
         clra                          sign extend to 16 bits
         bra   netfile:xmitblock       send syscall block

netfile:sendsyscallwrbuf ; now send SCBLK:WRBUF if valid
         ldx   sdos+sdos:ioblockptr    get pointer to syscall block
         ldab  scblk:wlen,x            get length of syscall block
         andb  #$7f                    (drop NOWAIT bit)
         cmpb  #scblk:wrlen+2          is WRBUF data valid ?
         blo   netfile:sendsyscallwrbufokrts b/ no, send 0 bytes for WRBUF
         ldx   sdos+sdos:ioblockptr    get pointer to syscall block
         ldd   scblk:wrlen,x           get number of bytes to transmit
         jsr   netfile:sendD           and send it
         ldx   sdos+sdos:ioblockptr    get pointer to syscall block
         ldd   scblk:wrlen,x           get number of bytes to transmit
         ldx   scblk:wrbuf,x           and where to send them from
         jsr   netfile:xmitblock       send block of data bytes
netfile:sendsyscallwrbufokrts
         okrts

netfile:sendchannelcapability ; send I/O channel capability stored in IOCB
         ldx   sdos+sdos:iocbpointer   get pointer to syscall block
         if    m6800!m6801!m6811
         ldb   #iocb:netchannelcapability ; find address of capability
         abx
         else  (m6809)
         leax  iocb:netchannelcapability,x ; find address of capability
         fin
         ldd   #capability:size        get number of bytes to transmit
         jmp   netfile:xmitblock       send block of data bytes
         page  Syscall Reply buffer receive logic
netfile:receiverdbuf ; receive the contents of the reply data buffer
; We don't need to handle case where remote sent too large a reply, as
; remote server cannot send response longer than our RDBUF unless he is sick.
         jsr   netfile:receiveD        get two byte remote SDOS error code
         std   netfile:remotesyscallerror save the error code
         ldx   sdos+sdos:ioblockptr    is there a reply buffer in Syscall blk?
         ldaa  scblk:wlen,x
         anda  #$7f
         cmpa  #scblk:rdbuf+4          (i.e., RDBUF and RDLEN defined?)
         blo   netfile:receiverdbufdone b/ no, time to exit!
         jsr   netfile:receiveD        get two byte reply buffer length
         ldx   sdos+sdos:ioblockptr    get pointer to system call block
         std   scblk:rplen,x           store length of reply
         ldx   scblk:rdbuf,x           get pointer to reply buffer
         jsr   netfile:receiveblock    receive response into SCBLK:RDBUF
netfile:receiverdbufdone
         okrts
         page  NetFile:-to-Socket-Manager interface routines
netfile:xmitblock ; (X) points to buffer of data to send
; (D) contains count of data to send
; SocketPointer has been set to point to desired socket
         jsr   sktmgr+socket:writeblock send a block via socket manager
         okrts

netfile:sendD ; send (D) to currently chosen socket
         pshb                          save 2nd byte
         bsr   netfile:xmitbyte        send 1st byte
         bcs   netfile:errored1s       oops, something wrong with network
         pulb                          restore 2nd byte
;        bsr   netfile:xmitbyte        transmit 2nd byte
;        okrts

netfile:xmitbyte ; send byte in (A) to currently chosen socket
; SocketPointer has been set to point to desired socket
         jsr   sktmgr+socket:writebyte send byte to socket
         okrts

netfile:receiveD ; read pair of bytes into (D) form currently chosen socket
         bsr   netfile:receivebyte     get 1st byte
         psha                          save on stack
         bsr   netfile:receivebyte     get 2nd byte
         bcs   netfile:errored1s       b/ error, no 2nd byte available
         tfr   a,b                     move 2nd byte to (B)
         pula                          restore 1st byte to make 16 bits
         okrts

netfile:errored1s ; failed with byte on top of stack
         ins                           get rid of byte
         jmp   sdos+sdos:errorinx

netfile:receivebyte ; read byte (A) from currently chosen socket
; SocketPointer has been set to point to desired socket
         jsr   sktmgr+socket:readbyte  get a byte via the socket manager
         okrts

netfile:receiveblock ; receive a block of data from a socket
; (X) points to reply buffer, (D) contains max reply length
; SocketPointer has been set to point to desired socket
; ?? what if remote sends too many bytes ??
         jsr   sktmgr+socket:readblock get a block of data via socket manager
         okrts

netfile:patch  ; vt driver patch space (heaven forbid it should get used...)
         rpt  100
         swi
         page NETSERVER: device code
netserver:name fcc "NETSERVER:"
         fcb   0

netserver:driver ; driver vector for NETSERVER: device
         fdb   netserver:open
         fdb   sdos+sdos:illegaldeviceoperation create
         fdb   sdos+sdos:illegaldeviceoperation reada
         fdb   sdos+sdos:illegaldeviceoperation readb
         fdb   sdos+sdos:illegaldeviceoperation writea
         fdb   sdos+sdos:illegaldeviceoperation writeb
         fdb   sdos+sdos:illegaldeviceoperation create
         fdb   sdos+sdos:illegaldeviceoperation rename
         fdb   sdos+sdos:illegaldeviceoperation delete
         fdb   netserver:control
         fdb   netserver:status
         fdb   netserver:reset

netserver:reset ; NETSERVER: device Reset logic
? ; is it really this simple?
         okrts

netserver:open ; LINK: device Open routine
; Nothing interesting happens on an Open
         lda   netserveropenflag       already opened?
         bne   sdos+sdos:illegaldeviceoperation b/ yes, can't open again!
         inc   netserveropenflag       no, so mark as opened
         okrts
         page
netserver:close ; LINK: device Close routine
         bsr   netserver:commonsetup   go do common setup code
         ; Loop thru open sockets, releasing them.
         ldx   #netserver:opensockets-skt:userchainflink pointer to list of sockets being served
netserver:closeloop ; close a socket owned by server
; This loop is unnecessary if Server module always exits correctly.
; But an ounce of paranoia is worth a ton of bug defenses, so we leave code in.
         ldx   skt:userchainflink,x    find next socket attached to this server
         cpx   #netserver:opensockets  any more sockets to release ?
         beq   netserver:closedone     b/ no more sockets to close
         stx   SocketPointer           set up pointer to socket properly
         bsr   netfile:releasesocket   let go of socket
         bcc   netserver:closeloop     b/ more sockets to close
netserver:closeerrored ; croaked trying to close a socket
         ldx   SocketPointer           throw the error away
         bra   netserver:closeloop

netserver:closedone ; no more sockets to close
         okrts                         signal successful completion

netserver:commonsetup ; come here to set up to process SYSCALL
; While NETSERVER: code is executing, location SocketPointer contains a pointer
; to the Socket to use.  Since this location conflicts with DCBpointer,
; we save the DCBpointer so that we can restore it when we exit
;        ldx   sdos+sdos:iocbpointer
;        ldd   DCBpointer              fetch DCBpointer
;        std   iocb:dcb,x              save DCBpointer so we can restore later
; (we need do nothing to save DCBpointer, as it is already stored in IOCB:DCB)
         pulx                          fetch return address
         ldd   #netserver:commonexit   set to exit thru common code
         pshd
         jmp   0,x                     return to caller

netserver:commonexit ; come here to exit NETSERVER driver SYSCALL routine
         bcc   netserver:commonexitcleanup  b/ no error, just exit
         jsr   sdos+sdos:errorsave     save the error code
         bsr   netserver:commonexitcleanup  undo damage to DCBpointer
         jmp   sdos+sdos:errored       and propogate the error code

netserver:commonexitcleanup ; come here to clean up when operation is complete
         ldx   sdos+sdos:iocbpointer   restore the DCBpointer
         ldd   iocb:dcb,x              by pulling it out of the DCB
         std   DCBpointer
         rts
         page
netserver:control ; NETSERVER: device perform control functions
         bsr   netserver:commonsetup   go do common setup code
         ldx   sdos+sdos:ioblockptr
         ldab  scblk:params+1,x
; >>> allow CC:WAITANYREADYSOCKET status request (must simulate with timeout!)
         cmpb  #cc:writesocketdata     write data to a specific socket ?
         beq   netserver:writesocketdata b/ yes, go handle
         cmpb  #cc:destroysocket       destroy a socket ?
         beq   netserver:destroysocket b/ yes, go handle
netserver:illdevop ; go complain
         jmp   sdos+sdos:illegaldeviceoperation

sdos:illegaldeviceoperation equ *-sdos
         jsr   sdos+sdos:error
         fdb   err:illdeviceop

netserver:writesocketdata ; user wants to write data to a socket
         bsr   netserver:validatesocketcapability
         ldx   sdos+sdos:ioblockptr    fetch pointer to I/O block
         ldd   scblk:wrlen,x           fetch pointer to write buffer
         ldx   scblk:wrbuf,x           and data source
         jsr   sktmgr+socket:writeblock send data to socket
         okrts

netserver:destroysocket ; service Destroy Socket request
         bsr   netserver:validatesocketcapability
         jsr   netfile:removesocketfromuserchain rip socket out of active list
         okrts

netserver:validatesocketcapability ; ensure that socket capability...
; is attached to end of SYSCALL block as required
; Socket capability is 16 bytes long
         ldx   sdos+sdos:ioblockptr    fetch pointer to I/O block
         lda   scblk:wlen,x            fetch length of SYSCALL block
         anda  #$7F                    mask off "Wait" bit
         cmpa  #scblk:end+capability:size
         blo   netserver:syscallblocktooshort b/ socket capability not supplied
         ldx   scblk:end+capability:locatorinfo+2,x fetch pointer to socket
         stx   SocketPointer           remember which Socket to use
         okrts                         and exit

netserver:syscallblocktooshort
         jsr   sdos+sdos:error
         fdb   err:syscalltooshort
         page
netserver:status ; NETSERVER: device Status request
         bsr   netserver:commonsetup   go do common setup code
         ldx   sdos+sdos:ioblockptr
         ldab  scblk:params+1,x
         cmpb  #sc:getbuffercount      ...?
         beq   netserver:getbuffercount b/ yes, go handle
         cmpb  #sc:readsocketdata      ...?
         beq   netserver:readsocketdata  b/ yes, go handle
         cmpb  #sc:anysocketready      see if some socket needs service ?
         beq   netserver:anysocketready b/ yes, go handle
         cmpb  #sc:createreceivesocket a Create Receive Socket request?
         beq   netserver:createreceivesocket b/ no, go complain
         jmp   sdos+sdos:illegaldeviceoperation go complain

netserver:createreceivesocket ; Control Call to create a receive socket
         jsr   sdos+sdos:checkrdlen    make sure enough buffer space is available
         fdb   capability:size
         clra                          "from any Network..."
         clrb                          "from any Node..."
         ; why can't I specify the TASK or xmit socket I wish to receive from?
         ldx   #netfile:SocketClass    Service Class we will will handle
         jsr   sktmgr+socket:createreceive go create a receive socket
         ldx   #netserver:opensockets  list of active receive sockets
         jsr   netfile:insertsocketintouserchain insert Socket at SocketPointer into chain (X)
         ; I know it says ...UserChain... and that we are not using it
         ; for exactly that purpose here.  But the ...UserChain... IS intended
         ; for use with Remote SDOS system calls, and it is not in use
         ; for any other purpose at the SDOS Network Server's end, so
         ; I have no reseverations about using it for NETSERVER:
         ldd   SocketPointer           fetch pointer to created socket
         ldx   sdos+sdos:ioblockptr    store socket address in reply buffer
         stx   capability:locatorinfo+2,x create dummy capability
         okrts

netserver:getbuffercount ; service Get (Receive) Buffer Data Count request
         jsr   sdos+sdos:checkrdlen
         fdb   2                       need room for two bytes
         bsr   netserver:validatesocketcapabilitypresent
         jsr   sktmgr+socket:getdatacount fetch data count
         ldx   sdos+sdos:ioblockptr    store data count
         ldx   scblk:rdbuf,x           where to store data count
         std   0,x
         okrts
         page
netserver:readsocketdata ; service Read Socket Data request
         bsr   netserver:validatesocketcapabilitypresent
         ldx   sdos+sdos:ioblockptr    fetch data count
         ldd   scblk:rdlen,x           fetch size of reply buffer
         std   scblk:rplen,x           set length to size of reply buffer
         if    m6800!m6801
         ldx   scblk:rdbuf,x           where to place result
         stx   tempx                   set dummy (Y) register
         else  (m6809!m6811)
         ldy   scblk:rdbuf,x
         fin
         jsr   sktmgr+socket:readblock
         okrts

netserver:anysocketready ; determine if any socket has receive data
; or needs service (i.e., has died...)
         jsr   sdos+sdos:checkrdlen    make sure enough buffer space is available
         fdb   capability:size
         ; Loop thru open sockets, checking to see if any socket is ready.
         ldx   #netserver:opensockets-skt:userchainflink pointer to list of sockets being served
netserver:anysocketreadyloop ; determine if this socket is ready with data
         ldx   skt:userchainflink,x    find next socket attached to this server
         cpx   #netserver:opensockets  any more sockets to test ?
         beq   netserver:nosocketsready b/ no more sockets are ready
         stx   SocketPointer           set up pointer to socket properly
         jsr   sktmgr+socket:getdatacount go see if socket is ready with data
         bcs   netserver:socketisready b/ errored, socket must be ready or dead
         tstd                          any data present ?
         bne   netserver:socketisready b/ some data is present
         ldx   SocketPointer           this socket is not ready
         bra   netserver:closeloop     go see if next socket in list is ready

netserver:socketisready ; found a ready socket!
; Return capability for Socket to caller Server can use the Socket.
         ldd   SocketPointer           fetch pointer to created socket
         ldx   sdos+sdos:ioblockptr    store socket address in reply buffer
         stx   capability:locatorinfo+2,x create dummy capability
         okrts

netserver:nosocketsready ; signal that no socket is ready
         jsr   sdos+sdos:error
         fdb   err:devicenotready

          if   net:test
          page LINK device driver
; LINK device driver -- simple driver used to verify that link driver
; does reliable I/O, without requiring use/testing of socket mgr
; Defining a DCB for remote file access automatically makes this code
; accessible by use of the remote node "XYZ:" with no file name supplied

link:driverentrytable
; this vector is attached to IOCB:DRIVER when a null filename is used
; It is used by SDOS to make read, write, control, and status
; requests of the LINK driver
          fdb     link:open            open a device
          fdb     link:close           close a device
          fdb     sdos+sdos:illegaldeviceoperation read ascii -- doesn't help debug any
          fdb     sdos+sdos:illegaldeviceoperation write ascii -- doesn't help debug any
          fdb     link:readb           read binary -- only good for COPY, CRC
          fdb     link:writeb          write binary -- only good for COPY to
          fdb     link:create          create is almost the same as open
          fdb     sdos+sdos:illegaldeviceoperation rename is senseless
          fdb     sdos+sdos:illegaldeviceoperation delete is senseless
          fdb     link:control         perform a control operation
          fdb     link:status          return device status
          fdb     link:reset           once-only at system startup
          fdb     sdos+sdos:illegaldeviceoperation power-fail restart--dream on!

link:reset ; LINK: device Reset routine
? ; is it really as simple as this ?
         okrts
         page
link:create ; LINK: device Create routine
link:open ; LINK: device Open routine
         ldx   sdos+sdos:iocbpointer
         clr   iocb:eofflag,x          reset "end of file hit" flag
         ldx   sdos+sdos:ioblockptr    inspect file name given
         ldx   scblk:wrlen,x           LINK: or REMOTENETSERVER mode desired?
         lbne  netfile:openremotefile  b/ no
         ; AHA! Wants to open remote node as a DEVICE!
         ldx   DCBpointer              find DCB being opened
         lda   dcb:netid,x             fetch Network ID of destination node
         ldb   dcb:nodeid,x            fetch Node ID of destination node
         jsr   ld:FindMatchingNRB      or create one if necessary
         ; assert: FindMatchingNRB returns NRB with NRB:NODEID and ...NETID set
         ldx   sdos+sdos:iocbpointer   remember NRB in IOCB
         std   iocb:nrbpointer,x       that must be used to communicate with remote node
         ldd   #link:driverentrytable  cheat: make IOCB:DRIVER point...
                                       ; to driver vector for LINK: mode
         std   iocb:driver,x           (so we don't have to test a flag!)
         ldd   dcbpointer              set IOCB:DCBPOINTER correctly
         ; following code can go away after OPEN code in SDOS is revised
         std   iocb:dcb,x              remember DCB so SYSCALL:RENAME works correctly

         leas  2,s                     cheat: skip OPEN2 and PLANTDCBINIOCB code
         stx   iocb:nrbpointer,x       remember location of NRB
         okrts                         all done!
         page
link:close ; LINK: device Close routine
         bsr   link:commonsetup        go do common setup code
         jsr   ld:clnk                 force NRB to close connection
         jsr   ld:releasenrb           let NRB go so it can be reused again
         okrts                         signal successful completion

link:commonsetup ; come here to set up to process SYSCALL
; While LINK: code is executing, location NRBpointer contains a pointer
; to the NRB to use.  Since this location conflicts with DCBpointer,
; we save the DCBpointer so that we can restore it when we exit
         ldx   sdos+sdos:iocbpointer
;        ldd   DCBpointer              fetch DCBpointer
;        std   iocb:dcb,x              save DCBpointer so we can restore later
; (we need do nothing to save DCBpointer, as it is already stored in IOCB:DCB)
         ldx   iocb:nrbpointer,x       set NRBPOINTER
         stx   nrbpointer
         pulx                          fetch return address
         ldd   #link:commonexit        set to exit thru common code
         pshd
         jmp   0,x                     return to caller

link:commonexit ; come here to exit link driver SYSCALL routint
         bcc   link:commonexitcleanup  b/ no error, just exit
         jsr   sdos+sdos:errorsave     save the error code
         bsr   link:commonexitcleanup  undo damage to DCBpointer
         jmp   sdos+sdos:errored       and propogate the error code

link:commonexitcleanup ; come here to clean up when operation is complete
         ldx   sdos+sdos:iocbpointer   restore the DCBpointer
         ldd   iocb:dcb,x              by pulling it out of the DCB
         std   DCBpointer
         rts
         page
link:control ; LINK: device perform control functions
         bsr   link:commonsetup        go do common setup code
         ldx   sdos+sdos:ioblockptr
         ldab  scblk:params+1,x
         cmpb  #cc:dumpbuffers         dumpbuffers ?
         beq   link:okrts              b/ yes, just ignore
link:illegaldeviceoperation ; signal that device operation is not allowed
         jmp   sdos+sdos:illegaldeviceoperation b/ no, go complain

link:status ; LINK: device perform status functions and return immediately
         bsr   link:commonsetup        go do common setup code
         ldx   sdos+sdos:ioblockptr    get pointer to syscall block
         ldab  scblk:params+1,x
         cmpb  #sc:getbuffercount      desire amount of available data ?
         beq   link:statusgetbuffercount b/ yes, go handle
         cmpb  #sc:geteof              end of file check ?
         beq   link:statusgeteof       b/ yes, go handle
         cmpb  #sc:gettype             device type check ?
;        beq   link:statusgettype      b/ yes, go handle
         bne   link:illegaldeviceoperation b/ no, illegal device operation
link:statustype ; return device type
         jsr   sdos+sdos:checkrdlen    check reply buffer length
         fdb   1
         ldaa  #dvtyp:link             indicate special stream device
         ldx   scblk:rdbuf,x           where to put answer
         staa  0,x                     return EOF flag
link:okrts ; signal success and exit
         okrts

link:statusgeteof ; return EOF status
         jsr   sdos+sdos:checkrdlen    check reply buffer length
         fdb   1
         ldx   sdos+sdos:iocbpointer   fetch pointer to IOCB
         ldaa  iocb:eofflag,x          fetch flag
         ldx   sdos+sdos:ioblockptr
         ldx   scblk:rdbuf,x           where to put answer
         staa  0,x                     return EOF flag
         okrts

link:statusgetbuffercount ; return amount of data available for reading
? ; perhaps hook thru VT driver would be better deal?
         jsr   sdos+sdos:checkrdlen    check reply buffer length
         fdb   1
         jsr   ld:rcnt                 get received and ready data count
         ldx   sdos+sdos:ioblockptr
         ldx   scblk:rdbuf,x           where to put answer
         std   0,x                     return EOF flag
         okrts
         page
link:readb ; LINK: device Read Binary data
         bsr   link:commonsetup        go do common setup code
         ldx   sdos+sdos:ioblockptr    get pointer to reply buffer
         ldd   scblk:rdlen,x           get number of bytes to read
         std   scblk:rplen,x           set size of reply buffer
         if    m6800!m6801
         ldx   scblk:rdbuf,x           and where to place them
         stx   tempx                   set dummy (Y) register
         else  m6809!m6811
         ldy   scblk:rdbuf,x           where to put response
         fin
         jsr   ld:gblk                 fetch block of data desired
         ; doesn't return until desired amount of data has been read
         okrts

link:writeb ; LINK: device Write Binary data
         bsr   link:commonsetup        go do common setup code
         ldx   sdos+sdos:ioblockptr    get pointer to reply buffer
         ldd   scblk:wrlen,x           get number of bytes to write
         ldx   scblk:rdbuf,x           and where to write from
         jsr   ld:sblk                 fetch block of data desired
         ; doesn't return until desired amount of data has been written
         okrts

link:patch  ; vt driver patch space (heaven forbid it should get used...)
         rpt   50
         swi
         fin   net:test
         page  Logical Link Driver interface
; The following interface routines pass control to the appropriate
; logical link driver entry using NRB:LINKDRIVER as a guide.

; ld:reset entry point not used via NRB, so no entry supplied here.

ld:bcst ; called to broadcast a message over a particular physical link
         if    m6800!m6801!m6811
         stx   tempx                   save message address
         std   scratchpad+2            and message length
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         ldd   lde:bcst,x              address of logical link driver entry
         pshd                          save on stack
         ldd   scratchpad+2            restore message length
         ldx   tempx                   message address
         rts                           pass control to driver
         else  (m6809)
         ldu   NRBpointer              get pointer to NRB
         ldu   nrb:plcb,y              get pointer to physical link control block
         stu   PLCBpointer             and set up for use by link driver
         ldu   plcb:linkdriver,u       get pointer to logical link driver
         jmp   [lde:bcst,u]            address of logical link driver entry
         fin

ld:sbyt ; called to send a single byte to a node over a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:sbyt,x]            pass control to logical link driver entry

         page
ld:sblk ; called to send a block of data to a node over a particular physical link
         if    m6800!m6801!m6811
         stx   tempx                   save message address
         std   scratchpad+2            and message length
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         ldd   lde:sblk,x              address of logical link driver entry
         pshd                          save on stack
         ldd   scratchpad+2            restore message length
         ldx   tempx                   message address
         rts                           pass control to driver
         else  (m6809)
         ldu   NRBpointer              get pointer to NRB
         ldu   nrb:plcb,u              get pointer to physical link control block
         stu   PLCBpointer             and set up for use by link driver
         ldu   plcb:linkdriver,u       get pointer to logical link driver
         jmp   [lde:sblk,u]            address of logical link driver entry
         fin

ld:thnt ; called to signal "Transmit Hint" to a node over a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:thnt,x]            pass control to logical link driver entry

ld:rhnt ; called to signal that more data is needed soon from a node over a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:rhnt,x]            pass control to logical link driver entry

         page
ld:rqb ; called to to get received broadcast count from a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:rqb,x]             pass control to logical link driver entry

ld:rcnt ; called to to get received data count from a node on a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:rcnt,x]            pass control to logical link driver entry

ld:gbyt ; called to get another byte soon from a node on a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:gbyt,x]            pass control to logical link driver entry

ld:rbyt ; called to get another byte leisurely from a node on a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:rbyt,x]            pass control to logical link driver entry
         page
ld:gblk ; called to get a data block soon from a node on a particular physical link
         if    m6800!m6801!m6811
         stx   tempx                   save buffer address
         std   scratchpad+2            and buffer length
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         ldd   lde:gblk,x              address of logical link driver entry
         pshd                          save on stack
         ldd   scratchpad+2            restore buffer length
         ldx   tempx                   buffer address
         rts                           pass control to driver
         else  (m6809)
         ldu   NRBpointer              get pointer to NRB
         ldu   nrb:plcb,u              get pointer to physical link control block
         stu   PLCBpointer             and set up for use by link driver
         ldu   plcb:linkdriver,u       get pointer to logical link driver
         jmp   [lde:gblk,u]            pass control to logical link driver entry
         fin

ld:rblk ; called to get a data block leisurely from a node on a particular physical link
         if    m6800!m6801!m6811
         stx   tempx                   save buffer address
         std   scratchpad+2            and buffer length
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         ldd   lde:rblk,x              address of logical link driver entry
         pshd                          save on stack
         ldd   scratchpad+2            restore message length
         ldx   tempx                   message address
         rts                           pass control to driver
         else  (m6809)
         ldu   NRBpointer              get pointer to NRB
         ldu   nrb:plcb,u              get pointer to physical link control block
         stu   PLCBpointer             and set up for use by link driver
         ldu   plcb:linkdriver,u       get pointer to logical link driver
         jmp   [lde:rblk,u]            pass control to logical link driver entry
         fin
         page
ld:clnk ; called to close connection with node over a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:clnk,x]            address of logical link driver entry

ld:blnk ; called to break connection with node over a particular physical link
         ldx   NRBpointer              get pointer to NRB
         ldx   nrb:plcb,x              get pointer to physical link control block
         stx   PLCBpointer             and set up for use by link driver
         ldx   plcb:linkdriver,x       get pointer to logical link driver
         jmp   [lde:blnk,x]            address of logical link driver entry
         page
FindMatchingNRBBadNetID
         jsr   sdos+sdos:error
         #err:illegalnetid

FindMatchingNRBBadNodeID
         jsr   sdos+sdos:error
         #err:illegalnodeid

ld:FindMatchingNRB ; find an NRB whose NRB:NETID matches (A)...
; and whose NRB:NODEID matches (B)
; If no matching NRB found, then allocate a Free NRB if one is available
; This procedure need not be fast: it is only used once when establishing link
         ldx   sdos+sdos:configuration locate list of NRBs
         cmpa  cnfg:MaxValidNetID,x    a little paranoia: validate network ID
         bhi   FindMatchingNRBBadNetID b/ illegal network ID
         std   scratchpad+2            save NetID/NodeID desired
         ldx   sdos+sdos:configuration see if we are on same network
         ; assert: (A) refers to valid network number
         ldx   cnfg:netIDtoNITmap,x    find list of Net Info Table pointers
         tfr   a,b                     find Network Info Table pointer
         clra
         asld                          (form word offset)
         if    m6809
         ldx   d,x                     find Network Information Table for NetID
         else
         stx   tempx                   find Network Information Table for NetID
         addd  tempx
         std   tempx
         ldx   tempx
         ldx   0,x                     fetch Network Information Table address
         fin
         lda   nit:gatewaynetID,x      is remote node reachable only by gateway?
         beq   *                       b/ reachable only via gateway
? ; GATEWAYS NOT THOROUGHLY THOUGHT OUT YET
         ldb   scratchpad+3            restore NodeID
         cmpb  nit:maxnodeid,x         check for valid Node ID
         bhi   FindMatchingNRBBadNodeID b/ invalid Node ID
         if    NITNDE:size=4
         clra                          multiply size NetID by NITNDE
         asld
         asld
         else
         lda   #NITNDE:size
         mul
         fin
         if    m6809
         leax  d,x                     find NIT Node Date Entry for NodeID
         stx   tempx                   save pointer to NITNDE slot
         else
         stx   tempx                   find NIT Node Data Entry for NodeID
         addd  tempx
         std   tempx                   save pointer to NITNDE slot
         ldx   tempx
         fin
         if    m6800!m6801!m6811
         sei                           lock out Link Drivers from allocation
         else  (m6809)
         orcc  #%01010000
         fin
         ldd   NIT:NodeLookupBase+NITNDE:NRB,x is NRB already allocated for node?
         beqd  FindMatchingNRBAllocate b/ no NRB allocated, must allocate one
? ; what about case where we locate a dead but matching NRB?
; how about an NRB in transition from live to dead ?
         if    m6800!m6801!m6811
         cli
         else  (m6809)
         andcc #\%01010000
         fin
? ; what about case where NRB is dying but not dead just before CLI,
; and makes transition to dead and evaporates as result of interrupt after CLI?
         std   NRBpointer              remember NRB found
         okrts                         with NRB:NETID and NRB:NODEID set
         page
FindMatchingNRBAllocate ; find an NRB to use for Network (A), Node (B)
; This sets NRB:NODEID and NRB:NETID, returns (X) pointing to NRB
         ldx   FreeNRBs                allocate a free NRB
         beq   FindMatchingNRBAllocateCant b/ no NRBs to allocate!
         ldd   nrb:nextfreenrb,x       find next NRB in free list
         std   FreeNRBs                and make that the head of the list
         stx   NRBpointer              save NRB address
         ldd   scratchpad+2            fetch identity of node
         sta   nrb:netid,x             record identity of remote node
         stb   nrb:nodeid,x            including which node in which network
         ldx   tempx                   location of NITNDE slot
         ldd   NRBpointer              remember NRB in NITNDE slot
         std   NIT:NodeLookupBase+NITNDE:NRB,x
         ldd   NIT:NodeLookupBase+NITNDE:PLCB,x remember where PLCB for node is
         ldx   NRBpointer              by storing into NRB
         std   nrb:plcb,x
         ldx   nrb:plcb,x              call NRB initialize routine for link
         ldx   plcb:linkdriver,x
?        ldd   NRBpointer              so initialize routine knows how find NRB
         jsr   [lde:nint,x]            set up for use by link driver
         if    m6800!m6801!m6811
         cli
         else  (m6809)
         andcc #\%01010000
         fin
         ldx   NRBpointer
         okrts

FindMatchingNRBAllocateCant
         if    m6800!m6801!m6811
         cli
         else  (m6809)
         andcc #\%01010000
         fin
         jsr   sdos+sdos:error
         err:nofreeNRBs
         page
ld:ReleaseNRB ; place NRB at (X) back into pool of free NRBs
         if    m6800!m6801!m6811
         tpa                           save current machine state
         psha
         sei                           lock out Link Drivers from allocation
         else  (m6809)
         pshs  cc                      save current machine state
         orcc  #%01010000
         fin
         ldd   FreeNRBs                get pointer to 1st free NRB in list
         std   nrb:nextfreenrb,x       make this NRB point to rest of list
         stx   FreeNRBs
         if    m6800!m6801!m6811
         pula                          restore machine to entry state
         tap
         else  (m6809)
         puls  cc
         fin
         okrts
         page  Socket Manager stubs
; Contains Dummy Socket Manager (simulates Socket Manager until it is implemented).
; The kluge used in each case is described in the corresponding section.

socket:createtransmit ; call to create a transmit socket
; (A) = NetID, (B) = NodeID, (X) points to 16 byte SocketClass desired
; Finds a free socket, locates matching NRB for NetID/NodeID
; Returns pointer to socket in (X) and SocketPointer or else takes error return
; We simulate the desired result by augmenting NRBs with a few extra slots,
; and simply allocate an NRB instead!  Thus SocketPointer actually
; ends up pointing at an NRB, which makes the other Socket: routines
; trivial to implement.
         jsr   ld:FindMatchingNRB      locate a free Node Representative block
? ; what if NRB is already busy???
         ; This sets NRB:NODEID and ...NETID, returns (X) pointing to NRB
         stx   skt:nrbpointer,x        make NRB point to self via SKT:NRB
         clr   skt:userchainreferencecount,x in case socket placed into chain
         okrts

         if    0  ; turn this on when Socket Manager is real
         jsr   FindFreeNRB             find NRB matching NetID and NodeID
         ldx   NRBpointer              grab pointer to found NRB
         pshx                          and save it
         jsr   socket:findfreesocket   see if we can find a free socket
         bcs   socket:createerred1     b/ no free sockets! I give up...
         puld                          restore pointer to NRB
         std   skt:nrb,x               remember NRB to use for transmission
         okrts

socket:createerred1 ; found NRB, but can't get a free socket
         puld                          get NRB pointer
         pshx                          save error code
         tdx
         jsr   ReleaseNRB              let NRB go free if no more references
         bcs   *+2                     if error letting go of NRB, swallow it!
         pulx                          restore error code
         jmp   sdos+sdos:errorinx
         fin

socket:createreceive ; call to create a receive socket
; (A) = NetID, (B) = NodeID, (X) points to 16 byte SocketClass desired
; Finds a free socket, locates matching NRB for NetID/NodeID
; Returns pointer to socket in (X) and SocketPointer or else takes error return
         jmp   socket:createtransmit   fake it the same was as CREATETRANSMIT
         page
socket:destroy ; call to destroy socket specified by SocketPointer
         jsr   ld:releaseNRB           let go of the NRB
         okrts

         if    0
; The following is wishful thinking
socket:connectevent ; call to connect "DataArrived" event to a task
; SocketPointer points to Socket (assert: socket is not busy with another ConnectEvent)
         ? ; needs implementation
         okrts
         fin

socket:receivehint ; call to signal that data is required from socket
; SocketPointer points to Socket, no error exit possible
         okrts

socket:getdatacount ; call to determine number of bytes now readable from socket
; SocketPointer points to Socket
; exits with (D) holding Data Ready count
         jsr   ld:rcnt                 get byte count from NRB
         okrts

socket:readbyte ; call to read ONE byte from a socket
; SocketPointer points to Socket
; exits with byte read in (A), or takes error exit
         jsr   ld:rbyt                 read byte from NRB
         okrts

socket:readblock ; call to read data from socket
; On entry:
;    SocketPointer points to Socket
;    (D) holds exact count to read
;    (X) points to buffer to read into
; Exits with (X) advanced by (D) or exits with error if socket has died
         jsr   ld:rblk
         okrts
         page
socket:writebyte ; call to write ONE byte to a socket
; On entry, SocketPointer points to Socket, (A) holds byte to write
; exits normally or takes error exit
         jsr   ld:sbyt
         okrts

socket:writeblock ; call to write data to a socket
; On entry:
;    SocketPointer points to Socket
;    (D) holds number of bytes to write
;    (X) points to buffer to write from
; Exits with (X) advanced by (D) or takes error exit
         jsr   ld:sblk
         okrts

socket:xmithint ; signal that data written to this socket must be sent soon
; SocketPointer points to Socket, no error exit possible
         okrts
        page   End of driver: Oversize checking
netdevicedriverend equ *

netdevicedriversize equ netdevicedriverend-netdevicedriver

          if      netdevicedriversize>>netdevicedriverestimatedsize
          ? Estimated size of VT Driver is too small ?
          fin     netdevicedriversize>>netdevicedriverestimatedsize

          if      netdevicedriversize+128<<netdevicedriverestimatedsize
          ? Estimated size of VT Driver is too big ?
          fin     netdevicedriversize+128<<netdevicedriverestimatedsize

          if      netdevicedriverend>>VTdriver
          ?error  NETSERVER: Driver overlaps VT driver
          fin     netdevicedriverend>>VTdriver

          end     ; **** NETSERVER: Driver ****
