!  File  NMOUSE:SUPER

! Supervisor

%option "-low-nons-nodiag-nocheck-nostack-nowarn"
%include "mouse.inc"

%record(*)%map star(%record(*)%name r); %result == r; %end

@16_ff2003 %byte level 1 interrupt register

! Queue handling

%routine lock(%registerrecord(queue fm)%name x)
! Attempt to lock queue X.
! Report error if we don't succeed after 99 attempts,
! but then carry on as if we had succeeded.
! This routine must only be called from within
! the supervisor, and interrupts must be off.
! D0 is the only register which is changed.
%label done
%register(d0)%integer i
%option "-half"
  *temp 0
  i = 99
  %cycle
    *tas x_lock; *bpl done  {success ->
    i = i//1                {slight delay
    i = i-1                 {keep trying
  %repeatuntil i=0
  *temp d0/a0
  *move.l a0,-(sp)
  rompstr("*Queue lock timeout ")
  *move.l (sp)+,a0
  rompsym(x_tag>>8); rompsym(x_tag)
  rompsym(' '); romphex(addr(x)); rompsym(nl)
  {Despite error pretend all is well}
done:
%end

%routine unlock(%registerrecord(queue fm)%name x)
! Unlock queue X, report error if it wasn't locked.
! No registers changed.
%label ok
  *bclr #7,x_lock; *bne ok
  *temp d0/a0
  *move.l a0,-(sp)
  rompstr("*Queue unlock error ")
  *move.l (sp)+,a0
  rompsym(x_tag>>8); rompsym(x_tag)
  rompsym(' '); romphex(addr(x)); rompsym(nl)
ok:
%end

%systemroutine setup queue (%record(queue fm)%name q)
! Make Q canonically empty.
  q_forward == q; q_backward == q; q_queue == q; q_lock = 0
%end

%systempredicate queue empty (%record(queue fm)%name q)
! Return TRUE/FALSE if Q is EMPTY/NOT.
! We don't bother to check for consistency.
! For example, Q_FORWARD==Q and Q_BACKWARD==Q should always
! be either both true or both false.
{}%if q_forward==nil %start
{}  setup queue(q); %true
{}%finish
  %trueif q_forward==q; %false
%end

%systemroutine enqueue(%record(queue fm)%name c,q)
! Add cell C to end of queue Q
! *No registers changed*
%record(queue fm)%name tail
  *temp a0/a1
  tail == q_backward
{}tail == q %if tail==nil
  c_forward == q; c_backward == tail; c_queue == q
  tail_forward == c; q_backward == c
  *move.l c,a0; *move.l q,a1
%end

%routine lenqueue(%registerrecord(queue fm)%name c,q)
! Enqueue with lock/unlock.
! Registers changed: D0
  *exg c,q; lock(c); *exg c,q
  enqueue(c,q)
  *exg c,q; unlock(c); *exg c,q
%end

%systemrecord(*)%map dequeue(%record(queue fm)%name q)
! Remove a cell from front of queue Q, return NIL if Q empty.
! Registers changed: A1 (and of course A0).
%record(queue fm)%name cell,head
  *temp a0/a1
  cell == q_forward
{}%result == nil %if cell==nil
  %result == nil %if cell==q
  head == cell_forward
  q_forward == head; head_backward == q
  cell_forward == cell; cell_backward == cell; cell_queue == nil
  %result == cell
%end

%record(*)%map ldequeue(%record(queue fm)%name q)
! Dequeue with lock/unlock.
! Registers changed: D0/A0/A1
%record(queue fm)%name c
  *temp a0
  lock(q)
    c == dequeue(q)
  unlock(q)
  %result == c
%end

%systemroutine requeue(%record(queue fm)%name c,q)
! Add to front (as if "putting back" a cell just dequeued).
! Registers changed: A0/A1.
%record(queue fm)%name head
  *temp a0
  head == q_forward
  head_backward == c; q_forward == c
  c_backward == q; c_forward == head; c_queue == q
%end

%systemrecord(*)%map unqueue(%record(queue fm)%name q)
! Remove from end (as if "taking back" a cell just enqueued).
! Registers changed: A0 only.
%record(queue fm)%name cell,tail
  *temp a0
  cell == q_backward; %result == nil %if cell==q
  tail == cell_backward; q_backward == tail
  tail_forward == q
  cell_forward == cell; cell_backward == cell; cell_queue == nil
  %result == cell
%end

%systemroutine exqueue(%record(queue fm)%name c)
! Remove C from wherever in whichever queue it is.
%record(queue fm)%name l,r
  %unless c_queue==nil %start
    l == c_backward; r == c_forward
    l_forward == r; r_backward == l
    c_forward == c; c_backward == c; c_queue == nil
  %finish
%end

%systemroutine inqueue(%record(queue fm)%name c,l,r)
! Insert C into the queue in which L and R are.
! L and R should be adjacent, C is put between them.
  %if l_forward==r %and r_backward==l %and l_queue==r_queue %start
    c_queue == l_queue
    c_forward == r; c_backward == l
    l_forward == c; r_backward == c
  %else
    c_queue == nil
  %finish
%end

%routine suspend(%register(a1)%record(queue fm)%name q)
! Suspend the current process by queueing it on Q.
! If Q==NIL, leave process adrift in space.
! Alert the local scheduler, then enter an idle loop.
! This routine must only be called from within
! the supervisor, and interrupts must be off.
  lenqueue(current process_header,q) %unless q==nil
  current process == nil
  a7 = 16_3f00-6*64   {switch to idle interrupt stack
  level 1 interrupt register = 255
  %cycle
    *stop #16_2000
  %repeat
%end

%systemroutine int signal semaphore (%record(semaphore fm)%name s)
! Callable directly by interrupt handlers, which do not
! have TRAPs at their disposal.  Also called within supervisor.
! Special case: S==NIL means voluntary pre-emption.
! Registers changed: D0/D1/A0/A1.
%record(process fm)%name p
%record(run queue fm)%name q
  *mfsr d1
  *otsr #16_700
  *temp d0/a0-a1
  %if s==nil %start
    suspend(currentprocess_runqueue_header) %unless currentprocess==nil
    %return
  %finish
  %unless s_header_tag='SE' %start
    rompstr("*Signal: Invalid Semaphore "); rompsym(s_header_tag>>8)
    rompsym(s_header_tag); rompsym(' '); romphex(addr(s)); rompsym(nl)
    %return
  %finish
  lock(s_header)
  s_counter = s_counter+1
  %if s_counter>0 %start  {no process transfer needed}
    unlock(s_header)
    *mtsr d1
    %return
  %finish
! Now transfer the process at the head of the semaphore
! wait queue to the tail of that process's run queue.
  p == dequeue(s_header)
  q == p_runqueue
  lock(q_header)
  unlock(s_header)
  enqueue(p_header,q_header)
  %if q_header_forward==p_header %start  {interrupt may be needed}
    unlock(q_header)
    %if q_interrupt register ## level1 interrupt register -
    %or current process==nil -
    %or addr(q)>addr(currentprocess_runqueue) -
    %then q_interrupt register = 255
  %finishelse unlock(q_header)
  *mtsr d1
%end

! Supervisor interface

%systemroutine move to sr(%registerinteger x)
! Loophole: Move X into SR
  *move.l (sp)+,a0
  *trap #0
  *jmp (a0)
%end

%systemintegerfn or to sr(%registerinteger x)
! Loophole: Or X into SR, result: previous SR
  *move.l (sp)+,a0
  *trap #1
  *jmp (a0)
%end

%systemintegerfn cpu time
  *sub.l a0,a0
  *trap #2
%end

%systemintegerfn elapsed time
  *sub.l a0,a0
  *trap #2
  %result = system elapsed time-d1
%end

%systemintegerfn time slice size
  *sub.l a0,a0
  *trap #2
  %result = d2
%end

%systemintegerfn priority
  *sub.l a0,a0
  *trap #2
  %result = d3
%end

%systemintegerfn privilege
  *sub.l a0,a0
  *trap #2
  %result  = d4
%end

%systemroutine set time slice size(%integer ticks)
  *sub.l a0,a0
  *trap #2
  d2 = ticks
  *trap #3
%end

%systemroutine set priority(%integer prio)
  *sub.l a0,a0
  *trap #2
  d3 = prio
  *trap #3
%end

%systemroutine set privilege(%integer onoff)
  *sub.l a0,a0
  *trap #2
  d4 = 0
  d4 = 1 %unless onoff=0
  *trap #3
%end

%systemroutine signal semaphore(%record(semaphore fm)%name s)
  *trap #6
%end

%systemroutine semaphore wait(%record(semaphore fm)%name s)
  *trap #7
%end

%record(*)%map get object(%integer type,val)
%record(semaphore fm)%name o
  o == dequeue(queue pool)
  %unless o==nil %start
    o_header_queue == o
    o_header_tag = type
    o_owner == current process
    o_counter = val
  %finish
  %result == o
%end

%routine put object(%record(queue fm)%name o)
  o_tag = \o_tag
  enqueue(o,queue pool)
%end

%systemroutine put message {into mailbox without signalling semaphore} -
  (%record(message fm)%name m, %record(mailbox fm)%name b)
  *trap #10
%end

%systemrecord(message fm)%map get message {out of mailbox without waiting} -
  (%record(mailbox fm)%name b)
  *trap #11
%end

%systemroutine send message {to mailbox and signal semaphore} -
  (%record(message fm)%name m, %record(mailbox fm)%name b)
  b == buffer pool %if b==nil
  put message(m,b)
  signal semaphore(b_semaphore)
%end

%systemrecord(message fm)%map receive message {out of mailbox after waiting} -
  (%record(mailbox fm)%name b)
  b == buffer pool %if b==nil
  semaphore wait(b_semaphore)
  %result == get message(b)
%end

%systemrecord(message fm)%map acquire message buffer
  %result == receive message(nil)
%end

%systemroutine return message buffer(%record(message fm)%name b)
  send message(b,nil)
%end

%record(semaphore fm)%mapspec create semaphore(%string(255)s,%integer c)
%record(mailbox fm)%mapspec create mailbox(%string(255)s,%record(semaphore fm)%name s)
%routinespec delete semaphore(%record(semaphorefm)%name s)
%routinespec delete mailbox(%record(mailboxfm)%name b)

%systemrecord(message fm)%map exchange message -
  (%record(message fm)%name m,%record(mailbox fm)%name b)
! Send M to B and await reply via one-off temporary mailbox.
%record(semaphore fm)%name s == create semaphore("",0)
%record(mailbox fm)%name r == create mailbox("",s)
  m_reply == r; send message(m,b)
  m == receive message(r)
  delete mailbox(r); delete semaphore(s)
  %result == m
%end

! Interrupt handling

%register(a4)%record(interrupt handler fm)%name current interrupt handler

%systemroutine return from interrupt
@0(a7)%integer pc
  current interrupt handler == current interrupt handler_header_forward
  pc = current interrupt handler_pc
%end

%systemroutine wait for interrupt
@0(a7)%integer pc
  current interrupt handler_pc = pc
  current interrupt handler == current interrupt handler_header_forward
  pc = current interrupt handler_pc
%end

%systemroutine add interrupt handler(%integer level)
%register(a1)%record(queue fm)%name q
%register(d3)%integer sr
  %returnunless 1<=level<=7
  q == int chain(level)_header
  sr = ortosr(16_2700)
  enqueue(current interrupt handler_header,q)
  movetosr(sr)
  *nop {let's not fall foul of the over-eager optimiser - aargh}
%end

%routine register kernel object(%record(*)%name o,%string(255)s)
%string(255)c
{{printstring("""";s;"""=")
  c = cliparam; cliparam = s
  *move.l o,a0; *trap #8; *move.l a0,o
  cliparam = c
{{romphex(addr(o)); newline
  %returnunless o==nil
  s = "Failed to register kernel object ".s
  %signal 0,1,,s
%end

%record(*)%map lookup kernel object(%string(255)s)
%record(*)%name o
%string(255)c
{{printstring("""";s;""":")
  c = cliparam; cliparam = s
  *trap #9; *move.l a0,o
{{romphex(addr(o)); newline
  cliparam = c
  %result == o %unless o==nil
  s = "Failed to locate kernel object ".s
  %signal 0,1,,s
%end

%systemrecord(semaphore fm)%map create semaphore(%string(255)s,%integer n)
%record(semaphore fm)%name q == get object('SE',n)
  register kernel object(q,s) %unless s=""
  %result == q
%end

%systemroutine delete semaphore(%record(semaphore fm)%name q)
  put object(q_header)
  register kernel object(q,"")
%end

%systemrecord(semaphore fm)%map lookup semaphore(%string(255)s)
%record(semaphore fm)%name q == lookup kernel object(s)
  q == nil %unless q_header_tag='SE'
  %result == q
%end

%systemrecord(mailbox fm)%map create mailbox(%string(255)s,%record(semaphore fm)%name x)
%record(mailbox fm)%name q == get object('MB',addr(x))
  register kernel object(q,s) %unless s=""
  %result == q
%end

%systemroutine delete mailbox(%record(mailbox fm)%name q)
  put object(q_header)
  register kernel object(q,"")
%end

%systemrecord(mailbox fm)%map lookup mailbox(%string(255)s)
%record(mailbox fm)%name q == lookup kernel object(s)
  q == nil %unless q_header_tag='MB'
  %result == q
%end

*temp d0-d1/a0-a1

! Implementation of TRAPs
! Although written as routines, none of these actually
! reach their %end, as they have to return using *RTE.

%routine trap0 {move to sr} (%registerinteger v)
@0(a7)%short sr
  *swap v; *move.w sr,v
  *swap v; *move.w v,sr
  *swap v; *ext v
  *rte
%end

%routine trap1 {or to sr} (%registerinteger v)
@0(a7)%short sr
  *swap v; *move.w sr,v
  *swap v; *or.w v,sr
  *swap v; *ext v
  *rte
%end

%constinteger privbit=64

%routine trap2 (%registerrecord(process fm)%name process)
! Obtain process attributes
  process == current process %if process==nil
  d3 = process_priority&7
  d4 = 1; d4 = 0 %if process_asn&privbit=0
  d2 = process_time slice size
  d1 = system elapsed time-process_start time
  d0 = process_cputime
  *rte
%end
  
%routine trap3 (%registerrecord(process fm)%name process,
  %register(d2)%integer time slice size,priority,privilege)
! Change process attributes
  process == current process %if process==nil
  *otsr #16_700
  %if privilege&1=0 %start
    process_asn = process_asn&\privbit
  %else
    process_asn = process_asn!privbit
  %finish
  priority = priority&7
  process_runqueue == runqueue(priority)
  process_priority = priority
  process_time slice size = time slice size
  process_time slice left = time slice size
  *movem.l d0-d1/a0-a1/a4,-(sp)
  *mfusp a4
  *movem.l d2-d7/a2-a6,-(sp)
  current process_ssp = a7
  suspend(current process_runqueue_header)
%end

%routine spare trap
  *subq.l #2,2(sp)
  *move.l 16_1010,-(sp)
  *rts
%end

%routine trap6 {signal semaphore} (%registerrecord(semaphore fm)%name s)
  int signal semaphore(s)
  *rte
%end

%routine trap7 {semaphore wait} (%register(a1)%record(semaphore fm)%name s)
  a1 = a0
  suspend(s_header) %if s==nil           {quiet death}
  %if s_header_tag='SE' %start
    *otsr #16_700
    lock(s_header)
    s_counter = s_counter-1
    %if s_counter>=0 %start              {no need to wait}
      unlock(s_header)
    %else
      s_counter = s_counter+1            {leave consistent while
      unlock(s_header)                   {we preserve context}
      *movem.l d0-d1/a0-a1/a4,-(sp)
      *mfusp a4
      *movem.l d2-d7/a2-a6,-(sp)
      currentprocess_ssp = a7
      lock(s_header)
      s_counter = s_counter-1            {do again}
      %if s_counter<0 %start             {it should be}
        enqueue(currentprocess_header,s_header)
        unlock(s_header)
        s == nil
        suspend(s_header)
      %finish
      unlock(s_header)                   {otherwise false alarm}
{}rompstr("Wait: false alarm"); rompsym(nl)
      *movem.l (sp)+,d2-d7/a2-a6         {undo preservation}
      *movem.l (sp)+,d0-d1/a0-a1/a4
    %finish
  %else
    rompstr("*Wait: Invalid Semaphore "); rompsym(s_header_tag>>8)
    rompsym(s_header_tag); rompsym(' '); romphex(addr(s)); rompsym(nl)
  %finish
  *rte
%end

%routine trap8 {register kernel object} (%registerrecord(kernel object fm)%name o)
  *otsr #16_700
  %if cliparam#"" -
  %and addr(o)&255=0 -
  %and addr(ko(1))<=addr(o)<=addr(ko(queue pool size)) %start
    o_owner == current process
    *temp d0-d1/a1-a2
    *move.l a2,-(sp)
    o_name = cliparam
    *move.l (sp)+,a2
  %finishelse o == nil
  *rte
%end

%routine trap9 {lookup kernel object} (%registerrecord(kernel object fm)%name o)
%register(d1)%integer i
  *otsr #16_700
  %unless cliparam="" %start
    %for i = 1,1,queuepoolsize %cycle
      o == ko(i)
      *temp d0/a1-a2
      %if o_owner##nil %and o_name=cliparam %start
        *rte
      %finish
    %repeat
  %finish
  o == nil
  *rte
%end

%routine trap10 {put message} (%registerrecord(message fm)%name m,
                               %registerrecord(mailbox fm)%name b)
  *otsr #16_700
  lenqueue(m_header,b_header)
  *rte
%end

%routine trap11 {get message} (%registerrecord(mailbox fm)%name b)
  *otsr #16_700
  b == ldequeue(b_header)
  *rte
%end

*temp

%begin
%integer j
%label scheduler,int1,int2,int3,int4,int5,int6,int7,intret
@16_1000 %integerarray exception vector(0:47)

{--------------------------}
{ Main program begins HERE }
{--------------------------}

! Plug in exception vector table entries,
! first traps 14:0, then interrupts 7:1.
! (Trap 15 and Int 0 are dealt with by PREFIX).

  *move.l a7,a0
  *lea exception vector(47),a7
  *pea sparetrap
  *pea sparetrap
  *pea sparetrap
  *pea trap11
  *pea trap10
  *pea trap9
  *pea trap8
  *pea trap7
  *pea trap6
  *pea sparetrap
  *pea sparetrap
  *pea trap3
  *pea trap2
  *pea trap1
  *pea trap0
  *pea int7
  *pea int6
  *pea int5
  *pea int4
  *pea int3
  *pea int2
  *pea int1
  *move.l a0,a7

! Initialise kernel object pools

  queue pool == star(ko(1))
  setup queue(queue pool)
  queue pool_tag = 'QP'

  buffer pool == star(ko(2))
  setup queue(buffer pool_header)
  buffer pool_header_tag = 'BP'

  buffer pool_semaphore == star(ko(3))
  setup queue(buffer pool_semaphore_header)
  buffer pool_semaphore_header_tag = 'SE'
  buffer pool_semaphore_counter = 0

  enqueue(star(ko(j)),queue pool) %for j = 4,1,queue pool size
  return message buffer(star(bu(j))) %for j = 1,1,buffer pool size

! Initialise the interrupt handler chains

  %for j = 1,1,7 %cycle
    setup queue(intchain(j)_header); intchain(j)_pc = addr(intret)
  %repeat
  intchain(1)_pc = addr(scheduler)

! Create the run queues

  %for j = 0,1,7 %cycle
    runqueue(j) == get object('RQ',addr(level 1 interrupt register))
  %repeat

! Return to loader

  set priority(7)
  %stop

! Scheduling interrupt handler

@16_100002 %byte mmu ud asn,*,mmu up asn
%register(d1)%integer i
scheduler:
  *otsr #16_700
  %unless currentprocess==nil %start
    interrupted priority = current process_priority
    %if current process_time slice left<0 %start  {expired}
{{rompstr("Time-slice"); rompsym(nl)
      currentprocess_timesliceleft = currentprocess_time slice size
      *mfusp a4
      *movem.l d2-d7/a2-a6,-(sp)
      current process_ssp = a7
      lenqueue(currentprocess_header,currentprocess_runqueue_header)
      currentprocess == nil
    %finish
  %finish
  interrupted priority = -1 %if currentprocess==nil
  i = 7
  %while i > interrupted priority %cycle
    rivalprocess == ldequeue(run queue(i)_header)
    %unless rivalprocess==nil %start
      %unless current process==nil %start
{}rompstr("Pre-empt ";currentprocess_name;" @"); rompsym(i+'0';nl)
        *mfusp a4
        *movem.l d2-d7/a2-a6,-(sp)
        current process_ssp = a7
        lenqueue(current process_header,current process_run queue_header)
      %finish
      currentprocess == rivalprocess
      currentprocess_starttime = system elapsed time
      a7 = currentprocess_ssp
      *movem.l (sp)+,d2-d7/a2-a6
      *mtusp a4
      %unless cputype=68000 %start
!!      i = currentprocess_vbr
!!      *=16_4e7b; *=16_1801 {*movec i,vbr
        i = currentprocess_asn
        mmu ud asn = i; mmu up asn = i
      %finish
      *movem.l (sp)+,d0-d1/a0-a1/a4
      *rte
    %finish
    i = i-1
  %repeat
intret:
  *movem.l (sp)+,d0-d1/a0-a1/a4
  *rte

int1:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(1)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int2:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(2)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int3:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(3)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int4:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(4)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int5:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(5)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int6:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(6)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)
int7:
  *movem.l d0-d1/a0-a1/a4,-(sp)
  current interrupt handler == intchain(7)_header_forward
  a0 = current interrupt handler_pc
  *jmp (a0)

%end
