! 2MHz ether process for Mouse. 
! AJS Mar 1987
!
%Option "-low-nocheck-nodiag-noline-nostack"
%Include ":l:Moose:mouse.inc"

%Constinteger min = 0, max = 532, min0 = 6, max0 = 538
%Constinteger dtx=16_30, stx=16_20, rdy=16_10, nak=16_50, etx=16_0B,
              opn=16_80, cls=16_90, ack=16_C0, esa=16_05, ssa=16_07, err=16_A0

%Constinteger ion  = 2_110
%Constinteger ioff = 2_0

%constinteger port free    = 1,
              port owned   = 2
%Constinteger port closed  = 1,
              port opening = 2,
              port open    = 3,
              port closing = 4
%constinteger await RDY    = 1,
              await ACK    = 2,
              await DTX    = 1,
              await ETX    = 2,
              await NAK    = 3,
              pending DTX  = 1

%constinteger  Undefined Error   =  0,
               Success           =  1,
               Bad Function Code =  2,
               Bad Port Number   =  3,
               Port not owned    =  4,
               No Free Port      =  5,
               Port already owned=  6,
               Packet was NAKed  =  7,
               Port was closed   =  8,
               Port was open     =  9,
               Bad Transmit length=10,
               Abort             = 11

%Constinteger Max Ctrl = 128  {Size of ether event pool from station}
%Constinteger Max Port = 31   {Last Port in station}

! Format of request packet from client process
%Recordformat reqfm (%Record(messagefm) msg,
                     %Integer tag,
                     (%Byte code %or %byte result),
                     %Byte port,
                     (%Byte rdte %or %Byte port0mode),
                     (%Byte rsap %or %Byte channel),
                     %bytename buffer,
                     (%Integer length %OrInteger maxbytes %orinteger resbytes))

! Format of ether event pool. 
%Recordformat Ctrlfm (%Integer bs, be, in, out, 
                      %Record(semaphorefm)%name sem,
                      %Bytearray port,type(0:maxctrl-1))

! Port record
! OPEN  = port closed, port opening, port open, port closing
! TMODE = 0, await RDY, await ACK
! RMODE = 0, pending DTX, await DTX, await ETX
! OWNED = port free, port owned
%Recordformat PortFm (%Byte open,tmode,rmode,lsap,owned,rdte,rsap,h,
                      %Record(queuefm) tpend,rpend,
                      %Record(reqfm)%name tcurr,rcurr,mgmt,
                      %Integer inbuff,maxbytes,resbytes)

! Ether Station registers
@16_7fffc %writeonly %volatile %Byteinteger Eths, Ethd, Ethz, Ethc

%Ownrecord(mailboxfm)            mbox
%Ownrecord(semaphorefm)          eevent
%Ownrecord(interrupthandlerfm)   ih
%Ownrecord(Portfm)%array         Port (0:MaxPort) = 0(*)
%Ownrecord(Ctrlfm)               Ctrl = 0
%Ownbyte                         LDTE=0

%systemstring(127)%fnspec itos(%integer v,p)
%routine phex1(%integer x)
  x = x&15; x = x+7 %if x>9; putsym(x+'0')
%end

%routine phex2(%integer x)
  phex1(x>>4); phex1(x)
%end

%Routine Wed (%Integer x)
   %Cycle; %Repeatuntil eths&8#0; ethd=x
%End

%Routine Wec (%Integer x)
   %Cycle; %Repeatuntil eths&8#0; ethc=x
%End

%Routine Setup Interrupts
   @0(a0)%Record(portfm) p
   %label int, ethctrl, ethi0, notstx, sendctrl, nw, ethnps, ethi1, done
   %label er3,er35,er4,er5

   ctrl_sem == eevent
   ctrl_bs  = addr(ctrl_port(0))
   ctrl_be  = addr(ctrl_port(MaxCtrl-1))+1
   ctrl_in  = ctrl_bs
   ctrl_out = ctrl_bs

   setup interrupt handler (ih, addr(int))
   Add Interrupt Handler (ih,4)

   eths = ion
   %Return
 
int:
   *move.b  eths,d0
   *bpl     done
   *and.w   #2,d0
   *bne     ethctrl

   *move.b  ethd,d0  {Must be Data => ignore}
   *bra     done

ethctrl:             {Control Msg}
   *move.b  ethc,d1
   *move.b  #16_F0,d0
   *and.b   d1,d0
   *beq     ethnps

! Port Specific Code
ethi0:
   *btst.b  #0,eths
   *beq     ethi0
   *moveq  #31,d1
   *and.b   ethd,d1    {D0 = control code, D1 = port}

   *cmp.b   #stx,d0
   *bne     notstx
   eths = ioff
{   putsym('[')
   *movem.l  d1-d2/a1-a3,-(sp)
   a0 = addr(port(d1))
   *move.l  a0,-(sp)
   d1 = p_maxbytes
   a0 = p_inbuff
   *lea     eths, a2
   *lea     ethd, a3
   *moveq   #-1,d0
er3:
   *subq    #1,d1
   *bmi     er4
er35:
   *move.b  (a2),d2
   *btst    #0,d2
   *beq     er35
   *move.b  (a2),d2
   *addq.l  #1,d0
   *btst    #1,d2
   *bne     er5
   *move.b  (a3),(a0)+
   *bra     er3
er4:                    ;! Now discard rest
   *move.b  (a2),d2
   *btst    #0,d2
   *beq     er4
   *move.b  (a2),d2
   *addq.l  #1,d0
   *btst    #1,d2
   *bne     er5
   *move.b  (a3),d2
   *bra     er4
er5:
   *move.l  (sp)+,a0
   *movem.l (sp)+,d1-d2/a1-a3
   p_resbytes = d0
   {putsym(']')
   eths = ion
   *move.b  #etx,d0
   *bra     sendctrl

notstx:
   *cmp.b   #rdy,d0
   *beq     sendctrl
   *cmp.b   #dtx,d0
   *beq     sendctrl
   *cmp.b   #ack,d0
   *beq     sendctrl
   *cmp.b   #nak,d0
   *beq     sendctrl
   *cmp.b   #err,d0
   *bne     done
sendctrl:
{   *move.l   d0,-(sp)
{   *move.l   d0,-(sp)
{   Putsym('%')
{   *move.l  (sp)+, d0
{   putlong(d0);putsym(' ');putlong(d1);putsym(nl)
{   *move.l  (sp)+, d0
   *move.l  ctrl_in, a0
   *move.b  d1,(a0)
   *move.b  d0,maxctrl(a0)
   *addq    #1,a0
   *cmp.l   ctrl_be,a0
   *bne     nw
   *move.l  ctrl_bs,a0
nw:
   *cmp.l   ctrl_out,a0
   *beq     done           {Reject it - no space}
   *move.l  a0,ctrl_in
   int signal semaphore (ctrl_sem)
   *bra     done

! Non Port Specific code  -  D1 = code
ethnps:
   *cmp.b   #ssa,d1
   *bne     done     {Ignore if not SSA}

ethi1:               {Read LDTE from station}
   *btst.b  #0,eths
   *beq     ethi1
   *moveq   #0,d1
   *move.b  ethd,d1
   *move.b  d1,ldte
   *bra     done
   
done:
   return from interrupt
   *move.l d0,d0    {Disuade Hamish}
%End

%Routine Reply to Client (%Record(reqfm)%name r)
   Send Message (r,r_msg_reply,nil)
%End

%Predicate Check Port Number (%Record(reqfm)%name r)
   %Trueif 0 <= r_port <= max port
   r_result = bad port number
   Reply to Client (r)
   %false
%End

%Predicate Check Port Owned (%Record(reqfm)%name r)
   %Trueif port(r_port)_owned = port owned
   r_result = port not owned
   Reply to Client (r)
   %False
%End

%Predicate Check Port Status (%Record(reqfm)%name r, %integer expect)
! fail and %False if expect # port status

   %Routine Do Fail (%Integer fail)
      r_result = fail
      Reply to Client (r)
   %End      

   %if port(r_port)_open = port open %start
      do fail (port was open) %andfalseif expect = port closed
   %Else            
      do fail (port was closed) %andfalseif expect = port open
   %Finish
   %True
%End

%Routine Bounce Queue (%record(queuefm)%name q)
   %Record(reqfm)%name r
   r == dequeue (q)
   %while r ## NIL %cycle
      r_result = abort
      Reply To Client (r)
      r == dequeue (q)
   %Repeat
%End

%Routine Claim Port (%Record(reqfm)%Name r, %Integer state)
   %Integer i

   %Routine Allocate (%Integer p)
      %If Port(p)_owned = port free %Start
         Port(p)_owned = port owned
         r_result = success
         r_port = p
      %Else
         r_result = port already owned
      %Finish
      Reply To Client (r)
   %End

   %if r_port = 255 %start
      %For i = 1, 1, Max Port %Cycle
         %If Port(i)_owned = port free %Start
            Allocate (i)
            %Return
         %Finish
      %Repeat
      r_result = no free port
      Reply to Client (r)
   %Else
      %returnunless check port number(r) 
      Allocate (r_port)
   %Finish
%End

%Routine Free Port (%Record(reqfm)%Name r, %Integer state)
   %Integer p

   %Returnunless check port number (r)
   %Returnunless check port owned  (r)
   %unless r_port = 0 %start
      %Returnunless check port status (r,port closed)
   %finish

   port(r_port)_Owned = port free
   r_result = success
   Reply to Client (r)
%End

%Routine Open Port (%Record(reqfm)%name r, %Integer state)
   %Record(portfm)%name p
   %switch sw(0:1)

   p == port(r_port)
   -> sw(state)

sw(0):   ;! User Request In
   %if r_port = 0 %start      ;! Null Operation
      r_result = success
      Reply to Client (r)
      %Return
   %Finish
   %Returnunless check port number (r)
   %Returnunless check port owned (r)
   %Returnunless check port status (r,port closed)

{ Putstring("Opening ");phex2(p_lsap);putstring(" to ");phex2(r_rdte)
{ putsym(' ');phex2(r_rsaP);putsym(nl)
   wec(opn); wed(p_lsap)
   p_mgmt == r
   p_open = port opening
   %Return

sw(await RDY):   ;! RDY received
   wec(stx)
   wed(p_lsap); wed(r_rdte); wed(r_rsap)
   wec(etx)
{   Putstring("Port ");phex2(p_lsap);putstring(" to ");phex2(r_rdte);
{   putsym(' ');phex2(r_rsap);putsym(nl)
   p_tcurr == NIL; p_rcurr == NIL; p_mgmt == NIL
   p_tmode = 0; p_rmode = 0
   setup queue (p_tpend)
   setup queue (p_rpend)
   p_open = port open
   p_rdte = r_rdte; p_rsap = r_rsap
   r_result = success
{   putstring("Port opened, ")
   Reply to Client (r)
{ putstring("Client informed");putsym(nl)
%End

%Routine Close Port (%Record(reqfm)%name r, %integer state)
   %Record(portfm)%Name p
   %switch sw(0:2)

   p == port(r_port)
   -> sw(state)

sw(0):   ;! User request in
{ Putstring("Close port ");phex2(p_lsap);putsym(nl)
   %returnunless check port number (r)
   %Returnunless check port status (r,port open)

   p_mgmt == r
   p_open = port closing
   Bounce queue (p_tpend); { Putstring("Bounce transmit, ")
   Bounce queue (p_rpend); { Putstring("Bounce receive");putsym(nl)
   %Returnif p_rmode = await ETX

sw(await ETX):  ;! the STX arrived or wasn't awaited

   %unless p_rcurr == NIL %start
      { Putstring("Bounce active receive, ")
      p_rcurr_result = abort
      Reply To Client (p_rcurr)
      p_rcurr == NIL
   %finish
   p_rmode = 0
   %Returnif p_tmode = await RDY

sw(await RDY):

   %unless p_tcurr == NIL %start
      { Putstring("Bounce active transmit");putsym(nl)
      p_tcurr_result = abort
      Reply To Client (p_tcurr)
      p_tcurr == NIL
   %finish
   p_tmode = 0 %unless p_tmode = await ACK
{   putstring("Close to station");putsym(nl)
   wec (cls); wed(p_lsap)
   r_result = success
{Putstring("Sending reply,")
   Reply to Client (r)
{PUtstring("send...");putsym(nl)
   %if p_lsap = 0 %start
      p_open = port open   ;! Reopenif 0
   %Else
      p_open = port closed
   %finish

%End

%predicate check transmit length (%Record(reqfm)%name r,
                                  %integer min,max)
   %trueif min <= r_length <= max
   r_result = bad transmit length
   Reply to client (r)
   %false
%End

%Routine Transmit Block (%Record(reqfm)%name r, %integer state)
   %Record(reqfm)%name nr
   %Record(portfm)%name p
   %switch sw(0:3)
   %label ew2,ew3

   p == port(r_port)
   -> sw(state)

sw(0):   ;! User request in
   %returnunless check port number (r)
   %Returnunless check port status (r,port open)
!   %if p_lsap = 0 %start
!      %returnunless check transmit length (r,min0,max0)
!   %Else
!      %Returnunless check transmit length (r,min,max)
!   %Finish
      
   %if p_tcurr ## NIL %start
      enqueue (r,p_tpend)
      %Return
   %Finish
   p_tcurr == r
{Putstring("Send DTX")
   wec(DTX); wed (p_lsap)
   p_tmode = await RDY
   %Return

sw(await RDY):  ;! Get here when RDY arrives
{Putstring("Send STX")
   wec (STX); wed (p_lsap)

   -> ew3 %if r_length <= 0

   d1 = r_length
   a0 = addr(r_buffer)
ew2:
   *btst    #3,eths
   *beq     ew2
   *move.b  (a0)+, ethd
   *subq    #1,d1
   *bgt     ew2
ew3:
   wec(etx)
   p_tmode = await ACK
   %Return

sw(await ACK): ;! Get here if ACK arrives
   r_result = success
   -> next

sw(await NAK): ;! Come here if NAK arrived
   r_result = packet was naked
   -> next

next:
   Reply to Client (r)
   p_tcurr == NIL; p_tmode = 0
   nr == dequeue(p_tpend)
   transmit block (nr,0) %unless nr == NIL
%End

%Routine Receive Block (%Record(reqfm)%Name r, %integer state)
   %Record(reqfm)%name nr
   %Record(portfm)%name p
   %switch sw(0:2)

   p == port(r_port)
   -> sw(state)

sw(0): ;! User request in
   %returnunless check port number (r)
   %Returnunless check port status (r,port open)
   
   %if p_rcurr ## NIL %Start
      enqueue (r,p_rpend)
      %Return
   %Finish
   p_rcurr == r
   %if p_rmode # pending DTX %start
      p_rmode = await DTX
      %Return
   %Finish

sw(await DTX):   ;! DTX has arrived
   {Putstring("Receive has DTX")
   p_inbuff = addr(r_buffer)
   p_maxbytes = r_maxbytes
   p_rmode = await ETX
   wec (rdy); wed(p_lsap)
   %Return

sw(await ETX):   ;! STX (really ETX) has arrived
   r_resbytes = p_resbytes
   p_rmode = 0
   p_rcurr == NIL
   r_result = success
   Reply to Client (r)
   nr == dequeue (p_rpend)
   receive block (nr,0) %unless nr == NIL
%End

%Routine Station Address (%Record(reqfm)%name r, %Integer state)
   r_result = success
   r_port = ldte
   Reply to Client (r)
%End

      
%Routine Handle Ether Event (%Integer eport,etype)
   %Switch sw(0:255)
   %Record(portfm)%Name p
   
   %Routine Ether Fail (%String(255) s)
      Putsym(7);putsym(32)
      Putstring(s);putsym(32)
      putlong(eport<<16+etype)
      putsym(nl)
   %End

   p == port(eport)
   -> sw(etype)

sw(rdy):
   {putstring("<rdy>")
   %if p_tmode = await RDY %start
      %if p_open = port closing %start
         Close Port (p_mgmt, await RDY)
      %Else
         Transmit Block (p_tcurr,await RDY)
      %Finish
   %elseif p_open = port opening
      Open Port (p_mgmt,await RDY)
   %Else
      Ether Fail ("Unexpected RDY")
   %Finish
   %Return
      
sw(etx):
   {putstring("<etx>")
   %if p_open = port closing %start
      close port (p_mgmt,await ETX)
   %elseif p_rmode = await ETX
      Receive Block (p_rcurr,await ETX)
   %Else
      Ether Fail ("Unexpected ETX")
   %Finish
   %Return

sw(dtx):
   {putstring("<dtx>")
   %if p_rmode = await DTX %start
      Receive Block (p_rcurr,await DTX)
   %Elseif p_open = port open %and p_rmode = 0
      p_rmode = pending DTX
   %Elseif p_open = port closing
      wec (ack); wed(eport)
   %Else
      Ether Fail ("Unexpected DTX (".itos(p_rmode,0).")")
   %Finish
   %Return

sw(nak):
   {putstring("<nak>")
   %if p_tmode = await ACK %start
      %if p_open = port closed %start
         p_tmode = 0
      %Else
         Transmit Block (p_tcurr,await NAK)
      %Finish
   %Else
      Ether Fail ("Unexpected NAK")
   %Finish
   %Return

sw(ack):
   {putstring("<ack>")
   %if p_tmode = await ACK %start
      %if p_open = port closed %start
         p_tmode = 0
      %Else
         Transmit Block (p_tcurr,await ACK)
      %Finish
   %Else
      Ether Fail ("Unexpected ACK")
   %Finish
   %Return

sw(*): 
   Ether Fail ("Unexpected Ether Event")
%End


%Routine Handle Request (%Record(reqfm)%Name r, %Integer state)
   %Constinteger last = 7
   %Switch sw(1:7)

   -> bum %unless 1 <= r_code <= Last
   -> sw(r_code)
   
sw(1):  Claim Port (r,state)         ; %Return
sw(2):  Free Port (r,state)          ; %Return
sw(3):  Open Port (r,state)          ; %Return
sw(4):  Close Port (r,state)         ; %Return
sw(5):  Transmit Block (r,state)     ; %Return
sw(6):  Receive Block (r,state)      ; %Return
sw(7):  Station Address (r,state)    ; %Return
bum:
   r_result = bad function code
   reply to client (r)
%End

%Routine Initialise
   %Integer i
   %record(dictfm)%name m
   %record(portfm)%name cp

   %For i = 0, 1, Max Port %cycle
      cp == Port (i)
      cp_open = port closed; cp_lsap = i
      cp_owned = port free
      setup queue (cp_tpend)
      setup queue (cp_rpend)
      cp_tcurr == NIL; cp_tmode = 0
      cp_rcurr == NIL; cp_rmode = 0
      cp_mgmt == NIL
   %Repeat      

   port(0)_open = port open   ;! Port 0 always open

   Put String ("Ether (2MHz) process. Version of 18th March 1987. Station = ")

   setup semaphore (eevent)
   setup mailbox (mbox, eevent)

   Setup Interrupts

   wec(16_0F)     ;! Reset Station
   wec(esa)       ;! Enquire LDTE
   %Cycle; %Repeatuntil ldte # 0
   phex2 (ldte) ; putsym(10)

   m == poa_logdict
   m == m_alt %while m_alt ## nil
   i = Make Entry ("ETHER_REQ",m)
   %if i = 0 %start
      putstring("Ether process failed to create mailbox");putsym(10)
      %stop
   %Finish
   integer(i) = addr(mbox)
%End

%Integerfn Remove Control (%Integername port,type)
   %Integer t,p

   t = ctrl_out
   %if ctrl_in # t %start
      port = byteinteger(t)
      type = byteinteger(t+maxctrl)
{      putstring("Port = ");putlong(port);putsym(' ')
{      putstring("type = ");putlong(type);putsym(nl)
      t = t + 1
      t = ctrl_bs %if t=ctrl_be
      ctrl_out = t
   %Else
      %Result = -1
   %Finish
   %Result = 0
%End

%Begin
   %integer p,port,etype
   %record(reqfm)%name req

   %on 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 %start
      Putstring("Ether process fatal - ")
      putstring(event_message);putsym(10)
      %Stop
   %Finish

   Initialise
   %Cycle
      Semaphore Wait (eEvent)
      req == Dequeue (mbox_queue)
      %If req ## NIL %start
         Handle Request (Req,0)
      %Else
         p = Remove Control (port,etype)
         %If p = -1 %start
            Putstring ("* Ether : Superfluous signal");putsym(10)
            %Continue
         %Finish
         Handle Ether Event (port,etype)
      %Finish
   %Repeat
%End
