; ; Real Time Operating System for the HD64180 CPU. ; ; Based on code written by Jack Ganssle and modified by Bob Paddock. ; Used with permission. ; ; ; v3.63s - MSB 12/1999 ; -- Updated tic values for faster (18.432MHz clock w/ Z8S180 CPU) ; ; The operating system is segmented into the following sections: ; Initialization ; Context Switcher ; Task control - which is further broken into: ; Task DEFINE ; Task RUN ; Task EXIT ; Task CANCEL ; Task set PRIORITY ; Task WAIT ; symbols ; public context_switch, os_init public os_define, os_run, os_exit public os_cancel, os_wait, os_priority public tcb, tcbptr ; ; include ports.lib ; globals on ; numtsk equ 16 ; max number of tasks-1 allowed ;tic equ 4608 ; timer divisor to get 100 tics/sec tic equ 9216 ; timer divisor to get 100 tics/sec (MSB 3.63s) ;cbar_data equ 84h ; RTOS CBAR value ; ; Task state definitions ; waiting equ 1 ; task is in a WAIT mode suspend equ 2 ; task has been interrupted so another can be serviced active equ 4 ; task currently has control of the CPU ready equ 8 ; task was started by a RUN & is ready to execute none equ 10h ; task not defined dormant equ 20h ; task is defined but has nothing to do ; tcb_size equ 16 ; size of each TCB entry ; ; Offsets into each TCB entry to get specific values ; t_state equ 0 ; task state t_cancel equ 1 ; cancel flag t_bank equ 2 ; task's MMU bank t_priority equ 3 ; task's priority t_rsi equ 4 ; task's reschedule interval t_rsc equ 6 ; task's reschedule count t_wait equ 8 ; task's wait count t_start equ 10 ; task's start address t_sp equ 12 ; task's stack pointer during timer int ;t_istate equ 14 ; task's state when suspended ; ; Data area for the operating system ; .DATA ; tcbptr ds 2 ; pointer to current tcb entry tcb ds numtsk*tcb_size ; task control block ; .CODE ; ;========================================================================= ; ; Initialization ; ; This code sets up the TCB and other variables. It MUST be called ; on initial reset BEFORE enabling interrupts. ; ; Note that each task is defined at state NONE. The TCB must be one ; entry larger than needed so the last unused task will always be NONE. ; os_init: ld hl,tcb ld (tcbptr),hl ; set pointer to tcb=start of tcb PUSH HL POP IX ld b,numtsk ; max number of tasks allowed ld de,tcb_size - 1 ; # bytes to add to get to next entry XOR A init1: ; in this loop we init the tcb ld (hl),none ; set state of each task=NONE inc hl ld (hl),A ; zero cancel bit add hl,de ; pt to next task djnz init1 ; do all tcb entries ; ; ld a,cbar_data ; set cbar for c0=0-3fff, b=4000-7fff, c1=8000-ffff ; out0 (cbar),a ; ld a,0 ; set RAM at physical 8000 ; out0 (cbr),a ; out0 (bbr),a ; set tasks at physical 0000+load offset ; ; since task 0 not defined, do it here ; ld (ix+t_state),active ; task 0 always active [unless suspended] ld (ix+t_priority),32 ; set middle level priority ld (ix+t_rsc),A ; set task 0 rsc=0 ld (ix+t_rsc+1),A ld (ix+t_bank),A ; set task 0 bank=0 ; ; [Any process with a priority lower than task zero will never be run] ; in0 a,(tcr) or a,11h out0 (tcr),a ; enable timer 0 for interrupts ld a,tic ; set high tic divisor out0 (tmdr0h),a out0 (rldr0h),a ld a,i ; find interrupt vector table ld h,a in0 a,(il) or 04h ; point to timer 0 ld l,a ld (hl),context_switch ; set interrupt vector ei ret ; ;========================================================================= ; ; Context Switcher ; ; This code sequences all task activities. ; ; Up to NUMTSK tasks can be defined. Each of these tasks requires an ; entry in the Task Control Block (TCB) of tcb_size bytes. Each entry ; takes the form: ; ; Byte 0 STATE - indicates which state this task is in ; 1 CANCEL - set nonzero if this task has been canceled ; 2 BANK - what memory bank the task is in ; 3 PRIORITY - task relative priority (63=highest, 1=lowest) ; 4 RESCHEDULE INTERVAL - time in tics between automatic ; rescheduling of this task ; 6 RESCHEDULE COUNT - # tics to go before restarting the task ; 8 WAIT COUNT - # tics to go till we can resume a waiting task ; 10 START ADDRESS - start address of this task ; 12 STACK POINTER - this task's SP at the time it is interrupted ; 14 INT STATE - indicates which state the task was in when suspended ; ; This is where the execution goes on an interrupt from the timer. ; context_switch: push hl ; push all registers push de push bc push af push ix push iy ; exx ; push hl ; push de ; push bc ; exx ; ex af,af' ; push af ; ex af,af' ; in0 a,(tcr) ; read tcr to clear interrupt in0 a,(tmdr0l) ; also must read tmdr to clear intr ld ix,(tcbptr) ; pt to current task's tcb entry ; LD A,(IX+t_state) ; save the task's current state ; LD (IX+t_istate),A ld (ix+t_state),suspend ; suspend the task ; ; Decrement all reschedule and wait counts in the entire tcb. ; IX must be preserved. ; ld iy,tcb ; pt to tcb start ld de,tcb_size ; adder to next tcb entry dc0: ld a,(iy+t_state) ; get task state cp none ; if none, end of used entries JR Z,cs_sp ; j if no more tasks to do ld l,(iy+t_rsc) ; set hl=reschedule count ld h,(iy+t_rsc+1) ld a,l or h ; if rsc=0, don't dec it jr z,dc1 ; j if rsc=0 dec hl ; rsc-1 ld (iy+t_rsc),l ; restore rsc ld (iy+t_rsc+1),h dc1: ld l,(iy+t_wait) ; now dec the wait count ld h,(iy+t_wait+1) ld a,l or h ; if wait=0, don't dec it jr z,dc2 ; j if wait=0 dec hl ; dec wait count ld (iy+t_wait),l ; save count ld (iy+t_wait+1),h dc2: add iy,de ; pt to next entry in tcb jr dc0 ; ; os_exit and os_wait enter here ; cs: di ; don't want to be disturbed LD DE,tcb_size ; TCB entry size cs_sp: ld hl,0 ; save the task stack pointer add hl,sp ; hl=stack pointer for this task ld (ix+t_sp),l ; save sp low in tcb ld (ix+t_sp+1),h ; save sp high in tcb ; ; Increment round robin pointer. IX points to just-suspended task. ; Add tcb_size, modulo the table size. Make sure the pointer is ; aimed at a task that is available for execution. ; inc1: add ix,de ; pt to next entry ld a,(ix+t_state) ; get task's state cp none ; if none, recycle to start of table jr nz,inc2 ; j if not table end ld ix,tcb ; recycle table ld a,(ix+t_state) ; a=task's state inc2: and ready+suspend+waiting ; Only these are available for exec jr z,inc1 ; if not one of those, go to next task ld a,(ix+t_rsc) ; if available, rsc =0 or (ix+t_rsc+1) jr nz,inc1 ; j if rsc<>0 ld a,(ix+t_state) ; a=task's state cp waiting ; is the task in a wait? JR NZ,find_tsk ; j if suspended or ready ld a,(ix+t_wait) ; to be available, wait=0 or (ix+t_wait+1) jr nz,inc1 ; j if wait count not up ; ; Find the next task to run. ; ; The following registers will be maintained: ; IX- Pointer to highest priority task so far ; IY- TCB pointer ; C - Highest priority found so far ; B - Task counter ; DE- tcb_size ; find_tsk: push ix pop iy ; start search at current task LD B,2 ; loop through the tcb till NONE found twice LD C,0 ; highest P so far=0 ft1: ld a,(iy+t_state) ; get state of task and ready+suspend+waiting jr z,ft3 ; j if not in any of those states ld a,(iy+t_rsc) ; see if rsc=0 or (iy+t_rsc+1) ; if 0, rsc=0 jr nz,ft3 ; j if rsc <>0 ld a,(iy+t_state) ; get task state cp waiting ; is this task in a wait mode? jr nz,ft2 ; j if the task is ready or suspended ld a,(iy+t_wait) ; see if wait count=0 or (iy+t_wait+1) jr nz,ft3 ; j if wait not - ft2: ld a,(iy+t_priority) ; a=task's priority cp C ; if this one > last biggest found? jp m,ft3 ; j if this one is < last biggest jr z,ft3 ; also skip if equal priorities ld C,a ; set new highest priority push iy pop ix ; set new next-tsk-to-do ft3: add iy,de ; make iy pt to next entry ld a,(iy+t_state) ; get state of next task cp none ; if none, we need to recycle table jr nz,ft1 ld iy,tcb ; recycle table DJNZ ft1 ; dec # times to go through table ; ; This is the task to do: ; ld (tcbptr),ix ; reset tcbptr ; ld l,(ix+t_sp) ; get low sp ld h,(ix+t_sp+1) ; get high sp ld sp,hl ; set the task's sp ; ld a,(ix+t_bank) ; set bank for this task ; out0 (bbr),a ; set bbr for this task ; ld a,(ix+t_state) ; ix pts to task to run; get state ld (ix+t_state),active ; set task will now be active cp ready ; possible states: ready, suspended, waiting jr nz,css1 ; j if not ready ; ld l,(ix+t_start) ; Ready: get low start address ld h,(ix+t_start+1); Ready: get high start address ; CALL pulse ; ************ EI JP (HL) ; Ready: Do It! css1: cp suspend ; suspended? jr nz,cs_wait ; j if not; must be waiting ; ; Restore registers from suspended task ; ; LD A,(IX+t_istate) ; restore t_state the way we found it ; LD (IX+t_state),A ; when it was suspended ; ; ex af,af' ; restore registers ; pop af ; ex af,af' ; exx ; pop bc ; pop de ; pop hl ; exx pop iy pop ix pop af pop bc pop de pop hl cs_wait: ; enter here to resume task that was waiting ; CALL pulse ; *********** ei ret ; resume suspended or waiting task ; ; ;pulse: ; ************ ; push af ; push bc ; ld bc,0c000h ; in a,(c) ; pop bc ; pop af ; ret ; ;========================================================================= ; ; These routines are called by executing programs to request ; service by the operating system. ; ; The following requests are supported: ; DEFINE, RUN, EXIT, CANCEL, PRIORITY, WAIT ; ; ; os_define - define a task. This routine doesn't start a task executing; ; rather, it simply makes the task known to the operating system so the ; context switcher can start it at the proper time. ; ; Entered with: ; HL = task's start address ; DE = task's top of stack ; C = task's runtime bank (bbr value) (IGNORED!) ; B = task's initial priority ; A = task number (0 to numtsk-1) ; os_define: call pt_to_task ; set ix to task entry ld (ix+t_state),dormant ; dormant till a RUN commanded ld (ix+t_bank),c ; set bank ld (ix+t_priority),b ; set initial priority ld (ix+t_start),l ; set start address ld (ix+t_start+1),h ld (ix+t_sp),e ; set top of stack ld (ix+t_sp+1),d ret ; ;------------------------------------------------------------------------- ; ; os_run - put a task into the READY state. ; ; This task is entered with A=task number and DE=reschedule interval. ; os_run: di call pt_to_task ; set ix=tcb entry ld (ix+t_rsi),e ; set rsi=de ld (ix+t_rsi+1),d ld (ix+t_rsc),e ; set rsc=de ld (ix+t_rsc+1),d ld (ix+t_cancel),0 ; make sure cancel not set ld (ix+t_state),ready ; elevate task to READY ei ret ; ;------------------------------------------------------------------------- ; ; os_exit - put a task back in the dormant state (if CANCEL was set) ; or back to READY. ; os_exit: di ld ix,(tcbptr) ; point to the tcb entry ld (ix+t_state),dormant ; set dormant ld a,(ix+t_rsi) ; get low reschedule interval ld (ix+t_rsc),a ; set rs count=rs interval ld a,(ix+t_rsi+1) ; do the entire 16 bits ld (ix+t_rsc+1),a ; I'd give 5 bucks for a 16 bit load ld a,(ix+t_cancel) ; a=1 if canceled or a jp nz,cs ; j if canceled ld (ix+t_state),ready ; put task in ready state jp cs ; exit to context switcher ; ;------------------------------------------------------------------------- ; ; os_priority - set the task's priority. Entered with B=new priority ; to assign (0 to 63) to this task. ; os_priority: di ld ix,(tcbptr) ld (ix+t_priority),b ; set new priority ei ret ; ;------------------------------------------------------------------------- ; ; os_cancel - Cancel the task whose task number is in A (0 to numtsk-1). ; os_cancel: di call pt_to_task ; set ix=tcb entry ld (ix+t_cancel),1 ; cancel task ei ret ; ;------------------------------------------------------------------------- ; ; os_wait - put the task into a WAIT state. The Wait count is passed ; in DE (1 to 32767). ; os_wait: di ld ix,(tcbptr) ; set ix=tcb entry ld (ix+t_state),waiting ; put task to sleep ld (ix+t_wait),e ; set wait count ld (ix+t_wait+1),d jp cs ; goto context switcher after pushes ; ;------------------------------------------------------------------------- ; ; pt_to_task - With an entry parameter of the task number (0 to numtsk-1) ; in A, return IX pointing to the TCB entry of the task. Preserve all ; registers except A and IX. ; pt_to_task: push de ld IX,tcb ; pt to tcb entry ld de,tcb_size ; # bytes/entry pttsk1: dec a jp m,pttsk2 ; j if done add IX,de ; pt to next tcb entry jr pttsk1 pttsk2: pop de ret ; ix=entry pter ; end