; ; --- Version 2.2 90-10-12 10:46 --- ; ; CTask - Timer interrupt handler (IBM specific) ; ; Public Domain Software written by ; Thomas Wagner ; Ferrari electronic Gmbh ; Beusselstrasse 27 ; D-1000 Berlin 21 ; Germany ; ; NOTE: Logic in this module has been changed to accommodate two ; different ways to handle chaining to the original INT 8 ; entry in version 1.2. ; ; CAUTION: This module can only be installed in the primary kernel. ; It is not ROMable. ; ; For non-IBM applications, you have to completely replace ; this module and write your own. ; You should be able to use this module at least as a model ; by throwing out about 80%, mainly the initialization ; and interrupt chaining stuff. All you really need is the ; tsk_timer_counter increment, and the ticker processing, ; which can be left unchanged. The section enclosed by ; IF INT8_EARLY most likely is the one you should use as ; reference, since you will usuallly not have other routines ; chained to the timer tick in an embedded system. ; name tsktim ; include tsk.mac include tskdeb.h ; .tsk_model ; Pubfunc tsk_install_timer Pubfunc tsk_remove_timer IF NOT INT8_EARLY Pubfunc tsk_chain_timer ENDIF ; extrn sched_int: far Globext inc_counter Globext set_flag ; extrn tsk_key_avail: byte extrn tsk_glob_rec: byte extrn tsk_timer_counter:word extrn tsk_instflags: word IF NOT INT8_EARLY extrn tsk_int8_counter:word ENDIF ; IF DEBUG AND DEB_FLASHERS Locext tsk_inccdis extrn tsk_debflash: word extrn tsk_dtposn: dword ENDIF ; ; STACKSIZE = 128 ; local stack size (words) ; timer equ 40h ; 8253 timer base I/O address inta00 equ 20h ; 8259 int controller base eoi equ 20h ; unspecific EOI ; intseg segment at 0 ; org 8*4 tintofs dw ? ; timer interrupt entry tintseg dw ? intseg ends ; biosdataseg segment at 40h org 1ah kbdptr1 dw ? kbdptr2 dw ? biosdataseg ends ; IF FAR_STACKS ctask_stacks segment word public 'CTASK_STACKS' ELSE .tsk_data ENDIF ; dw STACKSIZE dup(?) local_stack label word ; IF FAR_STACKS ctask_stacks ends .tsk_data ENDIF ; divisor dw ? timflag dw ? timcnt dw ? sys_ticks dw ? ; .tsk_edata .tsk_code ; tsk_dgroup dw @CTASK_DATA ; IF FAR_STACKS stackseg dw SEG ctask_stacks ELSE stackseg equ ENDIF ; timer_save label dword ; in CSEG to allow addressing tsofs dw ? tsseg dw ? ; r_ss dw ? r_sp dw ? in_timer db 0 ; ;---------------------------------------------------------------------- ; IF NOT INT8_EARLY ; ; Timer interrupt handler, late INT 8 processing ; ; Normal timer tick. The tick counter is incremented, and ; the interrupt controller is checked for other pending interrupts. ; If the timer tick occurred during processing of another interrupt, ; we may not call the scheduler, since this would delay the ; interrupt handler. ; ; Note that an EOI is issued here to allow interrupts to occur ; during further processing. The original INT 8 handler will be ; chained to from a special task. The reason behind this is that ; some TSR's link into the timer interrupt and may do some lengthy ; processing there. To allow the TSR to be preempted, we must use ; a task for the INT 8 processing. ; @timer_int_late proc far ; ; first, check for overrun. If we're already processing a tick, ; we simply ignore this one. That's not that great a way to ; react, but anything else would easily mess things up, and we're ; already in trouble anyway if this should happen. ; cmp cs:in_timer,0 je timer_ok push ax mov al,eoi ; issue EOI out inta00,al pop ax iret ; ; Not a second interrupt, continue processing. First, set the ; in_timer marker, and switch to our local stack. ; timer_ok: inc cs:in_timer mov cs:r_ss,ss mov cs:r_sp,sp mov ss,cs:stackseg mov sp,offset local_stack sti cld push ax push ds mov ds,cs:tsk_dgroup ; IF DEBUG AND DEB_FLASHERS cmp tsk_debflash,0 je debdd0 mov ax,DEBP_CNTTICK call tsk_inccdis debdd0: ENDIF ; push es ; save other regs push bx push cx push dx push si push di push bp callp inc_counter,<> ; increase timer tick counter ; ; Now the timer counter is decremented. If it is zero, ; we must chain to the original INT 8, so the counter for ; the chain task is incremented. ; dec timcnt ; decrement tick count jnz no_pass ; pass on this int if zero ; mov ax,sys_ticks mov timcnt,ax ; re-init tick counter ; callp inc_counter,<> ; ; Now we decrement all installed tick counters ; no_pass: les bx,tsk_glob_rec.ticker_chain ; count_dec: mov ax,es or ax,bx jz tick_ready mov ax,es:ticklo[bx] or ax,es:tickhi[bx] jz count_next sub es:ticklo[bx],1 sbb es:tickhi[bx],0 count_next: les bx,es:ticknext[bx] jmp count_dec ; ; Now check the BIOS keyboard buffer. ; If there are chars in the buffer, set the tsk_key_avail flag ; to wake up tasks waiting for the keyboard. This check is required ; to find out about keyboard stuffers placing characters directly ; into the keyboard buffer. ; tick_ready: mov ax,SEG biosdataseg mov es,ax assume es:biosdataseg mov ax,es:kbdptr1 cmp ax,es:kbdptr2 assume es:@CTASK_DATA je tick_exit callp set_flag,<> ; tick_exit: pop bp pop di pop si pop dx pop cx pop bx pop es ; cli mov al,eoi ; issue EOI out inta00,al mov al,0bh ; access int control reg out inta00,al in al,inta00 ; ints pending? or al,al jnz tim_retn ; don't schedule if other ints active IF DEBUG AND DEB_FLASHERS cmp tsk_debflash,0 je debdda1 push bx lds bx,tsk_dtposn mov byte ptr [bx+DEBP_CNTTICK+DEBFLASH_NDIGS*2],'+' pop bx debdda1: ENDIF pop ds pop ax mov ss,cs:r_ss mov sp,cs:r_sp dec cs:in_timer jmp sched_int ; else schedule ; tim_retn: IF DEBUG AND DEB_FLASHERS cmp tsk_debflash,0 je debdda2 push bx lds bx,tsk_dtposn mov byte ptr [bx+DEBP_CNTTICK+DEBFLASH_NDIGS*2],'-' pop bx debdda2: ENDIF pop ds pop ax mov ss,cs:r_ss mov sp,cs:r_sp dec cs:in_timer iret ; @timer_int_late endp ; ENDIF ; ;---------------------------------------------------------------------- ; IF NOT INT8_LATE ; ; Timer interrupt handler, early INT 8 processing ; ; With this interrupt handler, the original INT 8 is chained to ; directly, before calling the scheduler. ; ; This avoids compatibility problems with TSR's that do strange ; things in the timer tick, but it may lead to problems within ; CTask, should the strange TSR decide to loop in the INT 8 handler, ; or even to never return. Oh well, you can't please all TSR's all ; of the time... ; @timer_int_early proc far ; cld push ax push ds mov ds,cs:tsk_dgroup ; IF DEBUG AND DEB_FLASHERS cmp tsk_debflash,0 je debdd1 mov ax,DEBP_CNTTICK call tsk_inccdis debdd1: ENDIF ; ; The timer counter is decremented. If it is zero, ; we chain directly to the original INT 8. ; Since the local stack is not yet in use, ; multiple entries into this part are possible. ; xor ah,ah ; no delayed tick dec timcnt ; decrement tick count jnz eno_pass ; mov ax,sys_ticks mov timcnt,ax ; re-init tick counter ; ; Version 2.1 adds a check for scheduler active here. ; We may not call the original INT 8 if the scheduler is active, ; since this would crash the system when the old INT 8 causes ; a task_wait. See tskasm.asm for a more detailed explanation. ; cmp tsk_glob_rec.in_sched,0 je call_oldint ; mov ah,1 ; mark delayed tick jmp short eno_pass ; call_oldint: sti pop ds pop ax pushf call cs:timer_save ; call original INT 8 push ax push ds mov ds,cs:tsk_dgroup xor ah,ah ; no delayed tick jmp short e_pass ; ; On return from INT 8 processing, or when INT 8 was not chained, ; processing is pretty much the same as in timer_int_late. ; eno_pass: mov al,eoi ; issue EOI when not chained out inta00,al ; e_pass: cli cmp cs:in_timer,0 je etimer_ok pop ds pop ax iret ; etimer_ok: inc cs:in_timer mov cs:r_ss,ss mov cs:r_sp,sp mov ss,cs:stackseg mov sp,offset local_stack sti cld ; push es ; save other regs push bx push cx push dx push si push di push bp ; ; If the tick was delayed, we have to notify the tsk_int8 to ; process an additional tick when there's time. ; IF NOT INT8_EARLY or ah,ah ; check delay flag jz no_delay callp inc_counter,<> ENDIF ; no_delay: callp inc_counter,<> ; increase timer tick counter ; ; Now we decrement all installed tick counters ; les bx,tsk_glob_rec.ticker_chain ; ecount_dec: mov ax,es or ax,bx jz etick_ready mov ax,es:ticklo[bx] or ax,es:tickhi[bx] jz ecount_next sub es:ticklo[bx],1 sbb es:tickhi[bx],0 ecount_next: les bx,es:ticknext[bx] jmp ecount_dec ; ; Now check the BIOS keyboard buffer. ; If there are chars in the buffer, set the tsk_key_avail flag ; to wake up tasks waiting for the keyboard. This check is required ; to find out about keyboard stuffers placing characters directly ; into the keyboard buffer. ; etick_ready: mov ax,SEG biosdataseg mov es,ax assume es:biosdataseg mov ax,es:kbdptr1 cmp ax,es:kbdptr2 assume es:@CTASK_DATA je etick_exit callp set_flag,<> ; etick_exit: pop bp pop di pop si pop dx pop cx pop bx pop es ; cli mov al,0bh ; access int control reg out inta00,al in al,inta00 ; ints pending? or al,al jnz etim_retn ; don't schedule if other ints active mov ss,cs:r_ss mov sp,cs:r_sp pop ds pop ax dec cs:in_timer jmp sched_int ; else schedule ; etim_retn: mov tsk_glob_rec.pretick,1 ; mark that we missed a schedule mov ss,cs:r_ss mov sp,cs:r_sp pop ds pop ax dec cs:in_timer iret ; @timer_int_early endp ; ENDIF ; ;--------------------------------------------------------------------- ; ; Install timer. ; The scheduler is not called on this first tick. ; @tim_install proc far ; push ax push ds push es ; mov ds,cs:tsk_dgroup ; mov timflag,0 ; signal init ready mov ax,sys_ticks mov timcnt,ax ; init tick counter mov al,36h out timer+3,al ; setup to load divisor mov al,byte ptr divisor out timer,al ; lsb mov al,byte ptr divisor+1 out timer,al ; msb ; xor ax,ax mov es,ax assume es:intseg ; IF INT8_EARLY mov ax,offset @timer_int_early ELSE mov ax,offset @timer_int_late IF NOT INT8_LATE test tsk_instflags,IFL_INT8_DIR jz tim_inst_1 mov ax,offset @timer_int_early ENDIF ENDIF ; tim_inst_1: mov es:tintofs,ax mov es:tintseg,cs ; pop es pop ds pop ax jmp cs:timer_save ; @tim_install endp ; ;------------------------------------------------------------------------ ; ; Un-Install timer (wait until system tick count reached). ; The scheduler is not called while waiting for the tick count. ; @tim_uninstall proc far ; push ax push ds ; mov ds,cs:tsk_dgroup ; cli dec timcnt ; decrement tick count jz uninit ; go un-install if zero mov al,eoi ; else just issue EOI out inta00,al pop ds pop ax iret ; ; Uninstall timer int handler ; uninit: mov timflag,0 ; mark un-install complete mov al,36h ; setup to load divisor out timer+3,al mov al,0 ; divisor 0 means 65536 out timer,al ; lsb out timer,al ; msb ; push es xor ax,ax mov es,ax assume es:intseg mov ax,cs:tsofs ; restore vector mov tintofs,ax mov ax,cs:tsseg mov tintseg,ax pop es pop ds pop ax jmp cs:timer_save ; pass on interrupt ; @tim_uninstall endp ; ;---------------------------------------------------------------------- ; IF NOT INT8_EARLY ; ; void far tsk_chain_timer (void) ; ; Pass timer tick on to interrupt 8 chain. ; Localfunc tsk_chain_timer ; IF DEBUG AND DEB_FLASHERS cmp tsk_debflash,0 je debdd2 mov ax,DEBP_CNTTCHAIN call tsk_inccdis debdd2: ENDIF pushf cli call cs:timer_save ret ; tsk_chain_timer endp ; ENDIF ; ; ; void near tsk_install_timer (word divisor, word sys_ticks) ; ; This routine installs the timer tick int handler. ; The timer chip is reprogrammed on the next tick. ; Localfunc tsk_install_timer, ; IFDEF LOAD_DS push ds mov ds,cs:tsk_dgroup ENDIF ; mov ax,pdivisor mov divisor,ax mov ax,psysticks mov sys_ticks,ax mov timflag,1 ; set init-flag xor ax,ax mov es,ax ; establish addressing for intseg assume es:intseg ; mov ax,tintofs ; save old timer int addr mov tsofs,ax mov ax,tintseg mov tsseg,ax cli mov tintofs,offset @tim_install ; set new timer int addr mov tintseg,cs sti assume es:nothing wait_set: cmp timflag,0 ; wait until timer started jne wait_set IFDEF LOAD_DS pop ds ENDIF ret ; tsk_install_timer endp ; ; ; void far tsk_remove_timer (void) ; ; This routine un-installs the timer tick int handler. ; The timer chip is reprogrammed & the interrupt vector ; restored when the system tick count reaches zero. ; Localfunc tsk_remove_timer ; IFDEF LOAD_DS push ds mov ds,cs:tsk_dgroup ENDIF ; mov timflag,2 ; set un-init flag for timer xor ax,ax mov es,ax ; establish addressing for intseg assume es:intseg ; cli mov tintofs,offset @tim_uninstall ; set new timer int addr mov tintseg,cs sti assume es:nothing wait_tim: sti ; just to be safe cmp timflag,0 ; wait until int un-installed jne wait_tim IFDEF LOAD_DS pop ds ENDIF ret ; tsk_remove_timer endp ; .tsk_ecode end