- .PRINTX * RTX.MUB
- .COMMENT \
- A SIMPLE REAL-TIME EXECUTIVE (RTX).
- 1. Supports any number of tasks (limited to 255 in some places).
- 2. Total timer interrupt task switch time is less than 60us (Z180/6MHz).
- 3. Each task must have its own stack.
- MOD RECORD
- Moved from MB II.
- rtx_switch entry modded to prevent HL corruption.
- rtx_switch reloads rtx_timer, to prevent a premature next tick
- Calc_space task changed to 'onesec'.
- 'get_task_status' routine added. The rtx_switch does NOT
- reload rtx_timer if rtx is disabled (rtx_timer=255).
- The RTX has five entry points:
- 1. Initialisation.
- Entered (with JP) just once after power-up, to set-up initial PC and
- SP values. This entry kicks-off the RTX and never returns.
- 2. Timer interrupt (timer tick).
- This is the normal entry point. At each timer tick, the RTX switches
- to the next task.
- 3. Switch to next task.
- This entry point can be used by the currently executing task to
- ask the RTX to switch to the next task, e.g. if the task has nothing
- more to do. On the Z180, this entry point is the same as the timer
- tick entry point, because the timer int pushes the same things on the
- stack as a CALL does.
- On the MUB, this entry is implemented as RST 38h, for compact code.
- 4. Suspend a task (until re-activated).
- This 'kills' a task. The RTX will ignore that task, until the task is
- re-activated with the function below.
- 5. Re-activate a (suspended) task.
- This re-activates a (previously suspended) task.
- For each task, the RTX maintains the data area below:
- offset contents initial value
- +0 af from table
- +2 bc from table
- +4 de from table
- +6 hl from table
- +8 ix from table
- +10 iy from table
- +12 sp task's sp (*before* the timer interrupt)
- +14 pc task's entry point
- +16 msr task's MSR value (used with Z280 only)
- +18 status 0: task active, 1: task suspended
- +19 reserved
- The RTX uses push/pop instructions to save/reload registers because these are
- probably the fastest way.
- The RTX loads iopage to 00h (as part of clearing the timer 0 pending int) - this
- is OK because throughout the MUB ints are OFF whenever iopage<>0 and the RTX
- should therefore never tick with iopage<>0.
- Each task's MSR is preserved, although currently all tasks run with 'ei all_ints'
- and preserving MSR is not really necessary; it could just be forced to 'all_ints'
- when each task is entered.
- Z180
- ====
- If you want to use this RTX for a Z180, you might prefer to use the slightly
- more recent version used in the IPC/180. This supports different BBR value for
- each task.
- \
- global REAL_RTX_SWITCH
- global RTX_ACTIVATE_ENTRY
- global RTX_GET_TASK_STATUS
- global RTX_INIT_ENTRY
- global RTX_SUSPEND_ENTRY
- global RTX_TIMER_INT_ENTRY
- ; code (all these are RTX tasks)
- external BIG_REQUESTS
- external FRONT_PANEL
- external LANC_OPERATIONS
- external MAIN_DATA_LOOP
- external NEW_LINKMAPS
- external ONESEC
- external PROCESS_IPC_BUFS
- external PROCESS_OP_TIMEOUTS
- external PROCESS_OUT_RCBUFS
- external PROCESS_REM_PLOFN
- external SC_EAROM_UPDATE
- external SETUP_MODE
- ; data
- external HL_SAVE
- external PC_SAVE
- external RTX_ALL_SUSPENDED
- external RTX_CURRENT_TASK
- external RTX_DATA_AREAS
- external RTX_DATA_PTR
- external RTX_TIMER
- external MSR_SAVE
- external SP_SAVE
- external STACK_START
- INCLUDE DEFS.MUB
- SECTION CODE,X
- MUB EQU TRUE
- ; RTX initialisation table. Terminated by a line with PC=0.
- ; In the future, this should be modded to hold ix,iy,sp,pc,msr values only.
- ; All tasks have equal priority and execute on a round-robin basis.
- ; ** The task *order* (i.e. their #s) is assumed by calls to rtx_activate & rtx_suspend **
- ; ** THEREFORE: ADD NEW TASKS ONLY AT THE *END* OF THE TABLE **
- ; ** Dont forget to EQUate rtx_tasks in defs.mub to #tasks below (incl PC=0 task) **
- RTX_INITIAL_REGS: ; task# v
- ; v
- ; AF BC DE HL IX IY SP PC MSR stat+res v
- DW 0 0 0 0 0 0 STACK_START+(1*STACK_SIZE) MAIN_DATA_LOOP ALL_INTS 0 ; 0
- DW 0 0 0 0 0 0 STACK_START+(2*STACK_SIZE) LANC_OPERATIONS ALL_INTS 0 ; 1
- DW 0 0 0 0 0 0 STACK_START+(3*STACK_SIZE) BIG_REQUESTS ALL_INTS 0 ; 2
- DW 0 0 0 0 0 0 STACK_START+(4*STACK_SIZE) SC_EAROM_UPDATE ALL_INTS 0001H ; 3*
- DW 0 0 0 0 0 0 STACK_START+(5*STACK_SIZE) NEW_LINKMAPS ALL_INTS 0001H ; 4*
- DW 0 0 0 0 0 0 STACK_START+(6*STACK_SIZE) SETUP_MODE ALL_INTS 0 ; 5
- DW 0 0 0 0 0 0 STACK_START+(7*STACK_SIZE) ONESEC ALL_INTS 0001H ; 6*
- DW 0 0 0 0 0 0 STACK_START+(8*STACK_SIZE) FRONT_PANEL ALL_INTS 0001H ; 7*
- DW 0 0 0 0 0 0 STACK_START+(9*STACK_SIZE) PROCESS_OP_TIMEOUTS ALL_INTS 0001H ; 8*
- DW 0 0 0 0 0 0 STACK_START+(10*STACK_SIZE) PROCESS_OUT_RCBUFS ALL_INTS 0001H ; 9*
- DW 0 0 0 0 0 0 STACK_START+(11*STACK_SIZE) PROCESS_IPC_BUFS ALL_INTS 0001H ;10*
- DW 0 0 0 0 0 0 STACK_START+(12*STACK_SIZE) PROCESS_REM_PLOFN ALL_INTS 0001H ;11*
- DW 0 0 0 0 0 0 0 0 0 0
- ; (*) the above tasks such marked run only once and suspend themselves, and are
- ; re-activated by some event. The '0001H' value sets status=01h; this makes
- ; the task *initially* suspended (e.g. we *dont* want to update the eeprom at
- ; every power-up, do we?).
- ; RTX initialisation/entry point.
- RTX_INIT_ENTRY:
- DI ; Necessary ! (in case of entry with EI)
- ; Copy initial register values from ROM into RAM.
- LD HL, RTX_INITIAL_REGS
- LD DE, RTX_DATA_AREAS
- LD BC, RTX_TASKS*RTX_DATA_SIZE
- LDIR
- ; Reset ptr (IX) to base+20 of the first task
- LDW (RTX_DATA_PTR), RTX_DATA_AREAS+RTX_DATA_SIZE
- LD (RTX_TIMER), RTX_TC ; RTX downcounter time constant
- LD (RTX_CURRENT_TASK),0 ; Initial task # := 0
- ; Enable timer interrupt
- IF MUB
- ; (no action necessary because timer 0 int is already always enabled
- ; at the timer, and rtx1st does an EI etc)
- ELSE
- IN0A TCR
- SET 5, A ; Timer 1 ints (RTX) ON
- OUT0A TCR
- ENDIF
- ; Set-up SP to 1st POP task's initial values and jump to 1st task
- LD SP, RTX_DATA_AREAS
- JP RTX1ST ; (also does EI)
- ; This is the timer interrupt service routine which switches tasks.
- ; At entry here (and at exit) rtx_data_ptr points to the 1st byte after the
- ; task's data area (i.e. byte @base+20). Therefore, when we load SP from
- ; rtx_data_ptr and start PUSHing the registers, the first word pushed (MSR)
- ; is written in the right place. Rtx_data_ptr does NOT point at base+0.
- RTX_TIMER_INT_ENTRY:
- ; First, preserve the regs which we are going to corrupt, etc, while
- ; adjusting SP back to its value *before* the timer interrupt.
- ; All this must be done with instructions which dont modify flags.
- ; We get here from ct0 interrupt, with AF'.
- IF MUB
- LD (RTX_TIMER), RTX_TC ; Reload the down-counter
- EXX
- IOPAGE 0FEH ; Clear CC bit (reset 'int pending')
- LD A, 11100000B ; (this could be done *after* the RTX code
- OUT (0E1H), A ; but for some reason doing it there did not
- IOPAGE 0 ; work properly; it does not really matter)
- EXX
- EX AF, AF' ; and switch back to main AF
- INC SP ; Skip over Mode 3 Int Reason Code
- INC SP ; (always the same - boring)
- LD (HL_SAVE), HL ; Save HL of interrupted task
- POP (MSR_SAVE) ; Save MSR of interrupted task
- ; This entry point is used by rtx_switch, which has already loaded
- ; HL and MSR into hl_save and msr_save, and disabled all interrupts.
- RTX_ES: POP HL ; HL := PC of interrupted task
- LD (SP_SAVE), SP ; Save SP (the value *before* the timer int)
- ELSE
- LD (HL_SAVE), HL ; Save HL of interrupted task
- POP HL ; HL := PC of interrupted task
- LD (SP_SAVE), SP ; Save SP of interrupted task
- ENDIF
- ; HL now holds the interrupted task's PC value.
- ; We save the interrupted task's registers by a series of PUSHes.
- LD SP, (RTX_DATA_PTR) ; Set SP to interrupted task's data area
- IF MUB
- DEC SP ; Skip over status+reserved bytes
- DEC SP
- PUSH (MSR_SAVE) ; Save MSR
- PUSH HL ; Save PC
- PUSH (SP_SAVE) ; Save SP (the value *before* this interrupt)
- PUSH IY ; Save IY
- PUSH IX ; Save IX
- PUSH (HL_SAVE) ; Save HL
- ELSE
- DEC SP ; Skip over status+reserved bytes
- DEC SP
- DEC SP ; Z180: 'push' a dummy value instead of MSR
- DEC SP
- PUSH HL ; Save PC
- LD HL, (SP_SAVE)
- PUSH HL ; Save SP
- PUSH IY ; Save IY
- PUSH IX ; Save IX
- LD HL, (HL_SAVE)
- PUSH HL ; Save HL
- ENDIF
- PUSH DE ; Save DE
- PUSH BC ; Save BC
- PUSH AF ; Save AF
- ; Interrupted task is now saved. Go to next task.
- ; If next task's PC=0, this is the end of the task table and we wrap.
- LD B, RTX_TASKS ; Max # of tasks to go through
- RTX_NX: LD IX, (RTX_DATA_PTR)
- LD SP, IX ; This loads SP just right for the POPs below
- LD DE, RTX_DATA_SIZE
- ADD IX, DE ; Move rtx_data_ptr to next task's data area
- LD HL, RTX_CURRENT_TASK
- INC (HL) ; Current task # ++
- IF MUB
- LD DE, (IX-6) ; Check if next task's PC=0
- LD A, D
- OR E
- ELSE
- LD A, (IX-6)
- OR A, (IX-5)
- ENDIF
- JR NZ, RTX_01 ; NZ: no, continue
- LD IX, RTX_DATA_AREAS+RTX_DATA_SIZE ; else reset ptr to 1st task
- LD SP, RTX_DATA_AREAS ; and set SP to start POPping for 1st task
- LD (HL), 0 ; and reset 'current task #'
- RTX_01: LD (RTX_DATA_PTR), IX
- LD A, (IX-2) ; Now check that the newly-selected task is
- OR A ; not suspended.
- JR Z, RTX1ST ; Z: not suspended - continue
- DJNZ RTX_NX ; else loop through all the tasks in the table
- ; (If all tasks are suspended, we fall out here and the next task gets
- ; executed anyway, which does not really matter unless one actually
- ; relies on suspended tasks to *never* execute. Doing this properly
- ; is more complicated).
- LD A, 1 ; Mark 'all suspended' condition detected
- LD (RTX_ALL_SUSPENDED), A
- ; Load registers for next task.
- ; This is also the entry point for starting the RTX (enter with
- ; SP = base of 1st task's data area)
- RTX1ST: POP AF ; Load AF
- POP BC ; Load BC
- POP DE ; Load DE
- IF MUB
- POP (HL_SAVE) ; Recover HL
- POP IX ; Load IX
- POP IY ; Load IY
- POP (SP_SAVE) ; Recover SP
- POP (PC_SAVE) ; Recover PC
- POP HL ; Recover MSR
- ELSE
- POP HL
- LD (HL_SAVE), HL
- POP IX
- POP IY
- POP HL
- LD (SP_SAVE), HL
- POP HL
- LD (PC_SAVE), HL
- ENDIF
- LD SP, (SP_SAVE) ; Load SP
- ; Clear the pending interrupt, enable ints and enter the new task.
- IF MUB
- PUSH (PC_SAVE) ; Put PC on stack (for RETIL to pop-off)
- PUSH HL ; Likewise with MSR
- LD HL, (HL_SAVE) ; Load HL
- RETIL ; Enter new task
- ELSE
- LD HL, (PC_SAVE)
- PUSH HL ; Put PC on stack (for RET to pop-off)
- LD HL, (HL_SAVE) ; Load HL
- EX AF, AF'
- IN0A TCR ; Clear the pending timer 1 interrupt bit
- IN0A TMDR1L
- EX AF, AF'
- EI ; Re-enable interrupts
- RET ; Enter new task
- ENDIF
- ; CALL this entry point to cause a switch to the next task. Done via RST 38H.
- ; This entry also reloads the RTX tick down-counter, so that the next RTX tick
- ; cannot occur until after a whole tick period.
- REAL_RTX_SWITCH:
- IF MUB
- DI ; Prevent 'normal' RTX tick coming in here
- PUSH AF
- LD A, (RTX_TIMER) ; If rtx is NOT disabled,
- INC A
- JR Z, RRS_NL
- LD (RTX_TIMER), RTX_TC ; then reload its down-counter
- RRS_NL: PUSH BC
- LD (HL_SAVE), HL ; Save HL straight into RTX's HL save location
- LD C, 0
- LDCTL HL, (C) ; Read current MSR
- LD A, L
- OR ALL_INTS ; Correct for 'di' above having cleared the EI bits
- LD L, A
- LD (MSR_SAVE), HL ; Save MSR straight into RTX's MSR save location
- POP BC
- POP AF
- JP RTX_ES ; Do (nearly) as if a timer tick occured
- ELSE
- DI
- EX AF, AF'
- LD A, RTX_TC ; Reload RTX downcounter time constant
- LD (RTX_TIMER), A
- EX AF, AF'
- JP RTX_TIMER_INT_ENTRY ; Do as if timer tick occurred
- ENDIF
- ; CALL this entry point to suspend a task.
- ; Enter with E = task # (0..254).
- ; If E=255, then the task currently executing is suspended. This feature
- ; is useful if a task does not know its own task # (i.e. 'suspend me').
- ; ALSO, READ THE LARGE COMMENT BELOW. It effectively means that if a routine
- ; wants to suspend itself, it must use the 'task#=255' call, NOT a 'task=own#'
- ; call !!
- ; Kills DE,HL.
- RTX_SUSPEND_ENTRY:
- LD D, E ; Make a copy of task#
- INC E ; If E=0FFh
- JR NZ, RSE_05
- LD A, (RTX_CURRENT_TASK) ; then use the 'current' task # instead
- LD E, A
- INC E
- RSE_05: DEC E
- IF MUB
- LD A, RTX_DATA_SIZE ; Index to the task's data area
- MULTU A, E
- ADDW HL, RTX_DATA_AREAS+18
- ELSE
- PUSH DE
- LD D, RTX_DATA_SIZE
- MULDE
- LD HL, RTX_DATA_AREAS+18
- ADD HL, DE
- POP DE
- ENDIF
- LD (HL), 1 ; and mark it 'suspended'
- ; If entry task# = 255, then we have a 'suspend me' call, and the
- ; task is suspended IMMEDIATELY. Normally, this is what would be
- ; really required. However, if task#<>255, then we have a case where
- ; one task (or int routine, etc) is suspending another task, and we
- ; do NOT then perform a rtx_switch. Doing an rtx_switch makes sense
- ; only on a 'suspend me' request, not on a 'suspend task n' request.
- INC D
- CALL Z, REAL_RTX_SWITCH ; and switch immediately to next task
- RET ; Return via here when task is re-activated
- ; CALL this entry point to re-activate a task.
- ; Enter with E = task # (0..).
- ; Kills AF,HL (+DE on Z180).
- RTX_ACTIVATE_ENTRY:
- IF MUB
- LD A, RTX_DATA_SIZE ; Index to the task's data area
- MULTU A, E
- ADDW HL, RTX_DATA_AREAS+18
- ELSE
- LD D, RTX_DATA_SIZE
- MULDE
- LD HL, RTX_DATA_AREAS+18
- ADD HL, DE
- ENDIF
- LD (HL), 0 ; and mark it 'active'
- RET
- ; CALL this entry point to find out whether a task is currently activated (not
- ; suspended).
- ; Enter with E = task # (0..).
- ; Returns A=00 if activated, 01 if suspended.
- ; Kills AF,HL (+DE on Z180).
- RTX_GET_TASK_STATUS:
- IF MUB
- LD A, RTX_DATA_SIZE ; Index to the task's data area
- MULTU A, E
- ADDW HL, RTX_DATA_AREAS+18
- ELSE
- LD D, RTX_DATA_SIZE
- MULDE
- LD HL, RTX_DATA_AREAS+18
- ADD HL, DE
- ENDIF
- LD A, (HL) ; and get the 'status' byte
- RET
- END
Raw Paste