!  File  NMOUSE:2MEGP

! 2MHz Ether process for "new" Mouse, RWT Aug 1988,
! adapted from GDMR's version for "interim" Mouse, Dec 1987....

%option "-nons-low-nocheck-nodiag-noline"
%option "-nolist"
%include "mouse.inc"
%include "2meg.inc"
%begin

! This handler runs the station in "raw" mode.  It speaks the "standard"
! protocol (type 01/81), providing a port-like interface, and in addition
! allows the use of other protocol-types (for IP, ARP and the like).

{L{ %include "GDMR_H:Lights.Inc"
{L{ %constinteger lights type           = 16_80
{L{ %constinteger lights sending        = 16_40
{L{ %constinteger lights outbound       = 16_20
{L{ %constinteger lights old outbound   = 16_10
{L{ %constinteger lights old reTX       = 16_04 {16_08
{L{ %constinteger lights inbound        = 16_20
{L{ %constinteger lights old inbound    = 16_10
{L{ %constinteger lights clear          = 16_F8

%ownrecord(interrupt handler fm) interrupt handler = 0  {* First %own ! *} 

%constinteger ether packet size = 768-14;  ! Excludes header

%constinteger RX buffers            = 16;  ! 2^n ??
%constinteger reTX lives            = 6
%constinteger old protocol slots    = 48
%constinteger first station         = 16_10
%constinteger last station          = 16_7F
%constinteger last registered       = 16

%constinteger overall size = 14000
%constinteger  subpro size = 2000

! Ether station definitions

@16_7FFFC %readonly %volatile %byte status %or %writeonly %byte command
@16_7FFFD %byte data, *, control

%constinteger DTX = 16_30
%constinteger RDY = 16_10
%constinteger STX = 16_20
%constinteger ETX = 16_0B
%constinteger ACK = 16_C0
%constinteger ESA = 16_05
%constinteger SSA = 16_07
%constinteger RAW = 16_02

%constinteger int bit = 7, int mask = 1 << int bit
%constinteger rst bit = 6, rst mask = 1 << rst bit
%constinteger td  bit = 3, td  mask = 1 << td  bit
%constinteger rd  bit = 2, rd  mask = 1 << rd  bit
%constinteger rc  bit = 1, rc  mask = 1 << rc  bit
%constinteger rcd bit = 0, rcd mask = 1 << rcd bit

%constinteger interrupt level = 4
! SR values:
%constinteger interrupts off  = interrupt level<<8
%constinteger interrupts on   = 0
%constinteger supervisor mode = 16_2000

! Statistics

%ownrecord(ether stats fm)stats

%routine inc(%integername x)
  x = x+1
%end

%routine disaster(%string(255)which)
%integer i
   i = or to SR(16_700)
   newline; printstring("Unexpected event in 2MHz ";which;" handler:")
   newline; printstring(poa_message)
   write(poa_event, 1; poa_sub, 1);  space;  phex(poa_extra)
   printstring(" near PC ");  phex(poa_event PC)
   newline
   %for i = 0, 1, 15 %cycle
      phex(poa_event regs(i));  space
      newline %if i & 7 = 7
   %repeat
   semaphore wait(nil)
%end

! Buffer pool management

! We use a common format for request messages and ether buffers.
! However, we keep a pool of buffers to be filled by the receive
! interrupt handler.  The process which first fields incoming
! packets either puts them straight back into the receive pool
! (when it doesn't know what else to do with them), or it sends
! them on to the user (if there were pending read requests), in
! which case the read request buffer gets put back into the pool,
! or (when there are no read requests pending, but are likely to
! materialise "soon") the packet is saved in a pending queue. In
! this case the receive pool is replenished by a fresh buffer
! from the system pool.  Correspondingly, when read requests are
! satisfied by pending packets, the packets are sent to the user
! and the requests are returned to the system pool.

%recordformat ether header fm(%byte ds, dp, %integer dz,
                              %byte ss, sp, %integer sz,
                              %byte type, seq)

%constinteger old data type = 16_01
%constinteger old ACK  type = 16_81

%ownrecord(queue fm)RX buffer pool
%ownrecord(queue fm)inbound queue
%ownrecord(semaphore fm)%name inbound packet arrived == nil
%owninteger buffers remaining   { for information only }

%routine to RX pool(%record(ether buffer fm)%name buffer)
   move to SR(interrupts off)
      %if buffers remaining < stats_buffer low water -
      %then stats_buffer low water = buffers remaining
      buffers remaining = buffers remaining + 1
      enqueue(buffer_system part_header,RX buffer pool)
   move to SR(interrupts on)
%end

%record(ether buffer fm)%map from RX pool
! Careful: this one is called by the interrupt handler
%record(ether buffer fm)%name b
   b == dequeue(RX buffer pool)
   %unless b == nil %start
      b_offset = def offset
      buffers remaining = buffers remaining-1
   %finish
   %result == b
%end

%record(ether buffer fm)%map from system pool
%record(*)%name b
   b == acquire message buffer
   %result == b
%end

%routine to system pool(%record(ether buffer fm)%name buffer)
   return message buffer(buffer_system part)
%end

%routine setup RX buffer pool
%integer i
   setup queue(inbound queue)
   setup queue(RX buffer pool)
   buffers remaining = 0
   to RX pool(from system pool) %for i = 1, 1, RX buffers
   buffers remaining = RX buffers
   stats = 0
   stats_buffer low water = RX buffers
%end

%routine complete request(%record(ether buffer fm)%name r,%integer status)
  r_status = status
  send message(r_system part,r_system part_reply)
%end

%routine complete read request(%record(ether buffer fm)%name packet,request)
%record(ether header fm)%name header
   header == record(addr(packet_data(packet_offset-14)))
   packet_ra = header_ss;  packet_rp = header_sp
   packet_la = header_ds;  packet_lp = header_dp
   packet_type = header_type
   packet_code = request_code
   packet_tag = request_tag
   packet_context = request_context
   packet_system part_reply == request_system part_reply
   complete request(packet,ether success)
%end

%ownbytename TX buffer == nil
%owninteger TX bytes = 0
%ownrecord(semaphore fm)%name TX done == nil

%routine put data char(%registerinteger x)
  %cycle; %repeatuntil status&td mask#0
  data = x
%end

%routine put control char(%registerinteger x)
  %cycle; %repeatuntil status&td mask#0
  control = x
%end

%routine put control pair(%registerinteger x)
  put control char(x)
  x = 0; put data char(x)
%end

%routine skip data char
  %cycle; %repeatuntil status&rd mask#0
  d0 = data
%end

! Interrupt handler

%routine setup station handler
   %owninteger pending DTX = -1
   %ownrecord(ether buffer fm)%name RX buffer == nil
   %label interrupt

   interrupt handler_pc = addr(interrupt)
   add interrupt handler(interrupt level)
   move to SR(supervisor mode)
      command = rst mask;          ! Ints off, reset the station
      command = 0;                 ! Ints still off, un-reset
      put control char(RAW)
      put control char(ESA)
      command = rc mask ! rd mask; ! Enable receive interrupts
   move to SR(interrupts on)
   %cycle
      %returnunless stats_this station = 0
   %repeat

interrupt:

%register(a1)%bytename b
   *temp d0-d1/a0-a1

! The interupt handler proper.  We enable interrupts for received data
! and control characters (cleared by reading the appropriate register).
! The following may be received:
!     DTX: something has arrived from the wire
!     STX: the packet we asked for is coming up next
!     ETX: our packet has just finished (this won't cause an interrupt
!          as we're in a busy-wait loop at the time reading the packet)
!     RDY: the station is ready for our (TX) packet
!     ACK: can be ignored!
!     SSA: the station address is coming up next.
! Since we're in "raw" mode the "port" part of these will always be zero.
! Note, however, that it will be present and must be explicitly handled.
! If it's anything we don't recognise we log it and ignore it.  We have
! reasonable control over the receipt of everything except DTX, which
! is driven by packets arriving off the wire.  Since we're only geared
! up to handle one receive buffer at a time we tally the "extra" DTXs
! for future processing.
!
! Note: data is transferred in a busy-wait loop in the interrupt handler.
! This should result in less overhead than interrupt-per-character would.
! Any data interrupts are assumed to be caused by the station misbehaving:
! the character is read (to clear the condition) and thrown away.

! First off, read the status register to see if the interrupt was for us.
! If it wasn't we just return; if it was we fetch the control character
! (or read and throw away the data character).

   d0 = status
   return from interrupt %if d0&int mask=0  {station not interrupting =>}
   %if d0&rc mask=0 %start       {not control (i.e. data) char: discard}
     d1 = data
{}   rompstr("2MHz: spurious  "); romphex2(d1); rompsym(nl)
     return from interrupt
   %finish

! Next, read the control character and act upon it.

   d1 = control
   -> DTX arrived %if d1=dtx
   -> STX arrived %if d1=stx
   -> RDY arrived %if d1=rdy
   -> ACK arrived %if d1=ack
   -> SSA arrived %if d1=ssa
{} rompstr("2MHz: spurious ^"); romphex2(d1); rompsym(nl)
   inc(stats_unrecognised control)
   return from interrupt

DTX arrived:

! A packet has arrived in the station.
! Claim a buffer, ready to be filled with the packet when
! we ask for it by sending a RDY.
! If no buffer is available, tell the station to throw
! the packet away by sending an ACK.
! When DTX's pile up we simply count them - we only ask for
! one packet at a time.

   skip data char                { throw away port byte
   pending DTX = pending DTX+1   { Bump pending tally
   %if pending DTX#0 %start      { One pending already
      inc(stats_pended DTX)      { Tally it
      return from interrupt      { Must wait meantime
   %finish
   RX buffer == from RX pool
   %if RX buffer == nil %start   { No buffers left
      put control pair(ACK)
   %else
      put control pair(RDY)
   %finish
   return from interrupt

STX arrived:

! The STX has arrived.  Data frame will be arriving next.
! Read it in and tally it.  When the ETX arrives put the buffer on
! the arrived queue and signal the inbound handler's semaphore.

   skip data char
   b == rx buffer_data(rx buffer_offset)  { Start of data part
   D1 = -14                        { Header offset and initial byte count
   %cycle
      %exitif status&rcmask#0      { Control ->
      %continueif status&rdmask=0  { No data yet
      b[d1] = data                 { Read the character
      d1 = d1+1                    { Bump tally
   %repeat                         { Round for the next one
   RX buffer_bytes = d1            { Note packet size
   d0 = control                    { Must have been the ETX??
   enqueue(RX buffer_system part_header,inbound queue) { Link us in
   int signal semaphore(inbound packet arrived)
   pending DTX = pending DTX-1     { Note packet has been done
%option "-nott"
   %if pending DTX<0 %start        { No more packets waiting in station
      RX buffer == nil
   %else
      RX buffer == from RX pool
      %if RX buffer == nil %start
        inc(stats_packets dropped)
        put control pair(ACK)      { ask station to throw it away
      %else
        put control pair(RDY)      { ask station to let us have it
      %finish
   %finish
   return from interrupt

RDY arrived:

! This must have been in response to our DTX.  The TX buffer address
! and length will already have been set up, so all we have to do is
! hand it on to the station, bracketed by STX/ETX.  Since we're in raw
! mode the packet must be at least 14 bytes long, so we don't have to
! worry about dealing with awkward empty ones....

   skip data char
   put control pair(STX)
   d0 = TX bytes-1      { Number of bytes to send (adjusted for DBRA)
   b == TX buffer       { First byte to go
txloop:
      %cycle; %repeatuntil status&tdmask#0
      *move.b (B)+, data
   *dbra D0, txloop
   put control char(ETX)
   int signal semaphore(TX done)
   return from interrupt

ACK arrived:

! This is just in to keep the host/station protocol the same for raw
! mode as it is for cooked mode.  We just read the port byte and
! ignore it all....

   skip data char
   return from interrupt

SSA arrived:

! The next data character will be the station address

   %cycle; %repeatuntil status&rdmask#0
   stats_this station = data
   return from interrupt

   *nop {?!}
%end

! Inbound packet handler

%recordformat icb fm(%integer active,
                     %record(queue fm)pending packets, pending reads)

%ownrecord(icb fm)%array icbs(2 : last registered) = 0(*)
%ownrecord(semaphore fm)%name icb semaphore == nil

%routinespec old inbound handler(%record(ether buffer fm)%name buffer)

%routine inbound handler
%record(ether buffer fm)%name buffer
%record(ether header fm)%name header
%record(icb fm)%name icb
%record(ether buffer fm)%name rr
   %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start
      disaster("inbound")
   %finish
{{ printstring("Inbound handler: ");  write(free store, 0);  newline
   %cycle
      %cycle
{L{      lights and B(\ lights inbound)
         semaphore wait(inbound packet arrived)
{L{      lights or B(lights inbound)
         move to SR(interrupts off)
            buffer == dequeue(inbound queue)
         move to SR(interrupts on)
         %exitif buffer_bytes < 0                 {"runt"} -
             %or buffer_bytes > ether packet size {or outsize packet}
         stats_inbound bytes = stats_inbound bytes + buffer_bytes
         inc(stats_inbound packets)
         header == record(addr(buffer_data(buffer_offset-14)))
         %if 0 # header_ds # stats_this station %start
            ! Not for us
            to RX pool(buffer)
            inc(stats_dud destinations)
         %else %if header_type = old data type %c
               %or header_type = old ACK type
            ! Hand the buffer to the old protocol handler.  Let it
            ! free the buffer when it's finished
            old inbound handler(buffer)
         %else %if 2 <= header_type <= last registered
            semaphore wait(icb semaphore)
            icb == icbs(header_type)
            signal semaphore(icb semaphore) %andexitif icb_active = 0
            rr == dequeue(icb_pending reads)
            %if rr == nil %start
               ! Nothing waiting, so put it on the end of the list
               ! and replenish the pool with a new buffer
               enqueue(buffer_system part_header,icb_pending packets)
               to RX pool(from system pool)
            %else
               ! There's a read request already waiting, so complete it
               complete read request(buffer, rr)
               to RX pool(rr)
            %finish
            signal semaphore(icb semaphore)
         %finishelseexit
      %repeat
      to RX pool(buffer)
      inc(stats_dud types)
   %repeat
%end

! Outbound packet handler

%ownrecord(semaphore fm)%name outbound semaphore == nil

%routine outbound packet handler(%record(ether header fm)%name header,
                                 %record(ether buffer fm)%name buffer)
{L{lights or A(lights outbound)
   inc(stats_outbound packets)
   ! Grab semaphore, set up parameters for the transfer
   semaphore wait(outbound semaphore)
   %if buffer == nil %start  {Header-only transfer (as in ACK)}
      TX buffer == header_ds
      TX bytes = 14
   %else
      TX buffer == buffer_data(buffer_offset-14)
      move(14,header,TX buffer)
      TX bytes = buffer_bytes+14
      stats_outbound bytes = stats_outbound bytes+buffer_bytes
   %finish
   ! Kick the station
{L{lights or A(lights sending)
   move to SR(interrupts off ! supervisor mode)
      put control pair(DTX)
   move to SR(interrupts on)
   semaphore wait(TX done)
{L{lights and A(\lights sending)
   signal semaphore(outbound semaphore)
{L{lights and A(\ lights outbound)
%end

! Old-style protocol handler.

! Protocol control blocks are accessed in one of two ways: from the user side
! they are accessed directly by indexing into the table, while from the ether
! side they are accessed indirectly via a table of chained blocks, one slot 
! for each remote address.  "Port 0" is slightly special-cased to set up the
! destination station and port and to register the existence of a recipient.
! Mostly it is just the same as the VC ports.

%recordformat old protocol fm(%record(old protocol fm)%name next,
                              %record(ether header fm) header,
                              %integer reTX lives, next reTX, reTX interval,
                              %integer last seq, last address,
                              %record(queue fm) pending packets,
                                                pending reads, pending writes)

%ownrecord(old protocol fm)%array opcb(0 : old protocol slots) = 0(*)
%ownrecord(old protocol fm)%namearray opsl(first station - 1 : last station) == nil(*)

%ownrecord(semaphore fm)%name old protocol semaphore == nil

%owninteger timestamp = 0

%record(old protocol fm)%map find inbound opcb(%record(ether header fm)%name h)
%record(old protocol fm)%name p
   %result == nil %unless first station <= h_ss <= last station
   p == opsl(h_ss)
   %while p ## nil %cycle
      ! Do source/destination ports match?  Source address for each
      ! pcb is implied by the list it's on.
      %result == p %if p_header_dp = h_sp %and p_header_sp = h_dp
      p == p_next
   %repeat
   %result == nil
%end

%routine do old write(%record(old protocol fm)%name p, %integer setup)
%record(ether buffer fm)%name r
{L{lights or A(lights old outbound)
   %if setup # 0 %start
      p_reTX lives = reTX lives
      p_reTX interval = 1
      p_next reTX = timestamp + 3
   %finish
   r == p_pending writes_forward
   %if p == opcb(0) %start
      ! Port 0, copy in ds and dp
      p_header_ds = r_ra
      p_header_dp = r_rp
   %finish
   outbound packet handler(p_header,r)
   inc(stats_old outbound packets)
{L{lights and A(\ lights old outbound)
%end

%routine old inbound handler(%record(ether buffer fm)%name buffer)
%ownrecord(ether header fm) ACK = 0
%record(ether buffer fm)%name req
%record(ether header fm)%name header
%record(old protocol fm)%name p
   %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start
      disaster("old inbound")
   %finish
{L{lights or B(lights old inbound)
   header == record(addr(buffer_data(buffer_offset-14)))
   ->ignore broadcast %if header_ds = 0
   semaphore wait(old protocol semaphore)
   %if header_dp = 0 %then p == opsl(first station - 1) %c
                     %else p == find inbound opcb(header)
   %if p ## nil %start
      ! Connection is known.  Is this inbound data or an ACK for
      ! something we've sent?
      %if header_type = old data type %start
         ! Data packet has arrived
         inc(stats_old inbound packets)
         ! First ACK the packet.
         !ACK = 0
         %unless header_ss = stats_this station %start
            ACK_ss = stats_this station;  ! Filled in by station anyway...
            ACK_sp = header_dp
            ACK_ds = header_ss
            ACK_dp = header_sp
            ACK_type = old ACK type
            ACK_seq = header_seq
            outbound packet handler(ACK, nil)
         %finish
         ! Now if this packet's sequence number is new then we forward
         ! the data to the user or queue the buffer if there isn't
         ! a pending read.
         %if header_seq # p_last seq %c
               %or header_ss # p_last address %start
            p_last seq = header_seq
            p_last address = header_ss
            req == dequeue(p_pending reads)
            %if req == nil %start
               ! No user-request waiting, so put this on the pending queue
               ! and replenish the pool.
               enqueue(buffer_system part_header,p_pending packets)
               to RX pool(from system pool)
            %else
               ! There's a user request waiting, so complete it,
               ! and then grab a new buffer to return to the pool.
               complete read request(buffer, req)
               to RX pool(req)
            %finish
            signal semaphore(old protocol semaphore)
{L{         lights and B(\ lights old inbound)
            %return
         %finish
      %else %if header_type = old ACK type
         ! ACK packet has arrived
         %if header_seq = p_header_seq %c
               %and header_ss = p_header_ds %start
            ! It's an ACK for the last thing we sent.  First bump our
            ! send sequence number (avoiding 0, as cooked stations
            ! treat it as special).
            p_header_seq = (p_header_seq + 1) & 255
            p_header_seq = 1 %if p_header_seq = 0
            ! Now complete the first request on the pending write
            ! queue.
            req == dequeue(p_pending writes)
            %if req ## nil %start
               %if req_code = ether old write read %start
                  req_timeout = timestamp + req_timeout + 3 %if req_timeout>0
                  %if queue empty(p_pending packets) %start
                     enqueue(req_system part_header,p_pending reads)
                  %else
                     to system pool(buffer)
                     buffer == dequeue(p_pending packets)
                     complete read request(buffer,req)
                     buffer == req
                  %finish
               %else
                  complete request(req,ether success)
               %finish
               %unless queue empty(p_pending writes) %start;  ! Kick next
                  do old write(p, 1)
               %finish
            %finish {else spurious ack}
         %finish {else unexpected ack}
      %finish {else something bogus on queue}
   %else
      ! Else ignore it
      inc(stats_old inbound no takers)
   %finish
   signal semaphore(old protocol semaphore)
ignore broadcast:
   to RX pool(buffer)
{L{lights and B(\ lights old inbound)
%end
         
%routine old ether request(%record(ether buffer fm)%name m)
%record(old protocol fm)%name p, q
%record(ether buffer fm)%name pw
%record(ether buffer fm)%name RX buffer
%integer i, j

   %routine unlink pcb
      i = p_header_ds
      %if first station <= i <= last station %start
         %if opsl(i) == p %start
            ! We're the first on the chain
            opsl(i) == p_next
         %else
            ! We're not first in the chain, so follow it down...
            q == opsl(i)
            q == q_next %while q_next ## nil %and q_next ## p
            %if q == nil %start
               printstring("Slow PCB ");  write(m_context, 0)
               printstring(" not in chain for ");  phex2(i)
               newline
            %else
               q_next == p_next
            %finish
         %finish
      %elseunless i=0
         printstring("Dud ether address ");  phex2(i)
         printstring(" in slow PCB ");  write(m_context, 0)
         newline
      %finish
   %end

   %routine clobber pcb
      ! First off, get rid of any pending RX buffers.
      %cycle
         RX buffer == dequeue(p_pending packets)
         %exitif RX buffer == nil
         to system pool(RX buffer)
      %repeat
      ! Pending read and write requests next...  Complete these
      ! in the order in which they were queued, in case it matters
      ! to the caller.
      %cycle
         pw == dequeue(p_pending reads)
         %exitif pw == nil
         complete request(pw,ether operation aborted)
      %repeat
      %cycle
         pw == dequeue(p_pending writes)
         %exitif pw == nil
         complete request(pw,ether operation aborted)
      %repeat
   %end

   %if m_code = ether old define %start
      inc(stats_old defines)
      %if m_ra < first station -
      %or m_ra > last station  -
      %or m_ra = stats_this station %start
         complete request(m,ether dud destination)
      %else
         semaphore wait(old protocol semaphore)
         m_context = 0
         %for i = 1, 1, old protocol slots %cycle
            p == opcb(i)
            m_context = i %and %exit %if p_header_type = 0
         %repeat
         %if m_context = 0 %start   {all slots used up}
            complete request(m,ether no free slots)
         %else
            p = 0
            ! Build set of local port numbers in use
            j = 0
            q == opsl(m_ra)
            %while q ## nil %cycle
               j = j ! (1 << q_header_sp)
               q == q_next
            %repeat
            ! Find the lowest free local port
            m_lp = 0
            %for i = 1, 1, 31 %cycle
               %if j & (1 << i) = 0 %start
                  ! Found one free
                  p_header_sp = i
                  m_lp = i
                  %exit
               %finish
            %repeat
            %if m_lp = 0 %start     {no port number left}
               p = 0
               complete request(m,ether no free slots)
            %else
               p_next == opsl(m_ra); opsl(m_ra) == p
               p_header_ss = stats_this station
               p_header_ds = m_ra
               p_header_dp = m_rp
               p_header_type = old data type
               p_header_seq = (system elapsed time // 10)&255
               p_last seq = -1
               complete request(m,ether success)
            %finish
         %finish
         signal semaphore(old protocol semaphore)
      %finish
   %else %if m_code = ether old redefine
      inc(stats_old redefines)
      %unless 0 < m_context <= old protocol slots %start
         complete request(m,ether dud context)
      %else
         p == opcb(m_context)
         %if p_header_type = 0 %start
            complete request(m,ether dud context)
         %else
            ! Change the port number of our peer.
            ! Reset our send and receive sequence numbers.
            semaphore wait(old protocol semaphore)
            clobber pcb
            p_header_dp = m_rp
            p_header_seq = (system elapsed time // 10)&255 
            p_last seq = -1
            signal semaphore(old protocol semaphore)
            complete request(m,ether success)
         %finish
      %finish
   %else %if m_code = ether old undefine
      inc(stats_old undefines)
      %unless 0 < m_context <= old protocol slots %start
         complete request(m,ether dud context)
      %else
         p == opcb(m_context)
         %if p_header_type = 0 %start
            complete request(m,ether dud context)
         %else
            semaphore wait(old protocol semaphore)
            clobber pcb
            unlink pcb
            p = 0
            signal semaphore(old protocol semaphore)
            complete request(m,ether success)
         %finish
      %finish
   %else %if m_code = ether old read
      inc(stats_old reads)
      m_timeout = m_timeout + timestamp %if m_timeout > 0
      %unless 0 <= m_context <= old protocol slots %start
         complete request(m,ether dud context)
      %else
         p == opcb(m_context)
         %if p_header_type = 0 %start
            complete request(m,ether dud context)
         %else
            ! There are two possible cases here: either there is a
            ! packet waiting for someone to read it, or there are
            ! no packets waiting and instead a queue of (zero or more)
            ! read requests waiting.
            semaphore wait(old protocol semaphore)
            RX buffer == dequeue(p_pending packets)
            %if RX buffer == nil %start
               ! No pending packets.  Enqueue our read on the end of
               ! the wait queue.
               enqueue(m_system part_header,p_pending reads)
            %else
               ! A packet awaits a reader.  By implication, there
               ! will be no read requests pending.....
               complete read request(RX buffer, m)
               to system pool(m)
            %finish
            signal semaphore(old protocol semaphore)
            %return
         %finish
      %finish
   %else %if m_code = ether old write %or m_code = ether old write read
      inc(stats_old writes)
      %unless 0 <= m_context <= old protocol slots %start
         complete request(m,ether dud context)
      %else %unless 0 < m_bytes <= ether packet size -
      %and min offset <= m_offset -
      %and m_offset+m_bytes <= max offset+1
         complete request(m,ether operation undefined)
      %else
         p == opcb(m_context)
         %if p_header_type = 0 %start
            complete request(m,ether dud context)
         %else
            semaphore wait(old protocol semaphore)
            %if m_code = ether old write read %start
               %unless queue empty(p_pending writes) -
               %and queue empty(p_pending reads) %start
                  signal semaphore(old protocol semaphore)
                  complete request(m,ether dud context)
                  %return
               %finish
               %whilenot queue empty(p_pending packets) %cycle
                  inc(stats_pendings discarded)
                  to system pool(dequeue(p_pending packets))
               %repeat
            %finish
            j = 1; j = 0 %if queue empty(p_pending writes)
            enqueue(m_system part_header,p_pending writes)
            do old write(p, 1) %if j = 0   {Kick if this is the only one}
            signal semaphore(old protocol semaphore)
            ! Inbound process will complete it...
         %finish
      %finish
   %else %if m_code = ether old register0
      inc(stats_old defines)
      %if opsl(first station - 1) == nil %start
         ! Not yet defined
         semaphore wait(old protocol semaphore)
         p == opcb(0)
         p = 0
         p_header_ss = stats_this station
         p_header_type = old data type
         p_header_seq = (system elapsed time // 10)&255
         p_header_seq = 1 %if p_header_seq = 0;  ! Avoid 0!!
         p_last seq = -1
         ! ds & dp filled in on per-TX basis
         opsl(first station - 1) == p
         signal semaphore(old protocol semaphore)
         complete request(m,ether success)
      %else
         ! Already registered
         complete request(m,ether operation undefined)
      %finish
   %else %if m_code = ether old unregister0
      inc(stats_old undefines)
      semaphore wait(old protocol semaphore)
      %if opsl(first station - 1) == nil %start
         complete request(m,ether operation undefined)
      %else
         opsl(first station - 1) == nil
         p == opcb(0)
         clobber pcb
         p = 0
         complete request(m,ether success)
      %finish
      signal semaphore(old protocol semaphore)
   %else
      complete request(m,ether operation undefined)
   %finish
%end

%routine timeout handler
%ownrecord(semaphore fm)%name clock semaphore
%record(old protocol fm)%name p
%record(ether buffer fm)%name r, head
%integer i
   %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start
      disaster("timeout")
   %finish
   clock semaphore == create semaphore("",0)
{{ printstring("Timeout handler: ");  write(free store, 0);  newline
   submit regular timer request(clock semaphore,0,100)
   %cycle
      semaphore wait(clock semaphore)
      timestamp = timestamp+1
      semaphore wait(clock semaphore)
      timestamp = timestamp+1
{{printstring("Tick")
      ! Each time we are wakened we scan the pcb table looking for
      ! user timeouts and retransmit timeouts expiring.
      semaphore wait(old protocol semaphore)
      %for i = 0, 1, old protocol slots %cycle
         p == opcb(i)
         %if P_header_type # 0 %start
            ! In use.  First retransmit any pending write requests.
            %if (%not queue empty(p_pending writes)) -
                 %and p_next reTX <= timestamp %start
               ! First pending write's timeout has expired.
               p_reTX lives = p_reTX lives - 1
               %if p_reTX lives <= 0 %start
                  ! Not ACKed, return an error to user.
                  inc(stats_old ACK timeouts)
                  r == dequeue(p_pending writes);  ! Unlink it
                  complete request(r,ether write timeout)
                  ! Transmit the next on the queue, if any
                  ! for all the good it'll do...
                  do old write(p, 1) %unless queue empty(p_pending writes)
               %else
                  inc(stats_old retransmits)
                  p_reTX interval = p_reTX interval << 1
                  p_next reTX = timestamp + p_reTX interval
{L{          !    lights or A(lights old reTX)
                  do old write(p, 0)
{L{          !    lights and A(\ lights old reTX)
               %finish
            %finish
            ! Now check for expired read timeouts.
            ! Cycle through the whole list taking each request off
            ! the front and putting it back on the end unless we
            ! send it back to the user.
            head == nil;   !remember which request was at the front
            %cycle
               r == dequeue(p_pending reads)
               %exitif r == head  {back round to front (or empty)}
               head == r %if head == nil  {establish front}
               %if 0 < r_timeout <= timestamp %start
                  ! This one's specified timeout has expired.
                  ! Send back an error message
                  inc(stats_old user timeouts)
                  head == nil %if head == r  {if this was front, zap}
                  complete request(r,ether read timeout)
               %else
                  ! No timeout specified, or not expired yet.
                  enqueue(r_system part_header,p_pending reads)
               %finish
            %repeat
            %unless r == nil %start  {put front back on front}
               requeue(r_system part_header,p_pending reads)
            %finish
         %finish
      %repeat
      opcb(0)_last seq = -1 %if timestamp & 63 = 0;  ! Nasty frig for cooked stations
      signal semaphore(old protocol semaphore)
   %repeat
%end

! General protocol handler

%routine ether request(%record(ether buffer fm)%name m)
%record(ether buffer fm)%name RX
%record(ether buffer fm)%name r
%record(icb fm)%name icb
%record(ether header fm) header
   %unless 2 <= m_type <= last registered %start
      complete request(m,ether dud context)
   %finish
   icb == icbs(m_type)
   %if m_code = ether read %start
      %if icb_active = 0 %start
         complete request(m,ether dud context)
         %return
      %finish
      semaphore wait(icb semaphore)
      RX == dequeue(icb_pending packets)
      %if RX == nil %start
         ! Nothing waiting, we'll have to queue
         enqueue(m_system part_header,icb_pending reads)
      %else
         ! Take the first pending packet
         complete read request(RX, m)
         to system pool(m)
      %finish
      signal semaphore(icb semaphore)
   %else %if m_code = ether write
      %if icb_active = 0 %start
         complete request(m,ether dud context)
         %return
      %finish
      %unless 0 < m_bytes <= ether packet size -
      %and min offset <= m_offset -
      %and m_offset+m_bytes <= max offset+1 %start
         complete request(m,ether operation undefined)
         %return
      %finish
      header_ds = m_ra
      header_dp = 255
      header_dz = 0
      header_ss = stats_this station
      header_sp = 255
      header_sz = 0
      header_type = m_type
      header_seq = 255
      %if m_ra = stats_this station %start  {Loopback}
         move(14,header,m_data(m_offset-14))
         move to SR(interrupts off)
            r == from RX pool
         move to SR(interrupts on)
         complete request(m,ether success) %if r == nil
         r_code = m_code
         r_tag = m_tag
         r_context = m_context
         r_system part_reply == m_system part_reply
         move to SR(interrupts off)
            enqueue(m_system part_header,inbound queue)
         move to SR(interrupts on)
         signal semaphore(inbound packet arrived)
         complete request(r,ether success)
      %else
         outbound packet handler(header, m)
         complete request(m,ether success)
      %finish
   %else %if m_code = ether register
      %if icb_active # 0 %start
         complete request(m,ether dud context)
         %return
      %finish
      icb_active = 1
      complete request(m,ether success)
   %else %if m_code = ether unregister
      %if icb_active = 0 %start
         complete request(m,ether dud context)
         %return
      %finish
      semaphore wait(icb semaphore)
      ! Release all pending packets
      %cycle
         RX == dequeue(icb_pending packets)
         %exitif RX == nil
         to RX pool(RX)
      %repeat
      ! Return any pending read requests in the order they were queued
      %cycle
         r == dequeue(icb_pending reads)
         %exitif r == nil
         complete request(r,ether operation aborted)
      %repeat
      ! Mark inactive and release the icb
      icb_active = 0
      signal semaphore(icb semaphore)
      ! Finally send the request back with a success code
      complete request(m,ether success)
   %else
      complete request(m,ether operation undefined)
   %finish
%end

! Main program -- fields user requests

%record(process fm)%name p
%record(mailbox fm)%name mailbox == nil
%record(ether buffer fm)%name m
%label start inbound, start timeout
   %on 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 %start
      disaster("request")
   %finish
   mailbox                == create mailbox(ether mailbox name,
                             create semaphore("",0))
   become process(overall size)
   inbound packet arrived == create semaphore("",0)
   TX done                == create semaphore("",0)
   icb semaphore          == create semaphore("",1)
   old protocol semaphore == create semaphore("",1)
   outbound semaphore     == create semaphore("",1)
   setup RX buffer pool
   setup station handler
   p == create subprocess(subprosize, addr(start inbound),"Inbound")
   p == create subprocess(subprosize, addr(start timeout),"Timeout")
{{rompstr("2MHz ether address: "); romphex2(stats_this station); rompsym(nl)
{L{lights and B(\ lights clear)
{L{lights and A(\ lights clear)
   %cycle
      m == record(addr(receive message(mailbox)))
      %if m_code & ether old operation # 0 %start
{L{      lights or B(lights type)
         old ether request(m)
{L{      lights and B(\ lights type)
      %else %if m_code & ether operation # 0
{L{      lights or A(lights type)
         ether request(m)
{L{      lights and A(\ lights type)
      %else %if m_code = ether stats
         m_offset = def offset
         move(sizeof(stats),stats,m_data(def offset))
         complete request(m,ether success)
      %else %if m_code = ether station
         m_context = stats_this station
         complete request(m,ether success)
      %else
         complete request(m,ether operation undefined)
      %finish
   %repeat

start inbound:  inbound handler
start timeout:  timeout handler

%end %of %program
