;        title SDOS/RT (C) 1986 Software Dynamics, Inc.
         page  SDOS/RT (C) 1986 Software Dynamics, Inc.
*        SDOS/RT: Software Dynamics Operating System for Real-Time applications
*        (C) 1986 Software Dynamics, Inc. -- All Rights Reserved
*              Software Dynamics, Inc.
*              2111 West Crescent, Suite G
*              Anaheim, California, 92801
*
*        SDOS/RT provides a multi-tasking kernal operating system
*        for Real-Time applications.  An arbitrary number of
*        Tasks, each of priority 0-255 (255 being highest) are
*        scheduled by a very fast scheduler.  Each task operates
*        in an address space; multiple tasks may share a single
*        space.  A special space, the "system" space, holds all objects
*        directly manipulated by SDOS/RT, including Task control blocks,
*        address space maps, interrupt poll chains, event blocks and
*        other objects.  Routines to move data between one space and another
*        are available.  A task can wait on an arbitrary condition
*        composed of one or more Events (special counters), or a timeout.
*        A real-time clock is used to provide Time-of-day, Wait-for-n-ticks,
*        and round-robin equal-task-priority scheduling.   The
*        scheduler assures that no task ever starves to death.
*        Interrupt latency is kept to an absolute minimum throughout
*        the system.  Stack switching on interrupts allows a maximum of
*        8 data bytes on the interrupted task's stack before switching
*        to an interrupt stack occurs, ensuring high-performance interrupt
*        routines while assuring controlled stack overhead for all tasks.
*        Tasks operating in the system space can have an arbitrarily large
*        context block of their own to use at virtual locations $20-$FF
*        (locations 0-$1F are dedicated to 6801 control registers).
*        The interrupt poll chain is dynamically configured according to
*        priority, and vectored interrupts are supported.
         page
; User changeable definitions
         ifund SystemSpaceMap
SystemSpaceMap equ 0                   System Space Map selector constant
         fin

         ifund InterruptStackSize
InterruptStackSize equ 50              How much interrupt stack space needed
         fin

         ifund RTCODE
RTCODE   equ   $F000                   Place to put code
         fin

         ifund RTRAM
RTRAM    equ   $E600                   Scratch store above applications RAM
         fin

         ifund RTTNZP
RTTNZP   equ   0                       "Some task has non-zero Z register" is false
         fin
         page
changed  equ   0                       synonym for 0 to initialize variables

         ifund m6800
m6800    equ   0
         fin
         ifund m6801
m6801    equ   0
         fin
         ifund m6809
m6809    equ   0
         fin
         ifund m6811
m6811    equ   0
         fin
         if    m6800!m6801!m6811
inten    equ   $0E                     "CLI" instruction
intds    equ   $0D                     "SEI" instruction
         else  (m6809)
inten    equ   $1CAF                   ANDCC #\%01010000 (allow IRQ & FIRQ)
intds    equ   $1A50                   ORCC #%01010000 (disable IRQ & FIRQ)
         fin
         page
*        Context Block Definitions
*        (Offsets given assuming indexing using (X) after TSX instruction)
         if    m6800!m6801             includes h6303
         org   0
cb:cc    rmb   1                       condition codes
cb:b     rmb   1                       B register
cb:a     rmb   1                       A register
cb:x     rmb   2                       X register
cb:pc    rmb   2                       Program Counter
cb:size  equ   *                       Size of context block
         elseif m6811       ; 6801 with 2 index registers
         org   0
cb:cc    rmb   1                       condition codes
cb:b     rmb   1                       B register
cb:a     rmb   1                       A register
cb:x     rmb   2                       X register
cb:y     rmb   2                       Y register
cb:pc    rmb   2                       Program Counter
cb:size  equ   *                       Size of context block
         else  (m6809)
         org   0
cb:cc    rmb   1                       condition codes
cb:a     rmb   1                       A register
cb:b     rmb   1                       B register
cb:z     rmb   1                       Z register
cb:x     rmb   2                       X register
cb:y     rmb   2                       Y register
cb:u     rmb   2                       U register
cb:pc    rmb   2                       Program Counter
cb:size  equ   *                       Size of context block

         org   0
firq:cc  rmb   1                       condition codes for FIRQ context block
firq:pc  rmb   2                       Program Counter
firq:size equ  *                       Size of context block
         fin
;        Page Zero locations of interest to all Tasks and Interrupt routines
;
         org   $20                     above page zero I/O registers
         if    m6801!m6811
? ; other I/O devices above location $20
         fin
RT:SPad  rmb   8                       page zero location available to all tasks
                                       ; also used by SDOS/RT task level calls
RT:USPZ  equ   *                       beginning of user task page zero

;        org   $f0
;RT:LFLGS rmb  1                       holds line flags for BASIC Runtime Pkg
;RT:USP  rmb   2                       holds User Stack Pointer from SDOS/MT1.x
         org   $F3                     storage reserved for use by SDOS/RT
RT:IPad  rmb   8                       scratch usable whenever interrupts disabled
         org   $FE
RT:EStk  rmb   2                       error recovery stack pointer for tasks
         page
*
*        Task Control Block (TCB) definitions
               org 0
tcb:nexttcb    rmb 2                   pointer to next tcb in priority order
tcb:priority   rmb 1                   priority of this task; 255 is high
tcb:parameter  rmb 2                   contains wake-up code or zero
tcb:stack      rmb 2                   stack pointer of interrupted task
tcb:estk       rmb 2                   error recovery stack pointer
tcb:map        rmb 2                   pointer to address space map
tcb:scratchpad rmb 8                   holds page zero scratchpad locations
tcb:cpuslw     rmb 2                   cpu time in milliseconds since last wake-up
tcb:cputime    rmb 4                   number of cpu ticks consumed by task
tcb:id         rmb 8                   unique task id assigned by system
tcb:size       rmb 0                   size of tcb in bytes
*
*        Timeout Control Blocks (TOB) (for delays up to 4 seconds)
               org 0
tob:qflink     rmb 2                   pointer to next tob in time-order
tob:qblink     rmb 2                   pointer to previous tob in time-order
tob:delaydelta rmb 2                   negative number of ticks left before BOOM
tob:routine    rmb 2                   interrupt routine to execute upon timeout
tob:parameter  rmb 2                   passed to timeout interrupt routine
tob:size       rmb 0                   size of timeout control block in bytes
*
*        Event Control Blocks (EVN) definitions
               org 0
evn:count      rmb 2                   16 bit counter space (# events stored)
evn:tcb        rmb 2                   pointer to task to wake, or 0
evn:parameter  rmb 2                   what to tell task to be woken
evn:size       rmb 0                   size of event control block
*
*        Semaphore Control Block (SCB) definitions
               org 0
scb:count      rmb 1                   hold resource count (-128..127)
scb:tcbq       rmb 2                   points to list of tasks TCBs waiting
scb:size       rmb 0                   size of semaphore control block
;
; Interrupt Poll Block displacements
               org 0
ipb:priority   rmb 1                   holds priority of interrupt; 254 is high
ipb:jmpnextipb rmb 1                   holds JMP opcode
ipb:nextipb    rmb 2                   pointer to next IPB or tail IPB
ipb:entry      rmb 0                   entry point to poll block code

err:nosuchipb  equ    1080            No such Interrupt Poll Block
         page
         org   RTRAM
; SDOS/RT Working storage definitions
;
? ; how about RT:BANK? TCB:MAP?
RT:ISTK  fdb   changed                 ; holds stack pointer of task if RT:ISSF=0
RT:ISSF  fcb   1                       ; 0 --> Using Interrupt Stack
;                                      ; <>0 --> using Task Stack
;  Sample code to switch tasks follows.
;        lsr   RT:ISSF                 ; stacks switched yet ?
;        bcc   ...1                    ; b/ yes, don't bother doing it
;        sts   RT:ISTK                 ; save current task's stack pointer
;        lds   #RT:IISP                ; and switch Initial Interrupt Stack Pointer
; ...1

; The size of the interrupt stack is defined by user application I/O needs
         fcb   $5a                     ; This gets changes if interrupt stack overflows
         rmb   InterruptStackSize      ; stack space used by interrupt routines
RT:IISP  equ   *-(m6800!m6801!m6811)   ;
         ; What follows is a dummy context block which is restored by RTI
         ; of interrupt routine that actually performed the stack switch.
         ; This context block forces control to transfer to RT:IUSP.
         if    m6800!m6801
         ; Note that context block has interrupts DISABLED.
         $FF,$00,$00,$0000,RT:IUSP     ; CC, B, A, X, P registers in context block
         elseif m6811
         ; Note that context block has interrupts DISABLED.
         $FF,$00,$00,$0000,$0000,RT:IUSP ; CC, B, A, X, Y, P registers in context block
         else  (m6809)
         ; Note that this is a FIRQ context block and has interrupts DISABLED.
         $7F,RT:IUSP     ; CC, P registers in context block
         fin
         page
RT:TCBQ  fdb   RT:IdleTaskTCB          points to list of tasks to execute
RT:CTCB  fdb   RT:IdleTaskTCB          points to initial task to run
RT:TSUT  fdb   changed                 Task Start Up Time (milliseconds: lower 16 bits of wall clock)

RT:IdleTaskTCB ; Idle Task TCB.  Terminates list of ready tasks.
; This task "soaks up" all the idle processor time.
         fdb   0                       tcb:nexttcb
         fcb   0                       tcb:priority (255 is high)
         fdb   $FFFF                   tcb:parameter (nonzero req'd by RT:DEvn)
         fdb   RT:IdleTaskStack        tcb:stack
         fdb   RT:IdleTaskStack        tcb:estk
         fdb   SystemSpaceMap          tcb:map
         fcb   0,0,0,0,0,0,0,0         tcb:scratchpad
         fcb   0,0,0,0                 tcb:cputime
         fcb   0,0,0,0,0,0,0,0         tcb:id

         fcb   $a5                     this byte damaged if stack overflows
         rmb   cb:size                 room for NMI interrupt context
         fcb   0,0,0,0,0,0,0,0         mandatory 8 bytes of available stack
RT:IdleTaskStack ; value to use for stack pointer at reset time
         if    m6800!m6801
         fcb   $00,$00,$00             context block: CC, B, A,...
         fdb   $0000,RT:IdleTaskCode                  X, PC
         elseif m6811
         fcb   $00,$00,$00             context block: CC, B, A,...
         fdb   $0000,$0000,RT:IdleTaskCode            X, Y, PC
         else  (m6809)
         fcb   $80,$00,$00,$00,        context block: CC, A, B, Z,...
         fdb   $0000,$0000,$0000,RT:IdleTaskCode      X, Y, U, PC
         fin
RT:IdleTaskStackEnd ; end of Idle Task Stack area
         page
RT:DummyTOB ; Head/Tail of Timeout Block queue
         fdb   RT:TSTOB                ; point forward to Time Slice TOB initially
         fdb   RT:TSTOB                ; point backward to TSTOB
         fcb   $01,changed             ; max time (this TOB NEVER times out)
         fdb   RT:DummyTOBTimedOut     ; impossible event
;        fdb   $0000                   ; TOB:Parameter (not used for DummyTOB)

RT:TSTOB ; Time Slice TOB: goes off every 100 milliseconds
         fdb   RT:DummyTOB             ; point forward to end of TOB queue
         fdb   RT:DummyTOB             ; point backward to end of TOB queue
         fdb   $FFFF                   ; goes off almost instantly
         fdb   RT:TimeSlice            ; where to go when time slice expires
         fdb   100                     ; set (D) to next time slice

RT:Clk   fcb   changed,changed,changed,changed ; time in ticks since system startup

RT:HandlingExpiredTOBFlag changed      ; <>0 --> RT:Tick is busy with TOBs

RT:HighPriorityIntPollChainHead ; head of high priority interrupt poll chain
         fcb   255                     IPB:priority; high priority --> this block stays 1st
                                       ; initially, no high priority device
         jmp   RT:HighPriorityIntPollChainEnd+IPB:entry IPB:jmpnextipb
RT:HighPriorityInt ; come here via lowest priority vector
; (for non-vectored systems, this is usually $FFF8)
         if    RTTNZP                  ; some task has Z register non-zero
         if    m6809
         clra                          ; make Z register zero...
         tfr   a,dpr                   ; so that references to RT:IPAD work
         else  m6811
? ; don't know how to accomplish this
         fin   m6809
         fin   RTTNZP
         jmp   RT:HighPriorityIntPollChainHead+IPB:jmpnextipb

RT:LowPriorityIntPollChainHead ; head IPB of low priority interrupt poll chain
         fcb   255                     high priority --> this block stays 1st
                                       ; initially, no high priority device
         jmp   RT:LowPriorityIntPollChainEnd+IPB:entry jmp to next IPB
RT:LowPriorityIntChain ; head of low priority interrupt chain
;        come here if no high priority devices need service
;        perform stack switch now as convenience for low priority routines
         lsr   RT:ISSF                 stack already switched ?
         bcc   RT:LowPriorityIntPollChainHead+IPB:jmpnextipb
         sts   RT:ISTK                 save task stack pointer
         lds   #RT:IISP                use Interrupt Stack from here on
         jmp   RT:LowPriorityIntPollChainHead+IPB:jmpnextipb

RT:UnknownIntCounter fdb changed       counts # unidentifiable interrupts

; SDOS/RT Local names for various scratchpad locations
tempx    equ   RT:SPad

itempx   equ   RT:IPad                 scratch area for routines with ints disabled
itempa   equ   itempx+0                overlay onto itempx
itempb   equ   itempx+1
itempd   equ   RT:IPad+2
         page
         org   RTCODE
*        SDOS/RT Reset Code
RT:INIT ; Called by application program before system operation begins
; After performing its own reset code, application program calls this
; Then control must be passed to RT:STRT by application init routines.
; Reset SDOS/RT RAM storage
         clr   RT:ISSF                 0 --> Using Interrupt Stack
;        lds   #RT:IISP                use Interrupt stack for stack area
         ; Application must supply its own stack area for execution of reset code
         clra                          set DP register
         tfr   a,dp

         ldx   #RT:IISC                initialize Interrupt Stack
         ldy   #RT:IISP+(m6800!m6801!m6811)
         ldd   #RT:IISS
         jsr   RT:BMOV

         ; set up so idle task appears to have been interrupted
         ldx   #RT:IdleTaskStackEnd    set up Idle Task stack area
         ldd   #RT:IdleTaskCode        PC for idle task
         jsr   RT:ISTA                 set up stack area

         ldx   #RT:IdleTaskTCB         set up Task Queue...
         jsr   RT:ITCB                 go initialize TCB for idle task
         ldd   #$FFFF                  make idle task TCB:PARAMETER non-zero
         std   tcb:parameter,x         (this fact is used by RT:DEvn/RT:SIE)
         clr   tcb:priority,x          set priority of task to zero
         stx   RT:TCBQ                 to point to idle task
         stx   RT:CTCB                 make idle task be currently running task
         ldd   tcb:stack,x
         sts   RT:ISTK                 holds stack pointer of task if RT:ISSF=0

; To add more initial tasks to power up version of SDOSRT, insert code here.
;        ldx   #Task1TCB               get pointer to TCB
;        lda   #Task1Priority          get task priority
;        jsr   RT:ITCB                 go initialize task control block
;        jsr   RT:ATCB                 add task control block to ready queue
;        ldx   #Task2TCB
;        ....
;
         page
         ldx   #RT:PollChainInit       set up interrupt poll chain logic...
         ldy   #RT:HighPriorityIntPollChainHead in RAM
         ldd   #RT:PollChainInitEnd-RT:PollChainInit
         jsr   RT:BMOV

         ldd   #0                      zero number of unknown interrupts
         std   RT:UnknownIntCounter
         std   RT:Clk                  zero the 32 bit clock
         std   RT:Clk+2

         ldx   #RT:DummyTOB            initialize Head/Tail of TOB queue
         ldd   #RT:DummyTOBTimedOut    where to go if impossible event
         jsr   RT:ITOB                 (this links TOB to itself)
         lda   #$01                    = upper byte of most negative number
         sta   RT:DummyTOB+TOB:DelayDelta

         ldx   #RT:TSTOB               set up Time Slice TOB
         ldd   #RT:TimeSlice           where to go on timeslice expiration
         jsr   RT:ITOB                 initialize TOB
         ; The clock hardware must be properly initialized when we get here
         ldd   #100                    start up time slice TOB
         jsr   RT:UTOB                 by inserting it into time queue
         ; Note: this indirectly causes the first clock interrupt to occur
         clr   RT:HandlingExpiredTOBFlag note "not handling expired TOB"

         ldx   $fff8                   find location of JMP at IRQ vector
         ldd   #RT:HighPriorityInt     and make JMP go to poll chain
         std   1,x

         rts                           Intialization complete, start operations

RT:IISC ; Initial Interrupt Stack Content
         if    m6800!m6801
         ; Note that context block has interrupts DISABLED.
         $FF,$00,$00,$0000,RT:IUSP ; CC, B, A, X, PC in context block
         elseif m6811
         $FF,$00,$00,$0000,$0000,RT:IUSP ; CC, B, A, X, Y, PC in context block
         else  (m6809)
         $7F,RT:IUSP                   ; CC, PC in FIRQ context block
         fin
RT:IISS  equ   *-RT:IISC               size of Initial Interrupt Stack Content
         page
; Removal of the following line will be interpreted as copyright infringement!
        fcc   "SDOS/RT Copyright (C) Software Dynamics, Inc. 1986"

RT:ISTA ; initialize stack area for execution of task
; (X) holds pointer to byte just above last usuable byte of stack area
; (D) holds initial PC for task
; exits with initial stack for task in (D)
; callable from reset code or task level code
         leax  -cb:size,x              make space for a context block
         std   cb:pc,x                 save initial PC in context block
         if    m6809
         lda   #$80                    put Condition Code saying "entire..."
         sta   cb:cc,x                 and "interrupts enabled" into stack
         clr   cb:z,x                  default page zero pointer
         ; ? is this the right thing to do ?
         tfr   x,d                     so RT:ITCB can be called next
         else  m6800!m6801!m6811
         clr   cb:cc,x                 make interrupt-enabled Condition code
         stx   tempx                   do "TSD"...
         ldd   tempx
         subd  #1                      ...treat like TSX
         fin
         rts

RT:ITCB ; initialize Task Control Block at (X)
; (D) holds initial stack pointer value
; Scratchpad is left unchanged
; Exits with (X) pointing to TCB
; callable from task level code or reset code
         std   tcb:stack,x             save stack pointer for task
         ldd   #RT:EDST                set last-ditch error defense
         std   tcb:Estk,x
         ldd   #SystemSpaceMap         set map control word
         std   tcb:map,x               to default map
         ldd   #0                      get a zero to store
         std   tcb:nexttcb,x           mark task as "not in queue"
         std   tcb:parameter,x         and note that "no wakeup present"
         std   tcb:cpuslw,x            zero CPU time since last wakeup
         std   tcb:cputime,x           zero cputime used by task
         std   tcb:cputime+2,x
         stx   tcb:id,x                Insert code here to set task id.
         rts

RT:EDST equ *-(m6800!m6801!m6811) ; dummy context block with interrupts disabled for erroring task
         fdb   RT:EDST,RT:EDIE         dummy error recovery block

RT:EDIE ; task with no error recovery lands here if error occurs
         swi
         page
RT:ATCB ; Add task whose TCB is at (X) to Ready Queue...TCB:PRIORITY already set
         intds                         lock out the world
         jmp   RT:ITSC                 insert task in queue and switch contexts if needed

RT:CHPR ; Change priority of task to new priority in (A)
; called from task level routines only
         ldx   RT:CTCB                 get pointer to current task control block
         cmpa  tcb:priority,x          same priority as old priority ?
         beq   RT:CHPRrts              b/ yes, all done
         intds                         lock out the world
         sta   tcb:priority,x
         ldx   tcb:nexttcb,x           find next TCB in queue
         stx   RT:TCBQ                 take current TCB out of queue
         ldx   RT:CTCB                 get pointer to current task
         jmp   RT:ITSC                 insert task in queue and switch contexts if needed

RT:CHPRrts
         inten                         b/ yes
         rts
         page
RT:IdleTaskCode ; this is the code that the idle task executes
         wai                           stack interrupt context, wait for int
         bra   RT:IdleTaskCode         go do it again!

RT:IUSP ; Interrupt Unswitch Stack Pointer
; Comes here after interrupt routine that switched stacks does RTI
; Interrupts are disabled, DP register has value at time task was interrupted.
;        bra   RT:ISCH                 ; go see if task switch needed

RT:ISCH ; We might need to switch tasks, check task queue.
; Interrupts are disabled, and RT:ISSF=0
         inc   RT:ISSF                 ; (6~) note that stacks are not switched
         lds   RT:ISTK                 ; (7~) restore stack of current task
         ldx   RT:CTCB                 ; get pointer to current TCB
         cpx   RT:TCBQ                 ; same task at head of TCB queue ?
         ; Note: CPX returns NOT EQUAL if task queue has changed.
         beq   RT:IRTI                 ; b/ no need to switch tasks, just exit
         ; Save the context of the currently executing task.
         sts   TCB:stack,x             ; (6+1~) and save it in the Task Control Block
         ldd   RT:EStk                 ; (5~) save Error Recovery Stack Pointer...
         std   TCB:EStk,x              ; (6+1~) in TCB
         ldd   RT:SPad+0               ; (5~) Save Task Scratch Pad in TCB
         std   TCB:scratchpad+0,x      ; (6+1~)
         ldd   RT:SPad+2               ; (5~)
         std   TCB:scratchpad+2,x      ; (6+1~)
         ldd   RT:SPad+4               ; (5~)
         std   TCB:scratchpad+4,x      ; (6+1~)
         ldd   RT:SPad+6               ; (5~)
         std   TCB:scratchpad+6,x      ; (6+1~)
         ; we can use task's stack pointer safely here
         jsr   IO:RTOD                 ; Read Time Of Day in milliseconds (must be fast!)
         ; *** WARNING: IO:RTOD MUST NOT DISTURB (X)
         ; *** IT MUST ALSO BE *VERY* FAST, SINCE DONE AT TASK SCHEDULE TIME
         pshd                          ; save current time of day
         subd  RT:TSUT                 ; subtract Task Start Up Time
         ; What if > 60 seconds of CPU elapsed before task switch?
         ; Then this value is incorrect....GAK!
         ; Can we somehow force a task switch every 60 seconds ?
         addd  TCB:cpuslw,x            ; accumulate CPU since last task wait
         bcc   RT:ISCH1                ; b/ accumulated CPU < 65535 milliseconds
         ldd   #65535                  ; best approximation we can keep
RT:ISCH1 ; (D) has accumulated task CPU time since last wait
         ; The current task is ALWAYS charged for interrupt overhead. Tough.
         ; Note: this task CPU time accumulation must occur EVERY time a
         ; task enters/leaves the Run Queue...this happens automatically!
         ; No other place has to perform this computation.
         std   TCB:cpuslw,x            ; store accumulated CPU since last wait

         puld                          ; restore current time of day
         std   RT:TSUT                 ; note time we started up new task
         ; Now load up context of highest priority task and go execute it.
         page
RT:STRT ; control passes here after system initialization is complete
         ldx   RT:TCBQ                 ; (6~) fetch pointer to new task to run
         stx   RT:CTCB                 ; (6~) remember who is the new current task
         lds   TCB:stack,x             ; (6+1~) fetch task's stack pointer
         if    m6809
         ; We could choose to simply assert that DP has zero in it here.
         ; But we'd like the freedom to have large PZ's attached to each task.
         lda   cb:z,s                  ; (5+1~) fetch Z page for task
         tfr   a,dp                    ; (7~) and set Z page
         elseif m6811
? ; I don't know how to set the Z register on this machine
         fin
         ldd   TCB:scratchpad+6,x      ; (6+1~) Set page zero scratch pad from TCB
         std   RT:SPad+6               ; (5~)
         ldd   TCB:scratchpad+4,x      ; (6+1~)
         std   RT:SPad+4               ; (5~)
         ldd   TCB:scratchpad+2,x      ; (6+1~)
         std   RT:SPad+2               ; (5~)
         ldd   TCB:scratchpad+0,x      ; (6+1~)
         std   RT:SPad+0               ; (5~)
         ldd   TCB:EStk,x              ; (6+1~) Set Error Recovery Stack pointer
         std   RT:EStk                 ; (5~)
         ldx   TCB:nexttcb,x           ; find next TCB in queue
         ldd   TCB:map,x               ; fetch map selector information
         jsr   IO:SMAP                 ; call I/O routine to select map
RT:IRTI ; simply return to user
         rti                           ; (15~) and pass control to task
                                       ; ------
                                       ; (xx~) Task context switching time (6809)

RT:TimeSlice ; come here when RT:TSTOB expires
; This happens every 100 mS. --> 8 bit arithmetic is sufficient in clock routines
         ldx   #RT:TSTOB               ; put Time Slice TOB back in timer queue
         ldd   #100
         jsr   RT:UTOB
         rti

         if    0
         ldd   TCB:cpuslw,x            ; how much CPU time this task has eaten
         subd  TCB:cpuslw,x            ; how much time next task has used
? ; what if this quantity is greater than zero ?
; if both tasks have 65K ticks, this will set up a timer of zero, not right.
; What does "priority" mean now? A lower bound? FORGET IT? Who knows?
         negd                          ; how much time to give this task, max
         ldx   #RT:TSTOB               ; set up time slice TOB
         jsr   RT:UTOB                 ; update time slice time out block
; When TSTOB goes off,...?
; Now, when a task wakes up after waiting awhile, we compute how long
; it has been asleep, and subtract from TCB:cpuslw.  The difference, if
; positive, is the task's priority; if negative, the task's priority is
; zero, with zero being high.
         addd  TCB:cputime+2,x         ; accumulate used CPU time
         std   TCB:cputime+2,x
         bcc   RT:ISCH1                ; b/ no carry to propogate
         inc   TCB:cputtime+1
         bne   RT:ISCH1
         inc   TCB:cputtime
         fin

         page
         if    0
;        Sample code in I/O package to handle user space map
;
MappedTaskRTI ; rti in scheduler comes here for mapped task
;        Note that stack for such a task has dummy context block at bottom
;        with PC pointing here, CC set with interrupts (IRQ and FIRQ) are disabled
;        and (X) contains stack pointer to be used in mapped space
         jsr   IOTurnOnMap             turn on last map selected
         txs                           set up correct stack point
         rti                           at end of RTI, we are in user space

MappedSpaceInterrupt ; come here after interrupt while in mapped space
;        IRQ interrupt is disabled
         if    m6809
         orcc  %01000000               prevent FIRQ, too...
         fin
         ldx   RT:CurrentTask          get pointer to tcb
         ldx   tcb:stack,x             points to stack pointer
         sts   cb:x,x                  save mapped space sp in context block
         txs                           set sp to user stack
         jsr   IOEnableSystemMap       turn on system space
         jmp   IOInterruptPollChain    and go handle the interrupt
;
MappedSpaceSystemCall ; come here to invoke SDOS/MS
;        context block has been pushed in mapped user space
         fin   0
         page
RT:RTI ; exit from scheduler/clock tick routine
;        (S) is set to stack pointer for task
;        Address space is set up
         rti                           this is all it takes to wake up task

RT:SInt ; Simulate interrupt to (X), pass (D) to routine
; Stacks are switched when routine gets control
; Called from task level code ONLY
; All registers destroyed, but scratchpad is preserved
         if    m6809
         pshs  a,b,dp,x,y,u            push phony context block, return addr = PC
         ldaa  #$80                    fake CC bits with "Entire" bit set
         pshs  a
         lda   1,s                     restore (A) to entry state
         intds                         lock out interrupts
         elseif m6811
         pshy                          manufacture phony context block
         pshx
         psha
         pshb
         intds                         lock out interrupts
         staa  itempa                  save (A) momentarily
         clra                          force interrupts to be enabled on return
         psha                          push condition code register
         ldaa  itempa
         elseif m6801
         pshx                          manufacture phony context block
         psha
         pshb
         intds                         lock out interrupts
         staa  itempa                  save (A) momentarily
         clra                          force interrupts to be enabled on return
         psha                          push condition code register
         ldaa  itempa
         else  m6800
         ins                           manufacture phony context block
         ins                           (with garbage for X register)
         psha
         pshb
         intds                         lock out interrupts
         staa  itempa                  save (A) momentarily
         clra                          force interrupts to be enabled on return
         psha                          push condition code register
         ldaa  itempa                  restore (A)
         fin
         lsr   RT:ISSF                 stack already switched ? (ans: always NO)
;        bcc   RT:SInt1                b/ yes, don't switch stacks again
         sts   RT:ISTK                 no, save current stack pointer
         lds   #RT:IISP                and use interrupt stack pointer
;RT:SInt1 ; come here if stacks already switched
         jmp   ,x                      pass control to interrupt routine
         page
RT:ILck ; Initialize Lock (Semaphore) at (X) to (A)<128 resource units
; SCB must not be in use
; callable from task level or reset code
         sta   scb:count,x             set available resource count
         ldd   #RT:IdleTaskTCB         make queue point to "end of queue" TCB
         std   scb:tcbq,x              (this TCB has lowest possible priority)
         rts

RT:UnLk ; unlock block of code whose semaphore is in (X)
         intds                         ; lock out the world momentarily
         inc   scb:count,x             ; anybody in queue ?
         bgt   RT:ITSX                 ; b/ no, done releasing resource
         if    m6800!m6801
         stx   itempx                  ; save pointer to semaphore
         ldx   scb:tcbq,x              ; pointer to TCB to activate
         ldd   tcb:nexttcb,x           ; find pointer to TCB following that
         stx   itempd                  ; save pointer to TCB to activate
         ldx   itempx                  ; pointer to semaphore
         std   scb:tcbq,x              ; remove task from SCB queue
         ldx   itempd                  ; pointer to TCB to make ready to run
         elseif m6811
         ldy   scb:tcbq,x              ; pointer to TCB to activate
         ldd   tcb:nexttcb,y           ; find pointer to TCB following that
         std   scb:tcbq,x              ; remove task from SCB queue
         tfr   y,x                     ; pointer to TCB to make ready to run
         else  (m6809)
         ldu   scb:tcbq,x              ; pointer to TCB to activate
         ldd   tcb:nexttcb,u           ; find pointer to TCB following that
         std   scb:tcbq,x              ; remove task from SCB queue
         leax  0,u                     ; pointer to TCB to make ready to run
         fin
RT:ITSC ; insert task at (X) into queue and switch contexts if needed
; Assert: interrupts are disabled here
         jsr   RT:ITIQ                 ; insert task into ready queue
         ldx   RT:TCBQ                 ; are we still highest priority task ?
         cmpx  RT:CTCB                 ; ... ?
         beq   RT:ITSX                 ; b/ yes, pass control to caller
         ldx   #RT:ISCH                ; no, force task switch
         jmp   RT:SInt                 ; by interrupt to task scheduler

RT:ITSX  inten
         rts
         page
RT:Lock ; Lock block of code whose semaphore is in (X)
; Called only from Task level code
         intds                         ; lock out the world momentarily
         dec   scb:count,x             ; bump semaphore count
         bpl   RT:ITSX                 ; b/ safe for task to go thru code
         if    m6800!m6801
         stx   itempx                  ; save pointer to semaphore
         ldx   RT:TCBQ                 ; resource not available!
         ldaa  tcb:priority,x          ; (fetch task priority)
         ldx   tcb:nexttcb,x           ; remove this task from ready queue
         stx   RT:TCBQ
         ; Now insert task into semaphore wait queue
         ldx   itempx                  ; get pointer to semaphore
         ldx   scb:tcbq,x              ; find 1st TCB in semaphore queue
         cmpa  tcb:priority,x          ; insert next TCB before 1st TCB ?
         bls   RT:LockL                ; b/ no
         ldx   itempx                  ; yes, get pointer to 1st TCB
         ldd   scb:tcbq,x
         ldx   RT:CTCB                 ; make current TCB point to 1st TCB
         std   tcb:nexttcb,x
         ldd   RT:CTCB                 ; make current TCB be 1st task in queue
         ldx   itempx                  ; (address of semaphore)
         std   scb:tcbq,x
         elseif m6811
         stx   itempx                  ; save pointer to semaphore
         ldx   RT:TCBQ                 ; resource not available!
         ldaa  tcb:priority,x          ; (fetch task priority)
         ldx   tcb:nexttcb,x           ; remove this task from ready queue
         stx   RT:TCBQ
         ; Now insert task into semaphore wait queue
         ldx   itempx                  ; get pointer to semaphore
         ldx   scb:tcbq,x              ; find 1st TCB in semaphore queue
         cmpa  tcb:priority,x          ; insert next TCB before 1st TCB ?
         bls   RT:LockL                ; b/ no
         ldx   itempx                  ; yes, get pointer...
         ldd   scb:tcbq,x              ;      to 1st TCB
         ldx   RT:CTCB                 ; make current TCB point to 1st TCB
         std   tcb:nexttcb,x
         ldd   RT:CTCB                 ; make current TCB be 1st task in queue
         ldx   itempx                  ; (address of semaphore)
         std   scb:tcbq,x
         else  m6809
         leay  0,x                     ; save pointer to SCB
         ldu   RT:TCBQ                 ; resource not available!
         ldd   tcb:nexttcb,u           ; remove this task from ready queue
         std   RT:TCBQ
         ldaa  tcb:priority,u          ; fetch task priority
         ; Now insert task into semaphore wait queue
         ldx   scb:tcbq,x              ; find 1st TCB in semaphore queue
         cmpa  tcb:priority,x          ; insert next TCB before 1st TCB ?
         bls   RT:LockL                ; b/ no
         stx   tcb:nexttcb,u           ; make new TCB point to 1st in queue
         stu   scb:tcbq,y              ; make current TCB be 1st task in queue
         fin
         ldx   #RT:ISCH                ; force task switch
         jmp   RT:SInt                 ; by interrupt to task scheduler
         page
RT:LockL ; must insert current TCB after 1st TCB in queue
         stx   itempx                  ; save pointer to previous TCB
         ldx   tcb:nexttcb,x           ; find next task in list
         cmpa  tcb:priority,x          ; insert new task here ?
         bls   RT:LockL                ; b/ no, keep looking
         ; Now insert new TCB into queue before TCB at (X)
         if    m6800!m6801
         ldx   itempx                  get pointer to TCB before insert place
         ldd   tcb:nexttcb,x           get pointer to TCB after insert place
         ldx   RT:CTCB                 get pointer to TCB to insert
         std   tcb:nexttcb,x           make new TCB point to TCB after insert place
         ldx   itempx                  get pointer to TCB before insert place
         ldd   RT:CTCB                 get pointer to TCB to insert
         std   tcb:nexttcb,x           make TCB before insert place point to new TCB
         elseif  m6811
         ldy   itempx                  get pointer to TCB before insert place
         ldd   tcb:nexttcb,y           get pointer to TCB after insert place
         ldx   RT:CTCB                 get pointer to TCB to insert
         std   tcb:nexttcb,x           make new TCB point to TCB after insert place
         stx   tcb:nexttcb,y           make TCB before insert place point to new TCB
         else  (m6809)
         ; Now insert new TCB into queue before TCB at (X)
         ldu   itempx                  get pointer to TCB before insert place
         ldd   tcb:nexttcb,u           get pointer to TCB after insert place
         ldx   RT:CTCB                 get pointer to TCB to insert
         std   tcb:nexttcb,x           make new TCB point to TCB after insert place
         stx   tcb:nexttcb,u           make TCB before insert place point to new TCB
         fin
         ldx   #RT:ISCH                ; no, force task switch
         ; Passing control to RT:ISCH is necessary to get (X) loaded correctly
         jmp   RT:SInt                 ; by interrupt to task scheduler
         page
RT:AEvn ; Arm for (new) Event
; This routine zeroes TCB:Parameter, so that events tested
; by RT:CEvn or new events can record their code
; Task level call only!
         ldx   RT:CTCB                 ; for the current task,...
         clr   TCB:Parameter,x         ; mark "no event" occurred
         rts

RT:WEvn ; Wait for Event (EVN:): Task waits until some connected event has occurred
; Task level call only!
; Exits with task wake-up code in (D) and (X)
         ldx   RT:CTCB                 ; get pointer to current task TCB
         intds                         ; lock out the world
         ldaa  TCB:Parameter,x
         bne   RT:WEvnRts              ; b/ event has already occurred
         ldd   #RT:RTEW                ; where to go on wakeup
         pshd                          ; save as "return" address
         ldd   TCB:nexttcb,x           ; remove task from ready queue
         std   RT:TCBQ                 ; by making next task be first in queue
         ldd   #0                      ; mark this task as "not in queue"
         std   TCB:nexttcb,x
         ldx   #RT:ISCH                ; force task switch
         jmp   RT:SInt                 ; by interrupt to task scheduler

RT:WEvnRts ; come here to exit RT:WEVN if event has already occurred
         inten                         ; allow interrupts again
RT:RTEW ; Read TCB Event Wakeup code (TCB:Parameter)
; Task level call only!
; Exits with Z bit set if no wakeup code is present
         ldx   RT:CTCB                 ; get pointer to current task TCB
         if    m6800
         sei                           ; lock out the world
         ldaa  TCB:Parameter,x
         ldab  TCB:Parameter+1,x
         ldx   TCB:Parameter,x
         cli
         else  (m6801!m6809!m6811)
         ldd   TCB:Parameter,x         ; fetch parameter in splinter-free way
         tfr   d,x                     ; and set (X) and (D) to parameter
         fin
         tsta                          ; set Z if no wakeup condition
         rts
         page
RT:IEvn ; initialize Event Control Block at (X) to Event Parameter (X)
; callable by task level or by reset code
         std   evn:Parameter,x         ; save event code name
         clr   evn:Count,x             ; set event count to zero
         if    m6800
         ldd   #RT:IdleTaskTCB         ; connect event to Idle task (which never wa
         std   evn:TCB,x
         rts
         else  m6801!m6809!m6811
;        bra   RT:DEvn
         fin   m6800

RT:DEvn ; disconnect Event Control Block at (X) from task
; Task level call only!
         ldd   #RT:IdleTaskTCB         ; connect event to Idle task (which never wa
         if    m6800
         intds                         ; prevent timing splinters
         fin   m6800
         std   evn:TCB,x
         if    m6800
         inten                         ; unsafe to call from int code
         fin   m6800
         rts

RT:REvn ; reset Event Control Block counter for ECB at (X)
; Task level call only!
         clr   evn:Count,x               ; set event count to zero
         rts
         page
RT:CEvnFail ; Event is already connected to something else
         swi

RT:CEvn ; Connect Event Control Block at (X) to task
; Task level call only!
; Makes ECB point to current task
; If TCB has no wakeup parameter, and ECB has nonzero event count,
; then decrements EC count and sets wakeup parameter in TCB
; Note: only ONE task can be connected to an event at a time.
         intds
         ldd   evn:tcb,x                 ; is ECB connected to anything ?
         cmpd  RT:CTCB                   ; (connected to this task ?)
         beqd  RT:CEvn1                  ; b/ yes, that's ok...
         subd  #RT:IdleTaskTCB           ; unused? (point at idle task ?)
         bned  RT:CEvnFail               ; b/ ECB is already busy, BUG!
RT:CEvn1 ; assert: ECB is not connected to anything here
         ldd   RT:CTCB                   ; pointer to current task
         std   evn:TCB,x                 ; remember which TCB is connected
         ldaa  evn:Count,x               ; any events ?
         beq   RT:CEvnDone               ; b/ no
         ldd   evn:Parameter,x           ; fetch wakeup parameter from ECB
         if    m6800!m6801
         stx   itempx                    ; save pointer to ECB
         ldx   RT:CTCB                   ; TCB wakeup code already set ?
         tst   TCB:Parameter,x           ; ...?
         bne   RT:CEvnDone               ; b/ yes, we need do nothing further
         std   TCB:Parameter,x           ; store wakeup code into TCB
         ldx   itempx                    ; get pointer to ECB again
         elseif m6811
         ldy   RT:CTCB                   ; TCB wakeup code already set ?
         tst   TCB:Parameter,y           ; ...?
         bne   RT:CEvnDone               ; b/ yes, we need do nothing further
         std   TCB:Parameter,y           ; store wakeup code into TCB
         else  (m6809)
         ldu   RT:CTCB                   ; TCB wakeup code already set ?
         tst   TCB:Parameter,u           ; ...?
         bne   RT:CEvnDone               ; b/ yes, we need do nothing further
         std   TCB:Parameter,u           ; store wakeup code into TCB
         fin
         dec   evn:Count,x               ; eat up an event
RT:CEvnDone ; all done connecting event
         inten                           ; re-enable interrupts
         rts
         page
RT:SEvn ; Signal Event (X)
; Called by task level code only
         bsr   RT:SIE                  ; signal interrupt event
         ; Note: interrupts are disabled on return
         ; Efficiency note: we could avoid following check
         ; if we don't insert a new task into the ready queue
         ldx   RT:CTCB                 ; are we still the highest priority task ?
         cmpx  RT:TCBQ
         beq   RT:SEvnRTS              ; b/ yes
         ldx   #RT:ISCH                ; simulate interrupt to scheduler
         jmp   RT:SInt

RT:SEvnRTS
         inten                         ; allow interrupts again
         rts                           ; and exit

RT:ITSE ; Interrupt Timeout Signal Event
; TOB:Routine pointed here with TOB:Parameter pointing to an EVN block
; will cause the EVN to get Signalled when the timeout expires.
         bsr   RT:SIE                  signal TOB at (X)
         rti                           all done!

RT:SIW ; Signal Interrupt Waiting: (X) points to EVN
; Stacks are switched.  Sets evn:count to 1 if not already set to 1.
         intds                         ensure that interrupts are disabled
         if    m6800!m6801
         stx   itempx                  save pointer to ECB
         ldx   evn:tcb,x               find TCB connected to ECB
         ; assert: if ECB is "disconnected", it is really connected
         ; to the idle task, which ALWAYS has TCB:PARAMETER<>0!
         lda   tcb:parameter,x         wakeup code set ?
         beq   RT:SIEW                 b/ no, so set wakeup code
         ldx   itempx                  get pointer to ECB again
         else  (m6809!m6811)
         ldy   evn:tcb,x               find TCB connected to ECB
         ; assert: if ECB is "disconnected", it is really connected
         ; to the idle task, which ALWAYS has TCB:PARAMETER<>0!
         lda   tcb:parameter,y         wakeup code set ?
         beq   RT:SIEW                 b/ no, so set wakeup code
         fin
         ldaa  #1                      set event counter to 1
         sta   evn:count,x             (so task only wakes once for multiple RTSIW calls)
         rts

RT:SIE ; Signal Interrupt Event: come here with (X) --> Event Control Block
; Stacks are switched, but interrupts may be enabled/disabled
; Control returns with interrupts disabled
         intds                         ensure that interrupts are disabled
         if    m6800!m6801
         stx   itempx                  save pointer to ECB
         ldx   evn:tcb,x               find TCB connected to ECB
         ; assert: if ECB is "disconnected", it is really connected
         ; to the idle task, which ALWAYS has TCB:PARAMETER<>0!
         lda   tcb:parameter,x         wakeup code set ?
         beq   RT:SIEW                 b/ no, so set wakeup code
         ldx   itempx                  get pointer to ECB again
         else  (m6809!m6811)
         ldy   evn:tcb,x               find TCB connected to ECB
         ; assert: if ECB is "disconnected", it is really connected
         ; to the idle task, which ALWAYS has TCB:PARAMETER<>0!
         lda   tcb:parameter,y         wakeup code set ?
         beq   RT:SIEW                 b/ no, so set wakeup code
         fin
         inc   evn:count,x             bump event counter
         rts
         page
RT:SIEW ; TCB is still armed for wakeup
         if    m6800!m6801
         ldx   itempx                  fetch pointer to ECB again
         ldd   evn:parameter,x         fetch task wake-up parameter
         ; Assert: the upper 8 bits of evn:parameter are non-zero.
         ldx   evn:tcb,x               note wakeup reason in TCB
         std   tcb:parameter,x
         ldd   tcb:nexttcb,x           is TCB in Ready or Semaphore Queue?
         bned  RT:SIER                 b/ yes, all done!
RT:ITIQ ; insert task at (X) into Ready task list in priority order
; Interrupts are disabled
; callable from reset code and various points inside SDOS/RT
         stx   itempx                  save pointer to task to insert
         lda   tcb:priority,x          fetch task priority
         ldx   #RT:TCBQ-tcb:nexttcb    pointer to "previous" task
RT:ITIL ; find insert point for new task in queue in priority order
         stx   itempd                  save pointer to previous task
         ldx   tcb:nexttcb,x           find next task in list
         cmpa  tcb:priority,x          insert new task here ?
         bls   RT:ITIL                 b/ no, keep looking
         ; Now insert new TCB into queue before TCB at (X)
         ldx   itempd                  get pointer to TCB before insert place
         ldd   tcb:nexttcb,x           get pointer to TCB after insert place
         ldx   itempx                  get pointer to TCB to insert
         std   tcb:nexttcb,x           make new TCB point to TCB after insert place
         ldx   itempd                  get pointer to TCB before insert place
         ldd   itempx                  get pointer to TCB to insert
         std   tcb:nexttcb,x           make TCB before insert place point to new TCB
         else  (m6809!m6811)
         ldd   evn:parameter,x         fetch task wake-up parameter
         ; Assert: the upper 8 bits of evn:parameter are non-zero.
         std   tcb:parameter,y         note wakeup reason in TCB
         ldd   tcb:nexttcb,y           is TCB in Ready or Semaphore Queue?
         bned  RT:SIER                 b/ yes, all done!
         tfr   y,x                     make (X) point to TCB
RT:ITIQ ; insert task at (X) into Ready task list in priority order
; Interrupts are disabled
; callable from reset code and various points inside SDOS/RT
         stx   itempx                  save pointer to task to insert
         lda   tcb:priority,x          fetch task priority
         ldx   #RT:TCBQ-tcb:nexttcb,x  pointer to "previous" task
RT:ITIL ; find insert point for new task in queue in priority order
         stx   itempd                  save pointer to previous task
         ldx   tcb:nexttcb,x           find next task in list
         cmpa  tcb:priority,x          insert new task here ?
         bls   RT:ITIL                 b/ no, keep looking
         ; Now insert new TCB into queue before TCB at (X)
         ldx   itempd                  get pointer to TCB before insert place
         ldd   tcb:nexttcb,x           get pointer to TCB after insert place
         ldx   itempx                  get pointer to TCB to insert
         std   tcb:nexttcb,x           make new TCB point to TCB after insert place
         ldx   itempd                  get pointer to TCB before insert place
         ldd   itempx                  get pointer to TCB to insert
         std   tcb:nexttcb,x           make TCB before insert place point to new TCB
         fin
; RT:IUSP checks that RT:CTCB still matches RT:TCBQ, and, switches task if unequal.
RT:SIER  rts                           Done.
         page
;
;        Interrupt Poll Chains
;
;        SDOS/RT supports multiple (and therefore vectored)
;        interrupt poll chains.  Each poll chain consists of
;        a set of of "poll" blocks, which has the following structure:
;
;              fcb  priority           IPB:priority: device priority; 255 is high
;        NotThisDevice ; exit from this poll block
;              jmp                     IPB:jmp: Jumps to next poll block
;              fdb  nextpollblock      IPB:nextIPB: allows poll blocks anywhere in memory
;        ThisPollBlock ; IPB:entry: entry point to determine if device wants interrupt
;              ...check for device interrupt request...
;              b--  NotThisDevice      b/ this device doesn't want interrupt
;              if   DriverEnablesInterrupts
;              ...remove cause of interrupt...
;              inc  DeviceInterrupts   (initialized to -1)
;              bne  Bug                b/ recursive interrupt, must be bug!
;              lsr  RT:ISSF            stack already switched ?
;              bcc  StackAlreadySwitched b/ yes
;              sts  RT:ISSP            no, save task stack pointer
;              lds  #RT:IISP           switch to using (Initial) Interrupt Stack
;        StackAlreadySwitched
;              cli                     allow other device interrupts
;              else  (DriverDoesNotEnableInterrupts)
;        * there are 8 bytes on stack usable by driver
;              fin
;              ...service interrupt (use RT$SIE to tell task about event!)
;              if    DriverEnablesInterrupts
;              sei                     lock out interrupts
;              dec   DeviceInterrupts  allow another interrupt w/o complaint
;              else  (DriverDoesNotEnableInterrupts)
;              fin
;              rti                     see ingenious stack unswitch: RT:IUSP
;
;        DeviceInterrupts ; counts # interrupts (-1) being serviced by device
;              fcb  -1                 for recursive interrupt prevention

; Each Interrupt Poll chain has a head and a tail.
; The head is a "dummy" Interrupt Poll block with IPB:priority of 255.
; This ensures that all IPBS for real devices are inserted AFTER the head IPB.
; (IPBs for real devices have priorities in range 1..254)
; The interrupt hardware handled by that poll chain MUST ALWAYS transfer
; control to IPB:JMPOPCODE, as SDOSRT changes IPB:NEXTIPB dynamically.
;
; The tail is an IPB with IPB:priority of 0. This ensures that all IPBs
; for real devices are inserted before the tail IPB.  The tail IPB
; contains code to increment a "Bad Interrupt Count" for that interrupt
; vector, and then to simply exit the interrupt poll code with an RTI,
; under the presumption that the interrupt cause came and went before
; the poll chain could detect it (ACIAs can actually take away their
; interrupt of CTS suddenly drops!).
         page
; SDOSRT supplies a standard "low priority" interrupt poll chain head/tail
; for those systems that use only a single interrupt poll chain for relatively
; slow devices. This poll chain switches stacks on entry/exit, as overhead is
; assumed to be negligable.  For interrupts which require faster performance,
; the I/O package implementer can supply his own interrupt poll chain.

; SDOSRT Standard Interrupt Poll chains
;
RT:PollChainInit ; code to initialize poll chain logic
RT:PollChainInitOffset equ RT:PollChainInit-RT:HighPriorityIntPollChainHead
;RT:HighPriorityIntPollChainHead ; head of high priority interrupt poll chain
         fcb   255                     IPB:priority; high priority --> this block stays 1st
                                       ; initially, no high priority device
         jmp   RT:HighPriorityIntPollChainEnd+IPB:entry IPB:jmpnextipb
;RT:HighPriorityInt ; come here via lowest priority vector
; (for non-vectored systems, this is usually $FFF8)
         if    RTTNZP                  ; some task has Z register non-zero
         if    m6809
         clra                          ; make Z register zero...
         tfr   a,dpr                   ; so that references to RT:IPAD work
         else  m6811
? ; don't know how to accomplish this
         fin
         fin
         jmp   RT:HighPriorityIntPollChainHead+IPB:jmpnextipb

;RT:LowPriorityIntPollChainHead ; head IPB of low priority interrupt poll chain
         fcb   255                     high priority --> this block stays 1st
                                       ; initially, no high priority device
         jmp   RT:LowPriorityIntPollChainEnd+IPB:entry jmp to next IPB
;RT:LowPriorityIntChain ; head of low priority interrupt chain
;        come here if no high priority devices need service
;        perform stack switch now as convenience for low priority routines
         lsr   RT:ISSF                 stack already switched ?
         bcc   RT:LowPriorityIntPollChainHead+IPB:jmpnextipb+RT:PollChainInitOffset
         sts   RT:ISTK                 save task stack pointer
         lds   #RT:IISP                use Interrupt Stack from here on
         jmp   RT:LowPriorityIntPollChainHead+IPB:jmpnextipb
RT:PollChainInitEnd equ *

RT:HighPriorityIntPollChainEnd ; tail of high priority interrupt poll chain
         fcb   0                       IPB:priority; low priority --> this block stays at end of chain
         jmp   *                       control never comes here!
RT:NoHighPriorityIntDevice
         jmp   RT:LowPriorityIntPollChainHead+IPB:entry

RT:LowPriorityIntPollChainEnd ; tail IPB of low priority interrupt poll chain
         fcb   0                       lowest priority (don't use 0!)
         jmp   *                       not used
RT:NoLowPriorityIntDevice ; come here if no device claims int
;        interrupts are still disabled when we get here
         ldx   RT:UnknownIntCounter
         inx
         stx   RT:UnknownIntCounter
RT:LowPriorityIntReturn ; come here to do RTI from low priority routine
         rti                           this eventually goes to RT:ISCHD
         page
RT:ATPC ; Add To Poll Chain: add a new device dynamically to interrupt poll chain
;        (X) points to device interrupt poll block
;        (D) points to head IPB of poll chain desired
;        called only by task level routines
         intds                         lock out other tasks and interrupts
         std   itempx                  remember pointer to head of IPB chain
         ldaa  IPB:priority,x          get priority of current poll block
         stx   itempx+2                save it
         ldx   itempx
RT:ATPCL ; find place where new IPB: goes
         stx   itempx                  save this IPB as previous IPB
         ldx   IPB:nextipb,x           find next IPB
         cmpa  IPB:priority,x          insert new IPB before next IPB?
         bls   RT:ATPCL                b/ new IPB belongs further down chain
         stx   itempx+4                remember next IPB
         ldd   itempx+2                fetch pointer to new IPB
         ldx   itempx                  fetch pointer to previous IPB
         std   IPB:nextipb,x           make previous IPB point to new IPB
         ldx   itempx+2                get pointer to new IPB
         ldd   itempx+4                get pointer to next IPB
         std   IPB:nextipb,x           insert new IPB after next IPB
         inten                         allow the world again
         rts

RT:RFPC ; Remove IPB: specified by (D) from poll chain specified by (X)
         intds                         lock out other tasks and interrupts
RT:RFPC1 ; loop to locate IPB: in poll chain
         cmpd  IPB:nextipb,x           is it the next IPB in the chain ?
         beq   RT:RFPC2                b/ yes, go rip it out!
         ldx   IPB:nextipb,x           no, find next IPB in list
         tst   IPB:priority,x          hit end of poll chain without finding it?
         bne   RT:RFPC1                b/ no, see if this is place to remove it
         inten                         allow interrupts again
         jsr   RT:SErr                 Signal Error...
         #err:nosuchipb                "IPB is not present in poll block chain"

RT:RFPC2 ; (X) points to IPB with points to IPB at (D)
         stx   itempx                  save pointer to IPB preceding one at (D)
         ldx   IPB:nextipb,x           (same as TDX but works for all machines)
         ldd   IPB:nextipb,x           find IPB following one to remove
         ldx   itempx                  get pointer to IPB before one to remove
         std   IPB:nextipb,x           remove specified IPB from poll chain
         inten                         allow interrupts again
         rts                           and exit
         page
RT:DummyTOBTimedOut
         swi

RT:ITOB ; Initialize timeout block at (X) with routine (D)
; Preserves (X), (D)
; callable from task level or reset code
         std   TOB:Routine,x           ; set where to go when TOB expires
         std   TOB:Parameter,x         ; set parameter value into TOB
         stx   TOB:qflink,x            ; link TOB to itself...
         stx   TOB:qblink,x            ; marking it as "not in queue"
         rts                           ; done!
         page
RT:RTOB ; remove timeout block whose address is in (X) from queue
;        Exits with (X) pointing to removed block, other registers destroyed
;        Uses no stack slots (safe to use from fast interrupt routine)
;        Due to timing splinters, we might be attempting to extract
;        a timed-out block from the timer queue
;        sei                           ; interrupts are disabled on entry
         if    m6800!m6801
         stx   itempx                  ; save pointer to TOB temporarily
         ldd   TOB:qflink,x            ; find next TOB in queue
         ldx   TOB:qblink,x            ; make previous TOB point...
         std   TOB:qflink,x            ; forward to next TOB
         ldx   itempx                  ; fetch pointer to TOB again
         ldd   TOB:qblink,x            ; find previous TOB in queue
         ldx   TOB:qflink,x            ; make next TOB point...
         std   TOB:qblink,x            ; back to previous TOB
         ldx   itempx                  ; get pointer to TOB to be removed
         lda   TOB:DelayDelta,x        ; is this TOB an expired TOB?
         beq   RT:RTOBDone             ; b/ yes, don't update next delay
         ldb   TOB:DelayDelta+1,x      ; no, fetch delay from TOB to be removed
         ldx   TOB:qflink,x            ; find former next TOB in list
         addd  TOB:DelayDelta,x        ; add deleted delay...
         std   TOB:DelayDelta,x        ; to delay in next TOB
RT:RTOBDone ; extraction of timeout block is complete
         ldx   itempx                  ; find TOB address again
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         elseif m6811
         ldy   TOB:qblink,x            ; find previous TOB in queue
         ldd   TOB:qflink,x            ; find next TOB in queue
         std   TOB:qflink,y            ; make previous TOB point forward to next
         ldy   TOB:qflink,x            ; find next TOB so we can update it
         ldd   TOB:qblink,x            ; fetch pointer to previous TOB in queue
         std   TOB:qflink,x            ; make previous TOB point to next
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         lda   TOB:DelayDelta,x        ; is this TOB an expired TOB?
         beq   RT:RTOBDone             ; b/ yes, don't update next delay
         ldb   TOB:DelayDelta+1,x      ; no, fetch delay from TOB to be removed
         addd  TOB:DelayDelta,y        ; add deleted delay...
         std   TOB:DelayDelta,y        ; to delay in next TOB
RT:RTOBDone ; extraction of timeout block is complete
         else  (m6809)
         ldu   TOB:qflink,x            ; find next TOB in queue
         ldy   TOB:qblink,x            ; find previous TOB in queue
         stu   TOB:qflink,y            ; make previous TOB point to next
         sty   TOB:qblink,u            ; make next TOB point to previous
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         lda   TOB:DelayDelta,x        ; is this TOB an expired TOB?
         beq   RT:RTOBDone             ; b/ yes, don't update next delay
         ldb   TOB:DelayDelta+1,x      ; fetch delay from TOB to be removed
         addd  TOB:DelayDelta,u        ; no, add deleted delay...
         std   TOB:DelayDelta,u        ; to delay in next TOB
RT:RTOBDone ; extraction of timeout block is complete
         fin
;        ldd   #-$ff00                 ; ensure dummy TOB never times out
;        std   RT:DummyTOB+TOB:DelayDelta ; setting max delay delta
         lda   #1                      ; ensure dummy TOB never times out
         sta   RT:DummyTOB+TOB:DelayDelta ; (sets MSB of delay)
         rts
         page
RT:UTOB ; Update timeout block at (X) to new delay (D)
;        Note: zero delay takes some nonzero amount of time to discover
;        This routine destroys all registers
;        Uses 4 stack bytes including return address
;        (safe to use from interrupt stack routine)
;        sei                           ; must be entered with interrupts disabled
         cpx   TOB:qflink,x            ; is TOB in timer queue ?
         beq   RT:STOB                 ; b/ no, go add to timer queue
         std   itempd                  ; yes: save desired delay
         bsr   RT:RTOB                 ; then remove timeout from queue
         ldd   itempd                  ; restore desired delay and do new insert
RT:STOB ; Insert timeout block at (X) with delay (D)
;        Note: zero delay takes some nonzero amount of time to discover
;        This routine destroys all registers
;        Uses ? stack bytes including return address
;        (safe to use from interrupt stack routine)
;        This entry point not available to I/O package designers because
;        SDOSRT:UTOB is so cheap and this one is dangerous if misused
;        sei                           ; must be entered with interrupts disabled
         std   itempd                  ; save desired delay
         stx   itempx                  ; remember TOB address
         jsr   IO:RET                  ; in milliseconds in (B)
         ldx   RT:DummyTOB+TOB:qflink  ; find first TOB in queue
iFirstTOBbeforeInsert equ RT:IPad+4    ; lifetime = incarnation of RT:STOB
         stx   iFirstTOBbeforeInsert   ; remember which TOB is first in queue
; Here we must update DelayDeltas of TOBs at front of timer queue.
; If this TOB has an expired delay delta (upper byte is zero)
; then we must add (B) to its DelayDelta (it cannot exceed the value
; 255 because RT:TICK is called frequently enough to prevent this),
; and then we must update the TOB following it with the same value of (B).
; If this TOB has an un-expired delay delta, then we must add (B)
; to its DelayDelta; if the sum is still negative, then this TOB is
; the first in the logical queue to which we wish to add the new TOB,
; else the sum went positive (range 0..255), and we must update
; the next TOB with the resulting sum.
; The following code is designed to be as fast as possible...
; under all circumstances due to the tight tolerances of 1ms delays
         tfr   b,a                     ; save ticks in case expired delay
         addb  TOB:DelayDelta+1,x      ; adjust TOB Delay Delta
         stab  TOB:DelayDelta+1,x
         tst   TOB:DelayDelta,x        ; is this an expired TOB ? (preserves carry)
         bne   RT:STOBUpdateUnexpiredTOB ; b/ no, finish updating unexpired TOB
;        bcc   RT:STOBUpdateExpiredTOB0 ; b/ no carry to propogate
;        inc   TOB:DelayDelta,x        ; propogate carry to upper half
;RT:STOBUpdateExpiredTOB0 ; TOB:DelayDelta updated properly
; Note: we don't propogate carry to ensure that expired TOB stays expired
         page
RT:STOBUpdateExpiredTOBsLoop ; update DelayDelta of TOB at (X) with (A)
         ldx   TOB:qflink,x            ; find next TOB in timer queue
         tfr   a,b                     ; save ticks in case expired delay
         addb  TOB:DelayDelta+1,x      ; adjust TOB Delay Delta
         stab  TOB:DelayDelta+1,x
         tst   TOB:DelayDelta,x        ; is this an expired TOB ? (preserves carry)
         beq   RT:STOBUpdateExpiredTOBsLoop ; b/ yes, leave upper byte DelayDelta=0
RT:STOBUpdateUnexpiredTOB ; b/ no, finish updating unexpired TOB
         bcc   RT:STOBFindInsertPoint  ; b/ no carry to propogate, all TOBs updated
         inc   TOB:DelayDelta,x        ; propogate carry to upper half
         ; Now, if upper byte of delay delta = 0 then delay is complete
         ; This allows a delay delta overrun of up to 255 milliseconds
         ; Delay can be complete if clock has run past its time
         ; to request an interrupt, but has not been able to cause
         ; an interrupt because CPU is operating with interrupts disabled
         bne   RT:STOBFindInsertPoint  ; all TOBs are now up-to-date
RT:STOBUpdateNextUnexpiredTOB ; This TOB has expired, update next TOB with difference
; Note: Next TOB could have zero delay and be unexpired (sigh!)
         ldx   TOB:qflink,x            ; find next TOB in timer queue
         clra                          ; sign extend unused ticks to 16 bits
         if    m6800
         addb  TOB:DelayDelta+1,x      ; adjust TOB Delay Delta
         stab  TOB:DelayDelta+1,x      ; by adding unused ticks
         adca  TOB:DelayDelta,x        ; update upper 8 bits
         staa  TOB:DelayDelta,x        ; and store back
         else  (m6801!m6809!m6811)
         addd  TOB:DelayDelta,x        ; adjust TOB Delay Delta
         std   TOB:DelayDelta,x        ; by adding unused ticks
         tsta                          ; did delay expire ?
         fin
         beq   RT:STOBUpdateNextUnexpiredTOB ; b/ delay expired, continue updating
RT:STOBFindInsertPoint ; all TOBs starting with TOB at (X)...
; contain relative delays from NOW
; Furthermore, we assert the first TOB has non-zero delay
         ldd   itempd                  ; recover delay desired
         addd  TOB:DelayDelta,x        ; add next TOB delay delta
         bcc   RT:STOBFoundInsertPoint ; b/ insert point found at (X)
RT:STOBFindInsertPointLoop ; scan down TOBs hunting for insert point
; assert: can't run off end of timer queue because of dummy TOB
         ldx   TOB:qflink,x            ; find next TOB in queue
         std   itempd                  ; save delay if this is insert point
         tst   TOB:DelayDelta,x        ; is next TOB delay delta = zero ?
         beq   RT:STOBFindInsertPointLoop ; b/ yes, skip over this TOB
         addd  TOB:DelayDelta,x        ; add next TOB delay delta
         bcs   RT:STOBFindInsertPointLoop ; b/ remaining delay >= 0
         page
RT:STOBFoundInsertPoint ; insert point found at (X)
; Insert new TOB into timer queue preceding TOB at (X)
         std   TOB:DelayDelta,x        ; store revised delay delta
         if    m6809
         ldu   TOB:qblink,x            ; find preceding TOB in list
         ldy   itempx                  ; fetch pointer to TOB to insert
         ldd   #0                      ; compute negative of delay delta needed
         subd  itempd                  ; (note: this value can be zero!)
         std   TOB:DelayDelta,y        ; store delay delta for this TOB
         stu   TOB:qblink,y            ; set back link to point to previous TOB
         stx   TOB:qflink,y            ; set forward link to point to next TOB
         sty   TOB:qflink,u            ; set prev TOB's Flink to TOB to insert
         sty   TOB:qblink,x            ; set next TOB's Blink to TOB to insert
         elseif m6811
         ldy   itempx                  ; fetch pointer to TOB to insert
         ldd   #0                      ; compute negative of delay delta needed
         subd  itempd                  ; (note: this value can be zero)
         std   TOB:DelayDelta,y        ; store delay delta for this TOB
?        stx   TOB:qflink,x            ; make insert TOB point forward to next TOB
         ldx   TOB:qblink,x            ; find previous TOB
         sty   TOB:qflink,x            ; make previous TOB point to inserted TOB
         stx   TOB:qblink,y            ; make inserted TOB point back to previous TOB
         ldx   TOB:qflink,y            ; find next TOB again
         sty   TOB:qblink,x            ; make next TOB pont back to inserted TOB
         else  (m6800!m6801)
         ldd   #0                      ; compute negative of delay delta needed
         subd  itempd                  ; (note: this value can be zero!)
         stx   itempd                  ; save pointer to next TOB
         ldx   itempx                  ; get pointer to TOB to insert
         std   TOB:DelayDelta,x        ; store delay delta for this TOB
         ldd   itempd                  ; get pointer to next TOB
         std   TOB:qflink,x            ; make inserted TOB point forward to next TOB
         ldx   TOB:qflink,x            ; find next TOB
         ldx   TOB:qblink,x            ; find previous TOB
         ldd   itempx                  ; fetch location of TOB to insert
         std   TOB:qflink,x            ; make previous TOB point forward to inserted TOB
         stx   itempd                  ; save pointer to previous TOB
         ldx   itempx                  ; find TOB to be inserted
         ldd   itempd                  ; get address of previous TOB
         std   TOB:qblink,x            ; make new TOB point back to previous TOB
         ldd   itempx                  ; find TOB to be inserted
         ldx   TOB:qflink,x            ; find next TOB
         std   TOB:qblink,x            ; make next TOB point back to new TOB
         fin
         ldaa  #1                      ; ensure that dummy TOB...
         staa  RT:DummyTOB+TOB:DelayDelta ; stays last in time queue
;        jmp   RT:SelectNewClockDelay  ; go adjust the clock
RT:SelectNewClockDelay ; entry point from RT:STOB
         ldx   RT:DummyTOB+TOB:qflink  ; determine which TOB is first in queue
         cpx   iFirstTOBbeforeInsert   ; same as before we inserted TOB ?
         beq   RT:SetClockDelayRTS     ; b/ yes, don't need to adjust clock hardware
RT:SetClockDelayTo1stTOB ; set next clock delay according to 1st TOB in list
         ldaa  RT:HandlingExpiredTOBFlag ; is RT:Tick running ?
         ; This situation can only occur when RT:Tick has gained control,
         ; timed out some block and simulated an interrupt to a driver,
         ; and the driver has added/deleted some timeout block to/from the
         ; timer list.  After returning (via BNE below) to the driver,
         ; the driver will eventually return to RT:Tick, which will eventually
         ; finish processing the timer list, and will finally call
         ; RT:SelectNewClockDelay.  Since it will pass control here
         ; later, there is no point in going thru the work to set the
         ; clock delay now.  Whew.
         bne   RT:SetClockDelayRTS     ; b/ yes, no point in setting delay yet
         ldx   RT:DummyTOB+TOB:qflink  ; determine which TOB is first in queue
         ; We get here if there is no way to avoid setting clock delay.
         ; Since RT:Tick has done its thing, we are assured that
         ; RT:TSTOB (timeshare TOB) is in the timer queue.  Since
         ; RT:TSTOB has a delay of less than 256 milliseconds, we can
         ; limit our arithmetic on next desired delay to one-byte
         ; computations, make the code here and in RT:SCID considerably
         ; simpler, thank heavens.
         ; Assert: no more than TimeSlice ticks can have passed.
         ; We outlaw VERY long routines with interrupts disabled to ensure this.
         ldaa  TOB:DelayDelta,x        ; fetch upper 8 bits of delay
         ; 0 --> clock wants interrupt --> interrupt will call RT:Tick,
         ; which will handle the timed-out block
         beq   RT:SetClockDelayRTS     ; b/ delay has expired, clock must want interrupt
         ldab  TOB:DelayDelta+1,x      ; fetch lower 8 bits of delay
         ; Now (D) is Negative of delay desired.
         ; 0<=abs(B)<128 because delay in RT$TSTOB is < 128 ticks,
         ; and RT:TSTOB is ALWAYS in the timer queue.
;
; To make a good clock handler, the clock hardware needs to tick at a fixed
; rate, and have a comparator register that the scheduler can load.
; The clock must interrupt the system when the comparator says the desired
; time has passed.  The other style of clock (i.e., a Z80 CTC)
; can only be programmed to tell one when an interval is up; the time
; to program the chip is lost and so an error accumulates over time.
;        jsr   IO:SCID                 ; set clock interval delay
;        ret
         jmp   IO:SCID                 ; set clock interval delay

RT:SetClockDelayRTS  ;  don't need to set clock delay
         rts
         page
RT:Tick ; Handle clock ticks, update Timeout blocks
; Called by clock interrupt routine when interval specified...
; by last call to RT:SCID has elapsed.
; Entered with (B) holding actual elapsed time in milliseconds
; Stacks must already be switched, interrupts MUST be disabled.
; Caller must live in same space as SDOSRT.
; Note: no further entries to this point are legal until RT:SCID has
; been called again.  This rule prevents this routine from damaging
; the Timer block queue by diddling with it a second time before we are
; through diddling with it the first time.
         tfr   b,a                     ; save elapsed ticks
         adda  RT:Clk+3                ; bump elapsed time
         staa  RT:Clk+3
         bcc   RT:ProcessTicks         ; b/ no carry to propogate
         inc   RT:Clk+2                ; propogate carry
         bne   RT:ProcessTicks         ; b/ no carry to propogate
         inc   RT:Clk+1                ; propogate carry
         bne   RT:ProcessTicks         ; b/ no carry to propogate
         inc   RT:Clk+0                ; propogate carry
         page
RT:ProcessTicks ; process ticks in (B)
; Clock ticks are handled in a two phase process.
; First, clock ticks are added to the delay deltas to determine which,
; if any, timeout blocks have timed out.
; This logically divides the TOB queue into two parts:
; a list of timed-out blocks, and a list of not-yet-timed-out blocks.
; Then we process the list of timed-out blocks, issuing simulated
; interrupts for each one, as per RT:STOB specification.
;
; Timed-out blocks (ones whose DelayDelta has MSB=0) may already be in
; the list when we enter.  We handle these specially.
; All the timed-out blocks must be on the front of the list.
;
; The may be TOBs IN the list with DelayDelat=0; such TOBs merely
; represents a delay delta from its predecessor of 0 ticks, and
; does NOT mean a timed-out block.  Such blocks cannot be at
; the front of the list.
;
; The "obvious" procedure of processing the time-out blocks one at
; a time doesn't work because when a Timeout interrupt is sent to
; a driver, the driver may try to remove or add a new timeout block;
; this clearly won't work if the driver tries to remove one which
; has timed out on this clock tick, nor will new insertions work
; correctly until all the timed out blocks have been removed because
; of the unaccounted-for time held by this routine (i.e., you don't get
; the delay you expect, you get a much shorter delay).
;
; A fundamental assumption is that the number of timeout blocks in the
; queue at any instant will not be large, and therefore, the number
; of timed-out blocks will never be large.  This assumption allows
; us to implement timeout block handling routines with interrupts disabled.
; Our other choice would be to fire up a task, which would make the
; overhead so high that millsecond resolution timers would be a joke.
;
;        sei                           ; prevent timing splinters
         ; Now, process the first TOB in the list
         ldx   RT:DummyTOB+TOB:qflink  ; get pointer to list of timeout blocks
         ; assert: list is never empty because of Dummy TOB at end of queue
         tst   TOB:DelayDelta,x        ; is this an already-expired TOB ?
         bne   RT:ProcessTicksNoExpiredTOBs ; b/ no (usually branches)
         page
RT:ProcessTicksExpiredTOBloop ; we are about to update an expired TOB
; We don't go thru here very often. Add ticks to each expired delay delta
         tfr   b,a                     ; copy ticks in (A)
         adda  TOB:DelayDelta+1,x      ; add unused ticks to delay delta
         sta   TOB:DelayDelta+1,x      ; update delay delta in expired TOB
;        bcc   RT:ProcessTicksExpiredTOB0 ; b/ no carry to propogate
;        inc   TOB:DelayDelta,x        ; once expired, always expired.
;RT:ProcessTicksExpiredTOB0 ; TOB:DelayDelta has been updated.
         ldx   TOB:qflink,x            ; find next TOB in queue
         tst   TOB:DelayDelta,x        ; is next TOB also already-expired TOB ?
         beq   RT:ProcessTicksExpiredTOBloop ; b/ yes, go handle
         ; (B) holds unused ticks to propogate to next (assert: unexpired) TOB.
         addb  TOB:DelayDelta+1,x      ; add unused ticks to delay delta
         stab  TOB:DelayDelta+1,x      ; update delay delta in TOB
         bcc   RT:ProcessTimedoutBlocks ; b/ no carry to propogate
         inc   TOB:DelayDelta,x        ; propogate carry to upper 8 bits
         bne   RT:ProcessTimedoutBlocks ; b/ this TOB not expired, done updating
RT:ProcessTicksExpiredTOBs ; we know that there are expired TOBs in list
; (B) holds number of ticks to add to next (assert: unexpired) TOB
; Note: TOB:DelayDelta here means "relative to previous TOB:DelayDelta"
; ..in particular, it does NOT mean "expired delay" (yet).
         ldx   TOB:qflink,x            ; find next TOB in queue
         addb  TOB:DelayDelta+1,x      ; add unused ticks to delay delta
         stab  TOB:DelayDelta+1,x      ; update delay delta in TOB
         bcs   RT:ProcessTicksExpiredTOBs1 ; b/ carry to propogate
         ; Assert: if TOB:DelayDelta was zero, control passes thru here
         lda   TOB:DelayDelta,x        ; did TOB have zero delta before update?
         bne   RT:ProcessTimedoutBlocks ; b/ no, done updating TOBs (usually branch)
         ; TOB:DelayDelta was zero. We added (B) to zero, leaving (B) unchanged
         ; This TOB just expired, we must now propogate ticks to next TOB.
         bra   RT:ProcessTicksExpiredTOBs ; this TOB just expired

RT:ProcessTicksExpiredTOBs1 ; carry to propogate
; TOB:DelayDelta was NOT zero before update if we get here
         inc   TOB:DelayDelta,x        ; propogate carry to upper 8 bits
         beq   RT:ProcessTicksExpiredTOBs ; b/ this TOB just expired
         page
RT:ProcessTimedoutBlocks ; now simulate interrupt on each expired TOB
         inc   RT:HandlingExpiredTOBFlag ; remember we are handling expired TOBs
; Set up to process at least one expired TOB
; Switch stacks here so we only do it once for all expired TOBs processed
;        lsr   RT:ISSF                 ; stacks already switched ?
;        bcc   RT:ProcessTimedoutBlocks1 ; b/ stacks already switched
;        sts   RT:ISTK                 ; save current stack pointer
;        lds   #RT:IISP                ; use interrupt stack from here on
;RT:ProcessTimedoutBlocks1 ; stacks now switched
;        cli                           ; RT:SInt below accomplishes this
         ldx   RT:DummyTOB+TOB:qflink  ; get pointer to list of timeout blocks
;        ldab  TOB:DelayDelta+1,x      ; fetch overshoot on timeout
RT:SIntTOBloop ; loop here, doing an RT:SInt for each timed out block
; We first remove this timeout block from list in case interrupt routine
; insists on re-inserting it back into the timeout queue.
; Note that if, when we simulate an interrupt to a driver, it does an
; RT:RTOB on another timeout block block that is in the timeout list,
; RT:RTOB will remove it and we won't see it here as a result.  Isn't this
; whole stunt incredible ?
         if    m6800!m6801
         stx   itempx                  ; save pointer to TOB temporarily
         ldd   TOB:qflink,x            ; find next TOB in queue
         ldx   TOB:qblink,x            ; make previous TOB point...
         std   TOB:qflink,x            ; forward to next TOB
         ldx   itempx                  ; fetch pointer to TOB again
         ldd   TOB:qblink,x            ; find previous TOB in queue
         ldx   TOB:qflink,x            ; make next TOB point...
         std   TOB:qblink,x            ; back to previous TOB
         ldx   itempx                  ; find TOB address again
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         elseif m6811
         ldy   TOB:qblink,x            ; find previous TOB in queue
         ldd   TOB:qflink,x            ; find nxt TOB in queue
         std   TOB:qflink,x            ; make previous TOB point forward to next
         ldy   TOB:qflink,x            ; find next TOB so we can update it
         ldd   TOB:qblink,x            ; fetch ponter to previous TOB in queue
         std   TOB:qflink,y            ; make previous TOB point to next
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         else  (m6809)
         ldu   TOB:qflink,x            ; find next TOB in queue
         ldy   TOB:qblink,x            ; find previous TOB in queue
         stu   TOB:qflink,y            ; make previous TOB point to next
         sty   TOB:qblink,u            ; make next TOB point to previous
         stx   TOB:qflink,x            ; make this TOB point to self
         stx   TOB:qblink,x            ; to make additional removals harmless
         fin
         bsr   RT:SIntTOB              ; simulate interrupt to TOB
;        inten                         ; assert: interrupts enabled on return
; Since control is returned via RTI, hardware has chance to squeeze
; in an interrupt request before we continue.
; We assert that it CANNOT squeeze in a clock interrupt because this routine
; is part of RT:Tick, which is called by the clock interrupt handler,
; which means the clock has not yet been re-armed to cause another interrupt.
         intds                         ; lock out hardware interrupts
         ldx   RT:DummyTOB+TOB:qflink  ; get pointer to list of timeout blocks
;        ldab  TOB:DelayDelta+1,x      ; fetch overshoot on timeout
         ldaa  TOB:DelayDelta,x        ; is this an expired TOB ?
         beq   RT:SintTOBloop          ; b/ yes, go fake an interrupt to it
         clr   RT:HandlingExpiredTOBFlag ; remember RT:Tick is not running
         ldaa  #1                      ; make sure dummy TOB stays last in queue
         staa  RT:DummyTOB+TOB:DelayDelta+1
         ; done handling expired TOBs, restore interrupted context
         bra   RT:ProcessTimedoutBlocksDone ; go set clock delay
         page
RT:SIntTOB ; Simulate Interrupt to TOB:Routine at TOB(X)
         if    m6809
         pshs  x,y,u,d,dp              ; push dummy context block
         ldaa  #$80                    ; "Entire context+CLI"
         psha
         ldu   TOB:Routine,x           ; fetch pointer to routine to trigger
         ldd   TOB:Parameter,x         ; fetch parameter
         ldx   TOB:Parameter,x         ; ...to two convenient places
;        intds                         ; lock out the world
         jmp   ,u                      ; call TOB:Routine
         else  (m6800!m6801!m6811)
         leas  -(cb:size-1),s          ; form dummy context block
         clra                          ; form "interrupt enabled" Condition Codes
         psha
         ldd   TOB:Routine,x           ; fetch pointer to routine to call
         pshd                          ; save on stack so RTS "calls" it
         ldd   TOB:Parameter,x         ; fetch parameter
         ldx   TOB:Parameter,x         ; ...to two convenient places
;        sei
         rts
         fin

RT:ProcessTicksNoExpiredTOBs ; there are not expired TOBs in list, yet
         addb  TOB:DelayDelta+1,x      ; add unused ticks to delay delta
         stab  TOB:DelayDelta+1,x      ; update delay delta in TOB
         ; bcc below is generally taken, no timeout blocks expired
         bcc   RT:ProcessTimedoutBlocksDone ; b/ no carry to propogate
         inc   TOB:DelayDelta,x        ; propogate carry
         beq   RT:ProcessTicksExpiredTOBs ; b/ this TOB just expired
RT:ProcessTimedOutBlocksDone
         jmp   RT:SetClockDelayTo1stTOB  ; set clock timer delay, return to caller
;        rts                             ; and return from interrupt
         page
RT:STrp ; Set Error Handler location
;        Entered with return address pointing to error recovery routine
;        (X) points to code to be executed with error trap set
;        Pushes Error Trap context, which consists of:
;           Error Trap routine address,
;           and previous Error Trap Recovery Stack pointer
;        Must be in common space for fast access by all tasks
;        Destroys all registers
         std   RT:SPad                 preserve (D) to make caller's life nice
         ldd   RT:EStk                 save old error recovery stack pointer
         pshd                          save on top of stack
         sts   RT:EStk                 record new error recovery stack pointer
         ldd   RT:SPad                 restore (D) to entry value
         jmp   0,x                     go execute routine with error trap set

RT:RTrp ; Remove (pop) error trap context on top of stack
;        Must be in common space for fast access
;        (S) must be equal to RT:EStk when control passes to here!
         std   RT:SPad                 preserve (D) to make caller's life nice
         stx   RT:SPad+2               preserve (X) to make caller's life nice
         puld                          pull return address from stack
         pulx                          fetch previous error recovery stack pointer
         stx   RT:EStk                 and set error recovery stack pointer back
         leas  2,s                     throw away Error Trap routine address
         pshd                          save return address back on stack
         ldx   RT:SPad+2               restore (X) to entry value
         ldd   RT:SPad                 restore (D) to entry value
         rts                           exit back to caller

RT:GErr ; Generate error code specified inline after call
         pulx                          fetch return address
         ldx   0,x                     fetch error code
;        bra   RT:SErr                 signal the specified error

RT:SErr ; Signal error code specified by (X), entered via JMP
;        Must be in common space for fast access
;        bra   RT:PErr                 propogate the error code

RT:PErr ; Propogate error code specified by (X), entered via JMP
;        Must be in common space for fast access
         lds   RT:EStk                 restore SP to recovery point value
         puld                          get SP of next error recovery point
         std   RT:EStk                 and save in case error is propogate
;        sec                           signal presence of error
         rts                           go to error recovery routine
         page
RT:IErr ; Signal in-line error to caller by unwinding calls
         pulx                          fetch error code
         stx   tempx                   and save it
RT:IErrL ; loop popping return addresses until BCC/BCS/LBCC/LBCS found
         leas  2,s                     pop return address
         ldx   0,s                     get next return address
         ldaa  0,x                     fetch opcode
         if    m6809                   ? what about m6811?
         cmpa  #$10                    pre-byte for LBxx?
         bne   RT:IErr1                b/ no, go test for BCC/BCS
         ldaa  1,x                     yes, fetch opcode byte after pre-byte
RT:IErr1 ; test (A) for BCC/BCS
         fin   m6809
         anda  #%11111110              mask off condition-inverting bit
         cmpa  #$24                    BCC ? (BCS mapped to BCC by preceding ANND)
         bne   RT:IErrl                b/ no, unwind the stack some more!
         ldx   tempx                   pick up error code
         sec                           tell caller that error occurred
         rts                           and exit
         page
         if    m6809
RT:BMOV ; Block move bytes from (X) to (Y) for (D) bytes
*       (X) = from address
*       (Y) = to address
*       (D) = count (0..65535)
*
*       On exit, (X) has old (X)+(D); (Y) has old (Y)+(D)
*       Assumes that Copy-to region does NOT overlap Copy-from region
*       or, if there is an overlap, that FROM >= TO.
*
        leau    d,x     compute "end of transfer address"
        leau    -16,u   to counter offsets used in MoveDownLoop
        ; = center of last block of 16 bytes to transfer
        stu     tempx   save for loop limit comparison
        bitb    #1      moving an even number of bytes ?
        beq     BlockMoveDown0         b/ yes
        ldaa    ,x+     take care of moving odd byte
        staa    ,y+
BlockMoveDown0 ; even number of bytes left to move
;
;  Following picture is state of affairs at BlockMoveUp0
;
;                !----------!
; ^   (X) -->    !          !
; |              !  first   !
; |              !  block   !   This block is EVEN (odd byte already done)
; count mod 32   !  copied  !   but usually NOT 32 bytes in size
; |              !          !
; |              !----------!
; v          A   !          !   (offset -16)
;                !          !
;                    ...        Multiple blocks of 32 bytes
;                !          !
;                !          !
;                !----------!
;                !          !   (offset -16)
;                !  final   !
;    (TEMPX) --> !  block   !   (offset 0)
;                !  copied  !   (Generally) Block of 32 bytes
;                !          !
;                !          !   (offset +14)
;                !----------!
;    (X)+(D) --> !          !
;                    ....
         page
;  Our intent is that each loop iteration copies 32 bytes, because
;  we can do this most efficiently using 5 bit index register offsets.
;  The first loop copies just enough so the rest of the iterations can
;  always copy 32 bytes. Each iteration advances (X) and (Y) by 32
;  (we must do it this way because FROM >= TO, or we may scramble the data
;  we are moving), so that the last loop iteration has (X)+16 pointing to
;  the end-of-transfer address.  The loop terminates when (X) points to
;  the middle of last block that needs data transferred. To do this, we
;  need to arrange things so that the point labelled A has offset -16
;  on the second iteration of the loop.  Setting (X)-16+(count mod 32)
;  accomplishes this nicely.
;
        andb    #%11110 take xfer count mod 32 (assert: count is even!)
        tfr     b,a     adjust source and destination pointers...
        adda    #-16    to point to middle of block of 32 bytes
        leax    a,x     add (count mod 32)-16
        leay    a,y
        eorb    #%11110 complement so 0 maps to 15
        aslb            shift to make multiple of 4
        ldu     #BlockMoveDown32+4 set up to jump into loop
        jmp     b,u     jmp to LDD instruction
; Note: block count of 2 takes us to LDD 14,x
;       block count which is multiple of 32 jmps to CMPX
;
; BytesToMove   Enter Loop at           Add To X,Y
;              BlockMoveDown32+...
;
;    32               0                     16
;    30               4                     14
;    28               8                     12
;                 .........
;     2              60                    -14
;     0              64                    -16
;
        page
BlockMoveDownLoop ; come here to move next block of 32 bytes
        leax     32,x   (4+1~) advance source pointer
        leay     32,y   (4+1~) advance destination pointer
BlockMoveDown32 ; move block of 32 bytes centered around (X)
        ldd     -16,x   (5+1~) fetch source pair
        std     -16,y   (5+1~) store destination pair
        ldd     -14,x   (5+1~) fetch source pair
        std     -14,y   (5+1~) store destination pair
        ldd     -12,x   (5+1~) fetch source pair
        std     -12,y   (5+1~) store destination pair
        ldd     -10,x   (5+1~) fetch source pair
        std     -10,y   (5+1~) store destination pair
        ldd      -8,x   (5+1~) fetch source pair
        std      -8,y   (5+1~) store destination pair
        ldd      -6,x   (5+1~) fetch source pair
        std      -6,y   (5+1~) store destination pair
        ldd      -4,x   (5+1~) fetch source pair
        std      -4,y   (5+1~) store destination pair
        ldd      -2,x   (5+1~) fetch source pair
        std      -2,y   (5+1~) store destination pair
        ldd       0,x   (5+0~) fetch source pair
        std       0,y   (5+0~) store destination pair
        ldd       2,x   (5+1~) fetch source pair
        std       2,y   (5+1~) store destination pair
        ldd       4,x   (5+1~) fetch source pair
        std       4,y   (5+1~) store destination pair
        ldd       6,x   (5+1~) fetch source pair
        std       6,y   (5+1~) store destination pair
        ldd       8,x   (5+1~) fetch source pair
        std       8,y   (5+1~) store destination pair
        ldd      10,x   (5+1~) fetch source pair
        std      10,y   (5+1~) store destination pair
        ldd      12,x   (5+1~) fetch source pair
        std      12,y   (5+1~) store destination pair
        ldd      14,x   (5+1~) fetch source pair
        std      14,y   (5+1~) store destination pair
        cmpx     tempx  (6~) check limit
        bne     BlockMoveDownLoop (3~) b/ limit not reached
;                       --------
;                       15*(6+6)+5+5+5+5+6+3 = 209~/32 bytes --> 6.53~/byte
        leax     16,x   Set (X) at exit to entry (X)+(D)
        leay     16,y   Set (Y) at exit to entry (Y)+(D)
        rts
        page
         elseif m6811
TOPOINTER EQU  RT:SPad+0               TARGET ADDRESS
FROMPOINTER EQU RT:SPad+2              SOURCE ADDRESS
LIMIT    EQU   RT:SPad+4               TRANSFER LIMIT ADDRESS
BLOCKMOVEX EQU RT:SPad+6               SCRATCH PAD LOCATIONS
BLOCKMOVE EQU *
*	BLOCKMOVEDOWN -- MOVE BLOCK AT (X) TO (Y) FOR (D) BYTES
*	ON EXIT, (X) IS OLD (X)+(D)
*		 (Y) IS OLD (Y)+(D)
*	COPIES LARGE BLOCKS AT ??uS. PER BYTE
*	ASSUMES COPY-TO REGION DOES NOT OVERLAP COPY-FROM REGION
*	OR THAT "FROM" >= "TO"
*
BLOCKMOVEDOWN	EQU	*
	STB	TOPOINTER	SAVE LOWER BYTE OF MOVE COUNT
	STX	FROMPOINTER	SAVE WHERE TO COPY FROM
	ADDD	FROMPOINTER	COMPUTE ADDRESS OF BYTE PAST END OF FROM RE
	STD	LIMIT	SAVE AS LIMIT ADDRESS
	LDD	DCBPOINTER	SAVE DCB POINTER
	PSHD
	LDB	TOPOINTER	FETCH LOWER BYTE OF MOVE COUNT
	BITB	#%00000001	GOING TO MOVE TO AN EVEN NUMBER OF BYTES ?
	BEQ	BLOCKMOVEDOWN2	B/ YES
	LDA	,X	FETCH ODD BYTE FROM "FROM" AREA
	INX		ADVANCE "FROM" POINTER
	STA	,Y
	INY		ADVANCE "TO" POINTER
BLOCKMOVEDOWN2	; EVEN NUMBER OF BYTES LEFT TO MOVE
	BITB	#%00000010	TWO BYTES LEFT TO MOVE BEFORE MULTIPLE OF 4 REACHED ?
	BEQ	BLOCKMOVEDOWN4	B/ NO, READY TO MOVE MULTIPLES OF 4 BYTES
	LDD	,X	FETCH BYTE PAIR
	INX		ADVANCE "FROM" POINTER
	INX
	STD	,Y	STORE BYTE PAIR
	INY		ADVANCE "TO" POINTER
	INY
	LDAB	TOPOINTER	FETCH LOWER BYTE OF MOVE COUNT
BLOCKMOVEDOWN4 ; CHECK TO SEE IF MOVING 4 BYTES GETS US TO MULTIPLE OF 8
	BITB	#%00000100	4 BYTES LEFT TO MOVE BEFORE MULTIPLE OF 8 ?
	BEQ	BLOCKMOVEDOWN8	B/ NO
	LDD	0,X	MOVE 2 BYTES
	STD	0,Y
	LDD	2,X	MOVE 2ND 2 BYTES
	STD	2,Y
	INX		ADVANCE SOURCE POINTER BY 4
	INX
	INX
	INX
	INY		ADVANCE DESTINATION POINTER BY 4
	INY
	INY
	INY
	LDAB	TOPOINTER	FETCH LOWER BYTE OF MOVE COUNT
BLOCKMOVEDOWN8 ; CHECK TO SEE IF MOVING 8 BYTES GETS US TO MULTIPLE OF 16
	STX	FROMPOINTER	SET FROMPOINTER TO SOURCE ADDRESS
	STY	TOPOINTER	SET TOPOINTER TO DESTINATION ADDRESS
	BITB	#%00001000	8 BYTES LEFT TO MOVE BEFORE MULTIPLE OF 16 ?
	BEQ	BLOCKMOVEDOWN16A	B/ NO
	LDD	0,X	MOVE 1ST PAIR OF BYTES
	STD	0,Y
	LDD	2,X	MOVE 2ND PAIR OF BYTES
	STD	2,Y
	LDD	4,X	MOVE 3RD PAIR OF BYTES
	STD	4,Y
	LDD	6,X	MOVE 4TH PAIR OF BYTES
	STD	6,Y
	LDD	FROMPOINTER	ADVANCE SOURCE POINTER
	ADDD	#8
	STD	FROMPOINTER
	LDX	FROMPOINTER
	LDD	TOPOINTER	ADVANCE DESTINATION POINTER
	ADDD	#8
	STD	TOPOINTER
	LDY	TOPOINTER
BLOCKMOVEDOWN16A ; CHECK TO SEE IF NEED TO MOVE AT LEAST 1 BLOCK OF 16
	CPX	LIMIT	ALL DONE MOVING BYTES ?
	BEQ	BLOCKMOVEDOWND	B/ YES, LEAVE!
BLOCKMOVEDOWN16 ; MOVE 16 BYTES AND ADVANCE POINTERS
	LDD	0,X	MOVE 1ST PAIR OF BYTES
	STD	0,Y
	LDD	2,X	MOVE 2ND PAIR OF BYTES
	STD	2,Y
	LDD	4,X	MOVE 3RD PAIR OF BYTES
	STD	4,Y
	LDD	6,X	MOVE 4TH PAIR OF BYTES
	STD	6,Y
	LDD	8,X	MOVE 5TH PAIR OF BYTES
	STD	8,Y
	LDD	10,X	MOVE 6TH PAIR OF BYTES
	STD	10,Y
	LDD	12,X	MOVE 7TH PAIR OF BYTES
	STD	12,Y
	LDD	14,X	MOVE 8TH PAIR OF BYTES
	STD	14,Y
	LDD	FROMPOINTER	ADVANCE "FROM" POINTER
	ADDD	#16
	STD	FROMPOINTER
	LDX	FROMPOINTER
	LDD	TOPOINTER	ADVANCE "TO" POINTER
	ADDD	#16
	STD	TOPOINTER
	LDY	TOPOINTER
	CPX	LIMIT	AT LIMIT OF "FROM" REGION ?
	BNE	BLOCKMOVEDOWN16	B/ NO, GO MOVE ANOTHER 16 BYTES
BLOCKMOVEDOWND	; BLOCK TRANSFER IS COMPLETE!
	PULD		RESTORE DCBPOINTER
	STD	DCBPOINTER
	RTS
	PAGE
         else   (m6800!m6801)
TOPOINTER EQU  RT:SPad+0               TARGET ADDRESS
FROMPOINTER EQU RT:SPad+2              SOURCE ADDRESS
LIMIT    EQU   RT:SPad+4               TRANSFER LIMIT ADDRESS
BLOCKMOVEX EQU RT:SPad+6               SCRATCH PAD LOCATIONS

RT:BMOV ; Block Move
*       BLOCKMOVEDOWN -- MOVE BLOCK AT (X) TO (Y) FOR (D) BYTES
*       (Y) = LOCATION RT:SPad
*       ON EXIT, (X) IS OLD (X)+(D)
*                (Y) IS OLD (Y)+(D)
*       COPIES LARGE BLOCKS AT 17uS. PER BYTE
*       ASSUMES COPY-TO REGION DOES NOT OVERLAP COPY-FROM REGION
*       OR THAT "FROM" >= "TO"
*       DESTROYS CONTENT OF SCRATCHPAD
*
BLOCKMOVEDOWN   EQU     *
        STX     FROMPOINTER            SAVE WHERE TO COPY FROM
        ADDD    FROMPOINTER            COMPUTE ADDRESS OF BYTE PAST END OF FROM RE
        STD     LIMIT                  SAVE AS LIMIT ADDRESS
        LDB     LIMIT+1                RESTORE (B)...
        SUBB    FROMPOINTER+1          (B):= COUNT MOD 256
        BITB    #%00000001             GOING TO MOVE TO AN EVEN NUMBER OF BYTES ?
        BEQ     BLOCKMOVEDOWNE         B/ YES
        LDA     0,X                    FETCH ODD BYTE FROM "FROM" AREA
        INX
        STX     FROMPOINTER            (TO COPY ODD BYTE TAKES 37 CYCLES)
        LDX     TOPOINTER              STORE BYTE INTO "TO" AREA
        STA     0,X
        INX
        STX     TOPOINTER
        LDX     FROMPOINTER            GET SET TO MOVE BYTE PAIR
BLOCKMOVEDOWNE  ; EVEN NUMBER OF BYTES LEFT TO MOVE
        BITB    #%00000010             TWO BYTES LEFT TO MOVE BEFORE MULTIPLE OF 4 REACHED ?
        BEQ     BLOCKMOVEDOWNA         B/ NO, READY TO MOVE MULTIPLES OF 4 BYTES
        LDD     0,X                    FETCH BYTE PAIR
        INX
        INX
        STX     FROMPOINTER            (TO COPY BYTE PAIR TAKES 28 CYCLES/BYTE)
        LDX     TOPOINTER              WHERE TO PUT BYTE PAIR
        STD     0,X
        INX
        INX
        STX     TOPOINTER
        LDX     FROMPOINTER            GET SET TO MOVE 4 BYTES AT A TIME
BLOCKMOVEDOWNA
        LDAB    FROMPOINTER+1          DO WE STILL HAVE TO MOVE A MULTIPLE OF 16 BYTES
        SUBB    LIMIT+1                (B):= COUNT MOD 256
        BITB    #%00001111             ....?
        BEQ     BLOCKMOVEDOWNB         B/ YEP.
*
*       MOVE 4 BYTES AT A TIME UNTIL A MULTIPLE OF 16 IS LEFT TO MOVE
*       COPY RATE IS 23.5 uS. PER BYTE
*
BLOCKMOVEDOWN4
        LDD     2,X                    GET 2ND AND 3RD BYTE...
        LDX     ,X                     AND 1ST AND 2ND BYTES FROM THE "FROM" AREA
        STX     BLOCKMOVEX             SAVE 1ST AND 2ND BYTES
        LDX     TOPOINTER              NOW STORE 4 BYTES TO "TO" AREA
        STD     2,X                    STORE 2ND AND 3RD BYTE
        LDD     BLOCKMOVEX
        STD     ,X                     STORE 1ST AND SECOND BYTES
        LDAB    TOPOINTER+1            ADVANCE POINTERS BY 4 BYTES
        ADDB    #4
        STAB    TOPOINTER+1
        BCC     *+5
        INC     TOPOINTER
        LDAB    FROMPOINTER+1
        ADDB    #4
        STAB    FROMPOINTER+1
        BCC     *+5
        INC     FROMPOINTER
        LDX     FROMPOINTER            SET UP FOR NEXT LOOP ITERATION
        SUBB    LIMIT+1                MULTIPLE OF 16 BYTES LEFT TO MOVE ?
        BITB    #%00001111             ....?
        BNE     BLOCKMOVEDOWN4         B/ NOPE, MOVE ANOTHER 4 BYTES
BLOCKMOVEDOWNB
        CPX     LIMIT                  YES, ALL DONE MOVING BYTES ?
        BEQ     BLOCKMOVEDOWND         B/ YES, LEAVE!
*
*       MOVE 16 BYTES AT A TIME UNTIL TRANSFER IS COMPLETE
*       COPY RATE IS 17.1 uS. PER BYTE
*
BLOCKMOVEDOWN16 EQU     *
        LDD     0+2,X                  COPY 1ST 4 BYTES
        LDX     0+0,X
        STX     BLOCKMOVEX
        LDX     TOPOINTER
        STD     0+2,X
        LDD     BLOCKMOVEX
        STD     0+0,X
        LDX     FROMPOINTER            COPY 2ND GROUP OF 4 BYTES
        LDD     4+2,X
        LDX     4+0,X
        STX     BLOCKMOVEX
        LDX     TOPOINTER
        STD     4+2,X
        LDD     BLOCKMOVEX
        STD     4+0,X
        LDX     FROMPOINTER            COPY 3RD GROUP OF 4 BYTES
        LDD     8+2,X
        LDX     8+0,X
        STX     BLOCKMOVEX
        LDX     TOPOINTER
        STD     8+2,X
        LDD     BLOCKMOVEX
        STD     8+0,X
        LDX     FROMPOINTER            COPY 4TH GROUP OF 4 BYTES
        LDD     12+2,X
        LDX     12+0,X
        STX     BLOCKMOVEX
        LDX     TOPOINTER
        STD     12+2,X
        LDD     BLOCKMOVEX
        STD     12+0,X
        LDAB    TOPOINTER+1            ADVANCE POINTERS BY 16 BYTES
        ADDB    #16
        STAB    TOPOINTER+1
        BCC     *+5
        INC     TOPOINTER
        LDAB    FROMPOINTER+1
        ADDB    #16
        STAB    FROMPOINTER+1
        BCC     *+5
        INC     FROMPOINTER
        LDX     FROMPOINTER            CHECK TO SEE IF COPY IS COMPLETE
        CPX     LIMIT                  AT LIMIT OF "FROM" REGION ?
        BNE     BLOCKMOVEDOWN16        B/ NO, GO MOVE ANOTHER 16 BYTES
BLOCKMOVEDOWND  ; BLOCK TRANSFER IS COMPLETE!
        RTS
        PAGE
        FIN
        page
; ? <what about inter-bank block move ?>
; <interbank move 2 bytes, move one byte>
         page
RT:BCmp ; Block Compare (X) to (Y) for (B) bytes
; Exits with Z set if strings match
; Else exit Z reset, (X) and (Y) pointing just past non-matching characters.
         tstb                           any bytes to compare ?
         beq     RT:BCmpRts             b/ no, just exit
         if      m6809
         lda     ,x+                    fetch source byte
         cmpa    ,y+                    compare to match string
         bne     RT:BCmpNotEqual        b/ mismatch found
         decb                           down count remaining
         bne     RT:BCmp
RT:BCmpNotEqual
RT:BCmpRts
         rts
         elseif  m6811
         lda     ,x                     fetch source byte
         inx                            advance source pointer
         cmpa    ,y                     compare to match string
         bne     RT:BCmpNotEqual        b/ mismatch found
         iny                            advance compare-to pointer
         decb                           down count remaining
         bne     RT:BCmp
RT:BCmpRts
         rts

RT:BCmpNotEqual
         iny                            advance compare-to pointer
         rts                            assert: Z says "not equal"
         else    m6800!m6801
         lda     ,x                     fetch source byte
         inx                            advance source pointer
         stx     RT:SPad+2              save source pointer
         ldx     RT:SPad+0              fetch compare-to pointer
         cmpa    ,x                     compare to match string
         bne     RT:BCmpNotEqual        b/ mismatch found
         inx                            advance compare-to pointer
         stx     RT:SPad+0              update compare-to pointer
         ldx     RT:SPad+2              fetch source pointer again
         decb                           down count remaining
         bne     RT:BCmp
RT:BCmpRts
         rts

RT:BCmpNotEqual
         inx                            advance compare-to pointer
         stx     RT:SPad+0              update compare-to pointer
         ldx     RT:SPad+2              fetch source pointer again
         rts                            assert: Z says "not equal"
         fin
         page
RTSize   equ    *-RTCODE
         end                           ; SDOSRT
