! APM Front Bus Intel 82586 Ethernet driver
! RWT August 1989

! Byte sex

! The interface is wired for BYTE-FIDELITY, i.e. values which appear in
! consecutive byte locations as viewed by the 82586 do so as viewed by the
! CPU as well.  Since 82586 and 68010 have different byte sex this implies
! that values which are treated as 2-byte words have to have their bytes
! transposed in software, likewise for 4-byte words (which, incidentally,
! are invariably 24-bit addresses in which only 17 bits are relevant).
! This swapping is performed by the functions SWAP2 and SWAP4, respectively.
! Manipulation of pointers to control blocks using the 82586's method of
! using offsets with respect to a fixed base are performed using the
! functions OFFSET OF ITEM and ITEM AT OFFSET, which already contain the
! necessary calls to the SWAP functions.

! Memory layout

! The board contains 128 kbytes of dual ported memory, which appears at
! 040000:05FFFF in the CPU address space.  Address consistency between 82586
! and CPU is achieved by ignoring the 82586's seven high-order address bits.
! For example, the "System Configuration Pointer" record, which the 82586
! assumes is at FFFFF6, is actually at 05FFF6.  The CA (Channel Attention)
! signal to the 82586 is generated as a side effect of the CPU reading from
! any of the 16 highest-addressed bytes of the shared memory (i.e.at 05FFFX).

! NOTE:  BASE is in the middle of the memory, because of the 64k segmentation.
! All descriptors are kept in the upper half, data buffers are in the lower
! half and spill over into the upper.  It is arranged that the number of
! buffers reserved for receiving is large enough to occupy more than the lower
! half of memory, as a result all the transmit buffers are accessible using
! an offset from the same base as all the descriptors.  In fact, the transmit
! buffers are kept adjacent to their descriptors, which in turn are adjacent
! to their command blocks.  To avoid overheads involved in buffer chaining,
! all buffers are large enough to hold the maximum size frame (1500 bytes plus
! 14 in case the mode is selected in which addresses and type are in the
! buffers instead of the descriptors).  There is enough space for 64 receive and
! 20 transmit buffers including their respective descriptors.  Having transmit
! buffers adjacent to their command and buffer descriptors means that no extra
! space needs to be found for data for the configure and address setup commands.
! The maximum number of multicast addresses that can be squeezed into such an
! enlarged command block (MCMAX below) is about 255, more than adequate.

%option "-low-nons-nodiag-nocheck-nostack"
*temp d0-d4/a0-a3

%constinteger buffersize=1514, rbuffers=64, cbuffers=20
%constinteger mcmax=(buffersize+16)//6*6

%constinteger null=16_ffff  {The "offset" of NIL}

%recordformat scp fm  {System Configuration Pointer} -
(%byte sysbus, *,     {Data bus width: 1 for 8-bit bus, 0 for 16-bit bus}
 %integer *, iscp ad  {address of ISCP (see below)} )

%recordformat iscp fm {Intermediate System Control Pointer} -
(%byte busy, *,       {cleared by 82586 after reading SCB ad}
 %half scb offset,    {Address of
 %integer scb base    {SCB (see below)} )

%recordformat scb fm  {System Control Block} -
(%half status, command, cbl offset, rfa offset,
  crc errs, aln errs, rsc errs, ovr errs)

%recordformat buffer fm (%bytearray b(1:buffersize))

%recordformat buffer desc fm (%half count, next offset,
                              %integer buffer ad, %half size)

%recordformat frame desc fm (%half status, command, link offset, desc offset,
                             %bytearray dest, source(1:6), %half type)

%recordformat command fm (%half status, command, link offset,
 ({Dump}      %half dump buffer offset %or-
  {TDR}       %half tdr time %or-
  {MCA Setup} %half mc count, %bytearray mc data(1:mcmax) %or-
  {Configure} %byte co count, %bytearray co data(1:11) %or-
  {IA Setup}  %bytearray ia(1:6) %or-
  {Transmit}  %half desc offset, %bytearray dest(1:6), %half type,
              %half count, next offset, %integer buffer ad,
              %bytearray buffer(1:buffersize)))

! Only those incoming packets in which the TYPE field matches one of several
! pre-determined ranges of values are accepted, the rest are ignored.
! The interrupt handler determines whether to return an incoming frame buffer
! to the free list or to store it in one of the RANGE records below.
! The user-callable predicate ETHER FRAME RECEIVED extract a stored frame from
! these range records and makes it the current (accessible by user) frame.
! Prior to such extraction the previous "current" frame is returned to the
! free list.

%recordformat range fm (%record(range fm)%name next,
                        %half mintype,maxtype,head,tail)

@16_1070 {i.e. level 4} %integer interrupt vector

%constinteger base=16_50000 {in middle of memory space}

@(base-16_10000) -
%record(buffer fm)%array buffer(1:rbuffers),
%record(buffer desc fm)%array rbd(1:rbuffers),
%record(frame desc fm)%array rfd(1:rbuffers),
%record(command fm)%array com(1:cbuffers),
%integer old interrupt vector,intcount,sa4,sa5,
%half cbl head, cbl tail,
      pool head, pool tail,
      rfa head, rfa tail,
      rb head, rb tail

@(base+16_FFF6) %record (scp fm) scp
@(base+16_FFEE) %record (iscp fm) iscp
@(base+16_FFDE) %record (scb fm) scb

{Status/Command constants in SCB ** pre-swapped **}
%constinteger -
  smask        = 16_00F0,
  cx           = 16_0080,
  fr           = 16_0040,
  cnr          = 16_0020,
  rnr          = 16_0010,
  cmask        = 16_0007,
  cidle        = 16_0000,
  csuspended   = 16_0001,
  cready       = 16_0002,
  rmask        = 16_7000,
  ridle        = 16_0000,
  rsuspended   = 16_1000,
  rnoresources = 16_2000,
  rready       = 16_4000,
  cstart       = 16_0001,
  cresume      = 16_0002,
  csuspend     = 16_0003,
  cabort       = 16_0004,
  rstart       = 16_1000,
  rresume      = 16_2000,
  rsuspend     = 16_3000,
  rabort       = 16_4000,
  reset        = 16_8000

{Command/status bits in descriptors ** pre-swapped **}
%constinteger -
  done      = 16_80,
  busy      = 16_40,
  ok        = 16_20,
  aborted   = 16_10,
  el        = 16_80,
  suspend   = 16_40,
  interrupt = 16_20,
  eof       = 16_80  {in RBD COUNT field}

{Command codes ** pre-swapped **}
%constinteger -
  ia setup  = 16_0100,
  configure = 16_0200,
  multicast = 16_0300,
  transmit  = 16_0400,
  tdr       = 16_0500,
  dump      = 16_0600,
  diagnose  = 16_0700

%integerfn swap2(%register(d0)%integer a)
  *rol.w #8,a; *and.l #16_ffff,a
%end

%integerfn swap4(%register(d0)%integer a)
  *rol.w #8,a; *swap a; *rol.w #8,a
%end

%integerfn offset of item(%register(a0)%name x)
%register(d1)%integer a
  %result = null %if x==nil
  a = addr(x)-base
  %result = 1 %unless a&65535=a
  %result = swap2(a)
%end

%record(*)%map item at offset(%register(d0)%integer x)
  %result == nil %if x=null
  %result == record(swap2(x)+base)
%end

%routine issue command(%register(d0)%half x)

! Wait for SCB command word to clear, then copy X into it and waggle CA.

  d1 = scb_command %until d1=0
  scb_command = x
  x = scp_sysbus
%end  

%routine mask interrupts
  d0 = 16_0400; *trap #0
%end

%routine unmask interrupts
  d0 = 0; *trap #0
%end

! The 0 is personalised according to the APM's 2MHz ether address
%ownbytearray default local address(1:6) = 'F','r','e','d','-',0
%ownbytearray actual local address(1:6)

! SET MODES parameters
%externalbyte ether fifolim = 8, ether savbf = 0, ether atloc = 0,
              ether intloop = 0, ether extloop = 0, ether prom = 0

! Access to current transmit frame
%externalbytearrayname ether transmit dest(1:6)
%externalbytearrayname ether transmit data(1:buffersize)
%externalhalf ether transmit type
%externalhalf ether transmit size = 0

! Access to current receive frame
%externalbytearrayname ether receive dest(1:6)
%externalbytearrayname ether receive source(1:6)
%externalbytearrayname ether receive data(1:buffersize)
%externalhalf ether receive size = 0
%externalhalf ether receive type = 0
%externalhalf ether receive status = 0

! Statistics
%externalinteger commands executed = 0, commands failed = 0,
      frames received = 0, frames lost = 0, frames discarded = 0,
      extra buffers = 0, buffer low water = rbuffers, desc low water = rbuffers,
      tx restarts = 0, rx restarts = 0,
      crc errs = 0, aln errs = 0, rsc errs = 0, ovr errs = 0

%owninteger buffercount = rbuffers, desccount = rbuffers

%ownrecord(range fm)%name range list == nil
%ownrecord(command fm)%name current command == nil
%ownrecord(frame desc fm)%name current frame == nil

%record(range fm)%map find range(%register(d0)%half type)

! Locate the range record, if any, for which TYPE is in range.

%register(a0)%record(range fm)%name r == range list
  %cycle
    %result == r %if r==nil
    %result == r %if r_mintype<=type<=r_maxtype
    r == r_next
  %repeat
%end

%routine discard frame(%record(frame desc fm)%name f)

! (Called from either interrupt handler or user program)
! Return frame F (and any buffer descriptors hung off it) to the free list

%record(buffer desc fm)%name b,lb
%record(frame desc fm)%name lf
%half off
  %returnif f==nil
  b == item at offset(f_desc offset)
  %unless b==nil %start
    off = offset of item(b)
    %cycle
      b_size = swap2(buffersize)!el
      lb == item at offset(rb tail)
      %if lb==nil %start
        rb head = off
      %else
        lb_nextoffset = off
        lb_size = swap2(buffersize)
      %finish
      rb tail = off
      buffercount = buffercount+1
      %exitif b_count&eof#0
      b_count = 0
      off = b_nextoffset; b == item at offset(off)
    %repeat
    b_count = 0
    b_nextoffset = null
  %finish
  off = offset of item(f)
  f_desc offset = null
  f_command = el
  f_status = 0
  lf == item at offset(rfa tail)
  %if lf==nil %start
    rfa head = off
  %else
    lf_link offset = off
    lf_command = 0
  %finish
  rfa tail = off
  %if scb_status&rmask=rnoresources -
  %and rbhead#rbtail %and rfahead#rfatail %start
    f == item at offset(rfa head)
    f == item at offset(f_link offset) %while f##nil %and f_status#0
    %unless f==nil %start
      rx restarts = rx restarts+1
      f_desc offset = rbhead
      scb_rfaoffset = offset of item(f); issue command(rstart)
    %finish
  %finish  
  desccount = desccount+1
%end

%routine store received frames

! (Called only from interrupt handler) Deal with incoming frames, either
! store them in the appropriate range records or discard them.

%record(frame desc fm)%name f
%record(buffer desc fm)%name b
%record(range fm)%name r
%half off
  %cycle
    f == item at offset(rfa head)
    %exitif f==nil
    %exitif f_status&done=0
    frames received = frames received+1
    b == item at offset(f_desc offset)
    %unless b==nil %start
      %cycle
        rb head = b_next offset
        rb tail = null %andexitif rb head = null
        buffercount = buffercount-1
        %exitif b_count&eof#0
        extrabuffers = extrabuffers+1
        b == item at offset(rb head)
      %repeat
      bufferlowwater = buffercount %if buffercount<bufferlowwater
    %finish
    %if rfa head = rfa tail %start
      rfa head = null; rfa tail = null
    %else
      rfa head = f_link offset
    %finish
    f_link offset = null
    desccount = desccount-1
    desclowwater = desccount %if desccount<desclowwater
    %if ethersavbf=0 %and f_status&ok=0 %start
      frames lost = frames lost+1; discard frame(f); %continue
    %finish
    r == find range(f_type)
    %if r==nil %start
      frames discarded = frames discarded+1; discard frame(f); %continue
    %finish
    off = offset of item(f)
    f == item at offset(r_tail)
    %if f==nil %start
      r_head = off
    %else
      f_link offset = off
    %finish
    r_tail = off
  %repeat
%end

%routine recycle executed command descriptors

! (Called only from interrupt handler)

%record(command fm)%name c
%half off
  %cycle
    c == item at offset(cbl head)
    %exitif c==nil %or c_status&done=0
    commands failed = commands failed+1 %if c_status&ok=0
    commands executed = commands executed+1
    %if cbl head = cbl tail %start
      cbl head = null; cbl tail = null
    %else
      cbl head = c_link offset
    %finish
    c_link offset = null
    off = offset of item(c)
    c == item at offset(pool tail)
    %if c==nil %start
      pool head = off
    %else
      c_link offset = off
    %finish
    pool tail = off
  %repeat
  %if scb_status&cmask=cidle %and cblhead#null %start
    tx restarts = tx restarts+1
    scb_cbl offset = cblhead; issue command(cstart)
  %finish
%end
  
%routine find frame(%record(range fm)%name r)

! Discard any extant current frame.
! Then remove the first frame, if any, stored in the range record,
! and make it the current frame.

%record(buffer desc fm)%name b
  mask interrupts
    discard frame(currentframe)
  unmask interrupts
  %returnif r==nil
  currentframe == item at offset(r_head); %returnif currentframe==nil
  %if r_head=r_tail %start
    r_head = null; r_tail = null
  %else
    r_head = currentframe_linkoffset
  %finish
  currentframe_linkoffset = null
  b == item at offset(currentframe_descoffset)
  etherreceivedata == nil
  etherreceivesize = 0
  etherreceivestatus = swap2(currentframe_status)&16_2f80
  etherreceivetype = currentframe_type
  etherreceivedest == currentframe_dest
  etherreceivesource == currentframe_source
  %unless b==nil %start
    etherreceivedata == array(swap4(b_bufferad))
    etherreceivesize = swap2(b_count)&16_3fff
  %finish
%end

%routine acquire command buffer

! If there is not already a current command block, remove one from the free
! pool and make it the current command.  Then initialise the fields which
! are relevant for the transmit command, but not the command field itself.

%record(command fm)%name c
  %returnunless currentcommand == nil
  mask interrupts
    c == item at offset(poolhead)
    %unless c==nil %start
      %if poolhead=pooltail %start
        poolhead = null; pooltail = null
      %else
        poolhead = c_link offset
      %finish
      c_status = 0; c_command = 0; c_link offset = null
      c_desc offset = offset of item(c_count)
      c_buffer ad = swap4(addr(c_buffer))
      ether transmit data == c_buffer
      ether transmit dest == c_dest
      ether transmit type = 0
      ether transmit size = buffersize-14
      currentcommand == c
    %finish
  unmask interrupts
%end

%routine submit command

! Put the current command block onto the active list, starting up the
! command unit if necessary.  Then try to ensure that there is a new
! current command record ready for the next action.

%record(command fm)%name c, last
%half off
  c == current command; %returnif c==nil; current command == nil
  off = offset of item(c)
  c_command = c_command ! el ! interrupt
  c_link offset = null
  mask interrupts
    last == item at offset(cbl tail)
    %if last==nil %start
      cbl head = off
    %else
      last_link offset = off
      last_command = last_command&\el
    %finish
    cbl tail = off
  unmask interrupts
  acquire command buffer
  %returnif scb_status&cmask=cready
  mask interrupts
    scb_cbl offset = cbl head; issue command(cstart)
  unmask interrupts
%end

%externalroutine ether transmit frame

! The user is assumed to have filled in the data and dest arrays, as
! well as type and size variables for the current transmit buffer.
! Pass the buffer to the 82586 for transmission.

  %unless currentcommand==nil %start
!!  ethertransmittype = ethertransmitsize %if ethertransmittype=0
!!  ethertransmitsize = 46 {+14+4=64} %if ethertransmitsize<46
    currentcommand_type = ethertransmittype
    currentcommand_count = swap2(ethertransmitsize)!el
    currentcommand_nextoffset = null
    currentcommand_command = transmit
    submit command
  %finish
  acquire command buffer
%end

%externalroutine ether multicast address(%bytearrayname a(1:6))

! Add one to the list of MC addresses being formed in the current
! command buffer.  A==NIL causes the command to be executed.

%integer i
  acquire command buffer %if currentcommand==nil
  %returnif currentcommand==nil
  %if currentcommand_command=0 %start
    currentcommand_command = multicast
    currentcommand_mccount = 0
  %finish
  %if a==nil %start
    %unless currentcommand_mccount=0 %start
      submit command
    %finish
  %else
    %for i = 1,1,6 %cycle
      currentcommand_mccount = currentcommand_mccount+1
      %exitif currentcommand_mccount>=mcmax
      currentcommand_mcdata(currentcommand_mccount) = a(i)
    %repeat
  %finish
%end

%externalroutine ether local address(%bytearrayname a(1:6))

! Change hats, i.e. specify a new local DTE address.

  acquire command buffer
  currentcommand_command = ia setup
  currentcommand_ia = a
  submit command
  actual local address = a
%end

%externalroutine ether set modes

! Set non-standard modes as specified in the global parameter variables
! ETHERFIFOLIM, ETHERATLOC, ETHERSAVBF, ETHERINTLOOP, ETHEREXTLOOP, ETHERPROM.

  acquire command buffer
  currentcommand_command = configure
  ether fifolim = ether fifolim&15
  ether atloc = 8 %unless ether atloc=0
  ether savbf = 128 %unless ether savbf=0
  ether intloop = 64 %unless ether intloop=0
  ether extloop = 128 %unless ether extloop=0
  ether prom = 1 %unless ether prom=0
  currentcommand_cocount = 11
  currentcommand_codata(1) = ether fifolim
  currentcommand_codata(2) = ether savbf
  currentcommand_codata(3) = ether extloop ! ether intloop ! ether atloc ! 16_26
  currentcommand_codata(4) = 0
  currentcommand_codata(5) = 96
  currentcommand_codata(6) = 0
  currentcommand_codata(7) = 16_f2
  currentcommand_codata(8) = ether prom
  currentcommand_codata(9) = 0
  currentcommand_codata(10) = 64
  submit command
%end

%externalroutine ether startup(%bytearrayname local address(1:6))

! Initialise everything as from after hardware reset.

%label interrupt handler
%owninteger zero=0
%integer i
%record(command fm)%name c
%record(frame desc fm)%name f
%record(buffer desc fm)%name b
@(base-16_10000) %integerarray mem(1:32768)
  mem(i) = d7 %for i = 1,1,32768; ! Set dual ported memory to "unassigned"
  pool head = null; pool tail = null
  cbl head = null; cbl tail = null
  rfa head = null; rfa tail = null
  rb head = null; rb tail = null
  intcount = 0
  old interrupt vector = interrupt vector
  sa4 = a4; sa5 = a5
  interrupt vector = addr(interrupt handler)
  %for i = 1,1,rbuffers %cycle
    f == rfd(i)
    f_status = 0
    f_desc offset = null
    b == rbd(i)
    b_count = 0
    b_buffer ad = swap4(addr(buffer(i)))
    %if i=rbuffers %start
      f_link offset = null
      f_command = el
      b_next offset = null
      b_size = swap2(buffersize)!el
      rfa tail = offset of item(f)
      rb tail = offset of item(b)
    %else
      %if i=1 %start
        rfa head = offset of item(f)
        f_desc offset = offset of item(b)
      %finish
      f_link offset = offset of item(f[1])
      f_command = 0
      b_next offset = offset of item(b[1])
      b_size = swap2(buffersize)
    %finish
  %repeat
  %for i = 1,1,cbuffers %cycle
    c == com(i)
    %if i=cbuffers %start
      c_link offset = null
      pooltail = offset of item(c)
    %else
      %if i=1 %start
        poolhead = offset of item(c)
      %finish
      c_link offset = offset of item(c[1])
    %finish
  %repeat
  scb = 0
  iscp_scbbase = swap4(base)
  iscp_scboffset = offset of item(scb)
  iscp_busy = 1
  scp_iscpad = swap4(addr(iscp))
  scp_sysbus = zero
  issuecommand(0)
  default local address(6) = byte(16_3fa8) {2 MHz ether address}
  local address == default local address %if local address==nil
  actual local address = local address
  ether local address(local address)
  ether set modes
  mask interrupts
    scb_rfa offset = rfa head; issuecommand(rstart)
  unmask interrupts
  ether receive dest == actual local address
  %return

interrupt handler:

%register(d3)%integer status

  *movem.l d0-d4/a0-a5,-(sp)
  a4 = sa4; a5 = sa5
  status = scb_status&smask
  %if status#0 %start
    intcount = intcount+1
    issue command(status)
    recycle executed command descriptors
    store received frames
    crc errs = swap2(scb_crc errs)
    aln errs = swap2(scb_aln errs)
    rsc errs = swap2(scb_rsc errs)
    ovr errs = swap2(scb_ovr errs)
  %finish
  *movem.l (sp)+,d0-d4/a0-a5
  *move.l old interrupt vector,-(sp)
  *rts
%end

%externalroutine ether shutdown

! Similar to 82586 hardware reset.
! Also expunge range list and remove interrupt handler.

%record(range fm)%name r
  issuecommand(reset)
  %cycle; %repeatuntil scb_command=0
  interrupt vector = old interrupt vector
  %cycle  
    r == range list; %exitif r==nil
    range list == r_next; dispose(r)
  %repeat
%end

%externalroutine ether accept type range(%half mintype,maxtype)

! Add a type range to the list of types to be accepted by the
! receiver interrupt handler.

%record(rangefm)%name r
  r == new(r)
  r_mintype = mintype; r_maxtype = maxtype; r_head = null; r_tail = null
  r_next == range list; range list == r
%end

%externalroutine ether ignore type range(%half mintype,maxtype)

! Remove a range list previously added.  Both min and max must match.
! Then throw away any pending frames stored there.

%record(rangefm)%name prev==nil, r==range list
  %while r##nil %cycle
    %if r_mintype=mintype %and r_maxtype=maxtype %start
      %if prev==nil %then range list == r_next %else prev_next == r_next
      find frame(r) %until currentframe==nil
      dispose(r); %exit
    %finish
    prev == r; r == r_next
  %repeat
%end

%externalpredicate ether frame received(%half type)

! The first range record found for which TYPE is in range is examined.
! If no frame is stored there, return FALSE.  Otherwise remove the first
! frame, make it the current frame, and return TRUE.  Note that the type
! of that frame will not necessarily be the same as the type parameter
! specified here, the parameter is used only to identify a range.

  find frame(find range(type))
  %falseif currentframe==nil
  %true
%end
