; ; --- Version 2.2 90-11-22 12:04 --- ; ; CTask - DOS access module. ; ; Public Domain Software written by ; Thomas Wagner ; Ferrari electronic Gmbh ; Beusselstrasse 27 ; D-1000 Berlin 21 ; Germany ; ; NOTE: Some routines have been moved to "tsksec.asm" in Version 2.1 ; to support a minimal secondary kernel. ; ; The support for the critical section interrupt was dropped in ; version 2.1. Swapping the DOS variable area eliminates ; the need for maintaining critical sections across calls. ; ; The DOS interrupt (and the related direct disk I/O interrupts) are ; not reentrant. Access to DOS thus has to be channelled such that no ; two tasks use DOS services simultaneously. However, there is one ; exception to this rule. Whenever DOS is waiting for the keyboard, it ; issues a special interrupt, INT 28h, to signal that background ; processing for functions > 0Ch may be performed. This is used in the ; DOS interface for CTask in the following manner: ; ; A task issuing a DOS interrupt will request one of two resources: ; "lower_dos" for functions <= 0C, "upper_dos" for functions > 0C. ; If a task gets access to "lower_dos", it will also request the ; "upper_dos" resource to lock out other tasks from interrupting. ; This "upper_dos" resource is shortly released on each INT 28, and ; then immediately reclaimed, with task priority temporarily raised ; to the maximum value. The first task waiting to execute a ; function > 0C will thus be scheduled to execute the request, but ; the resource will be reassigned to the INT 28 handler as soon as ; this request terminates, so the waiting task will not be delayed too ; long. ; ; There are two additional safety measures which have to be taken to ; avoid getting into conflicts with other resident background programs, ; especially the DOS PRINT background spooler: ; ; Before actually executing the request, the status of the DOS in-use ; flag is checked. If this flag is set, the task enters a busy waiting ; loop, calling the scheduler so that the processor is not tied up. ; ; NOTE: The method for checking the status of DOS is described in-depth ; in the book "The MS-DOS Encyclopedia" from Microsoft Press, in ; the chapter on TSR programming. The logic in this module was ; developed without the help of this book, so if you compare ; the code here with the routines in the Encyclopedia, you may ; notice that not all re-entry conditions are checked here. ; According to my experience with debugging TSR's and CTask, the ; logic should be sufficient for all but the most obscure TSR's. ; If you want to be completely on the safe side, you might consider ; adding the more thorough checks listed in the Encyclopedia. ; ; CAUTION: This module is not ROMable, and it can only be ; installed in the primary kernel. ; name tskdos ; include tsk.mac ; IF DOS ; .tsk_model ; include tskdeb.h ; Pubfunc tsk_install_dos Pubfunc tsk_remove_dos Pubfunc tsk_fatal IF CHECKING Pubfunc tsk_fatal_pmd Globext tsk_set_currdis Globext tsk_set_dualdis Globext tsk_rputc Globext tsk_rputs Globext tsk_vrprintf CGlbext tsk_rprintf ENDIF ; public tsk_emergency_exit ; Globext create_resource Globext delete_resource Globext request_resource Globext request_cresource Globext release_resource Globext create_flag Globext delete_flag Globext set_flag Globext clear_flag Globext wait_flag_clear Globext yield Globext preempt_off ; Locext tsk_switch_stack Locext tsk_old_stack Locext tsk_remove_group Locext tsk_kill_group Globext tsk_remove_tasker ; extrn tsk_instflags: word ; extrn tsk_glob_rec: byte IF NOT SINGLE_DATA extrn tsk_global: dword ENDIF ; get_in_use_flag = 34h ; DOS-function to get in_use_flag address get_invars = 5d06h ; DOS-function to get DOS-variables area ; psp_offset = 10h-2 ; Offset of current PSP in DOS save area ; ; (-2 because we add 2 to the start) ; ; Structure of the start of a PSP, including undocumented fields ; psp_record struc ; dw ? ; INT 20 dw ? ; alloc block end db ? ; reserved db 5 dup(?) ; system call psp_exit_addr dd ? ; exit routine address psp_ctlc_addr dd ? ; control C routine address psp_cerr_addr dd ? ; critical error routine address parent_psp dw ? ; PSP of parent process db 20 dup(?) ; files table psp_envseg dw ? ; environment segment psp_ustack dd ? ; ss/sp of caller dw ? ; file table length dd ? ; file table pointer dd ? ; pointer to nested PSP (?) ; psp_record ends ; ; intseg segment at 0 org 10h*4 vidoff dw ? ; video interrupt vidseg dw ? org 13h*4 diskoff dw ? ; disk i/o interrupt diskseg dw ? org 20h*4 termoff dw ? ; program terminate vector termseg dw ? org 21h*4 idosoff dw ? ; dos interrupt idosseg dw ? org 25h*4 absreadoff dw ? ; absolute disk read absreadseg dw ? org 26h*4 abswriteoff dw ? ; absolute disk write abswriteseg dw ? org 27h*4 keepoff dw ? ; Terminate but stay resident keepseg dw ? org 28h*4 idleoff dw ? ; dos idle interrupt idleseg dw ? org 2ah*4 csectoff dw ? ; dos critical section csectseg dw ? org 40h*4 fdiskoff dw ? ; redirected floppy disk I/O interrupt fdiskseg dw ? ; intseg ends ; ;---------------------------------------------------------------------------- ; ; Variables ; .tsk_data ; idle_active db 0 ; Idle-Interrupt active dos310 db 0 ; DOS version >= 3.10 ; IF TSK_NAMEPAR udos_name db "DOSUPPER",0 ldos_name db "DOSLOWER",0 hdio_name db "HARDDISK",0 fdio_name db "FLEXDISK",0 vid_name db "VIDEO",0 ENDIF ; ; in_error dd ? ; Adress of DOS error-mode-flag ; lower_dos resource <> upper_dos resource <> hdisk_io resource <> fdisk_io resource <> video resource <> ; vid_flags dw ? i13h_flags dw ? i13f_flags dw ? ; .tsk_edata .tsk_code ; ;--------------------------------------------------------------------------- ; ; Original Interrupt-Entries ; savvid label dword ; original Video entry savvidoff dw ? savvidseg dw ? ; savdisk label dword ; original Disk I/O entry savdiskoff dw ? savdiskseg dw ? ; savfdisk label dword ; original Floppy Disk I/O entry savfdiskoff dw ? savfdiskseg dw ? ; savtermoff dw ? ; Terminate vector save savtermseg dw ? ; savdos label dword ; original DOS-Entry savdosoff dw ? savdosseg dw ? ; savidle label dword ; original IDLE-Entry savidleoff dw ? savidleseg dw ? ; savcsect label dword ; Critical Section save savcsectoff dw ? savcsectseg dw ? ; savabsread label dword ; Absolute Disk Read save savabsreadoff dw ? savabsreadseg dw ? ; savabswrite label dword ; Absolute Disk Write save savabswriteoff dw ? savabswriteseg dw ? ; savkeepoff dw ? ; Terminate resident vector save savkeepseg dw ? ; ; dos_version dw ? ; DOS version ; temp_1 dw ? ; tsk_dgroup dw @CTASK_DATA ; zero db 0 ; calldos macro pushf cli call cs:savdos endm ; ;--------------------------------------------------------------------------- ; ; void tsk_install_dos (void) ; ; Install DOS handler ; Localfunc tsk_install_dos, ; ; create needed resources & flags ; IFDEF LOAD_DS push ds mov ax,@CTASK_DATA mov ds,ax ENDIF ; IF TSK_NAMEPAR callp create_resource,<,> ELSE callp create_resource,<> ENDIF ; IF TSK_NAMEPAR callp create_resource,<,> ELSE callp create_resource,<> ENDIF ; test tsk_instflags,IFL_DISK jz inst_nodsk1 IF TSK_NAMEPAR callp create_resource,<,> ELSE callp create_resource,<> ENDIF ; IF TSK_NAMEPAR callp create_resource,<,> ELSE callp create_resource,<> ENDIF ; inst_nodsk1: test tsk_instflags,IFL_VIDEO jz inst_novid1 IF TSK_NAMEPAR callp create_resource,<,> ELSE callp create_resource,<> ENDIF ; inst_novid1: ; mov tsk_glob_rec.l_swap,0 ; init swap area length to 0 ; ; Get the DOS version. Only versions 3.1 and up support the ; DOS variable swap, so disable swapping if it's an earlier version, ; or if it's the OS/2 box. ; mov ah,30h int 21h mov cs:dos_version,ax cmp al,3 jb not_dos3 cmp al,0ah je not_dos3 ; OS/2 compatibility box cmp al,3 jne is_dos3 ; DOS 4.x cmp ah,10 jb not_dos3 is_dos3: inc dos310 ; ; Get address and length of the DOS internal variable area. ; This call is not documented, and not available in versions ; prior to 3.1. It returns the address of the area in DS:SI, ; and the length of the area that must be swapped between calls ; from different groups in DX. ; Additionally, the total length of the area, including ; the DOS-stacks, minus the length in DX, is returned in CX. ; ; The DOS in_error flag is at the start of this area, the ; in_use flag is the second byte. Both bytes are excluded ; from the swap. ; test tsk_instflags,IFL_NODOSVARS jnz not_dos3 ; push ds mov ax,get_invars int 21h mov ax,ds pop ds mov word ptr in_error,si mov word ptr in_error+2,ax inc si mov word ptr tsk_glob_rec.dos_in_use,si mov word ptr tsk_glob_rec.dos_in_use+2,ax inc si mov word ptr tsk_glob_rec.dos_vars,si mov word ptr tsk_glob_rec.dos_vars+2,ax sub dx,2 mov tsk_glob_rec.l_swap,dx jmp short save_ints ; ; For versions < 3.10, and if the IFL_NODOSVARS flag is set, ; get the address of DOS's in_use-flag. This flag indicates that ; DOS is already active. This might happen if there are other ; background tasks, like popups or print spoolers, active in ; parallel to CTask. ; This is also the address of the critical error flag in DOS. Beginning ; with DOS 3.10 the flag is located one byte before the in_use_flag. ; With older DOS versions, we would have to search through DOS for the ; address. This is omitted here, but you could include the code ; for pre 3.1 versions from pages 378-379 of the MS-DOS Encyclopedia. ; not_dos3: mov ah,get_in_use_flag int 21h mov word ptr tsk_glob_rec.dos_in_use,bx mov word ptr tsk_glob_rec.dos_in_use+2,es mov word ptr in_error+2,es dec bx mov word ptr in_error,bx cmp dos310,0 jne save_ints ; OK for DOS >= 3.10 ; ; For DOS < 3.1, we simply ignore the error flag. ; CTask's DOS handler will not work that great anyway with older ; versions. ; mov word ptr in_error,offset zero mov word ptr in_error+2,cs ; ; Save old interrupt vectors ; save_ints: push es xor ax,ax mov es,ax ; assume es:intseg ; test tsk_instflags,IFL_VIDEO jz inst_novid2 mov ax,vidoff ; video mov savvidoff,ax mov ax,vidseg mov savvidseg,ax ; inst_novid2: test tsk_instflags,IFL_DISK jz inst_nodsk2 mov ax,diskoff ; Disk I/O mov savdiskoff,ax mov ax,diskseg mov savdiskseg,ax mov ax,fdiskoff ; Floppy Disk I/O mov savfdiskoff,ax mov ax,fdiskseg mov savfdiskseg,ax ; inst_nodsk2: mov ax,termoff ; DOS mov savtermoff,ax mov ax,termseg mov savtermseg,ax ; mov ax,idosoff ; DOS mov savdosoff,ax mov ax,idosseg mov savdosseg,ax ; mov ax,idleoff ; IDLE mov savidleoff,ax mov ax,idleseg mov savidleseg,ax ; mov ax,csectoff ; Critical Section mov savcsectoff,ax mov ax,csectseg mov savcsectseg,ax ; mov ax,absreadoff ; Absolute Disk read mov savabsreadoff,ax mov ax,absreadseg mov savabsreadseg,ax ; mov ax,abswriteoff ; Absolute Disk write mov savabswriteoff,ax mov ax,abswriteseg mov savabswriteseg,ax ; mov ax,keepoff ; Terminate Resident mov savkeepoff,ax mov ax,keepseg mov savkeepseg,ax ; ; Enter new Interrupt-Entries ; cli test tsk_instflags,IFL_VIDEO jz inst_novid3 mov vidoff,offset @videntry ; Video Entry mov vidseg,cs ; inst_novid3: test tsk_instflags,IFL_DISK jz inst_nodsk3 mov diskoff,offset @diskentry ; Disk I/O Entry mov diskseg,cs mov fdiskoff,offset @fdiskentry ; Floppy Disk I/O Entry mov fdiskseg,cs ; inst_nodsk3: mov idosoff,offset @dosentry ; DOS-Entry mov idosseg,cs mov idleoff,offset @idleentry ; Idle-Entry mov idleseg,cs mov termoff,offset terminate_int ; Terminate Process Entry mov termseg,cs mov csectoff,offset @critsectint ; Critical Section Entry mov csectseg,cs mov keepoff,offset keep_int ; Keep Process Entry mov keepseg,cs mov absreadoff,offset absread_int ; Absolute Disk Read Entry mov absreadseg,cs mov abswriteoff,offset abswrite_int ; Absolute Disk Write Entry mov abswriteseg,cs sti ; assume es:nothing ; pop es IFDEF LOAD_DS pop ds ENDIF ret ; ; tsk_install_dos endp ; ;--------------------------------------------------------------------------- ; ; void tsk_remove_dos (void) ; ; Un-install DOS handler ; Localfunc tsk_remove_dos ; IFDEF LOAD_DS push ds mov ax,@CTASK_DATA mov ds,ax ENDIF ; ; Delete resources & flags ; callp delete_resource,<> callp delete_resource,<> ; test tsk_instflags,IFL_DISK jz rem_nodsk1 callp delete_resource,<> callp delete_resource,<> ; rem_nodsk1: test tsk_instflags,IFL_VIDEO jz rem_novid1 callp delete_resource,<> ; rem_novid1: push es xor ax,ax mov es,ax ; assume es:intseg ; ; Restore interrupt entries ; cli test tsk_instflags,IFL_VIDEO jz rem_novid2 mov ax,savvidoff mov vidoff,ax mov ax,savvidseg mov vidseg,ax ; rem_novid2: test tsk_instflags,IFL_DISK jz rem_nodsk2 mov ax,savdiskoff mov diskoff,ax mov ax,savdiskseg mov diskseg,ax mov ax,savfdiskoff mov fdiskoff,ax mov ax,savfdiskseg mov fdiskseg,ax ; rem_nodsk2: mov ax,savtermoff mov termoff,ax mov ax,savtermseg mov termseg,ax ; mov ax,savdosoff mov idosoff,ax mov ax,savdosseg mov idosseg,ax ; mov ax,savidleoff mov idleoff,ax mov ax,savidleseg mov idleseg,ax ; mov ax,savcsectoff mov csectoff,ax mov ax,savcsectseg mov csectseg,ax ; mov ax,savabsreadoff mov absreadoff,ax mov ax,savabsreadseg mov absreadseg,ax ; mov ax,savabswriteoff mov abswriteoff,ax mov ax,savabswriteseg mov abswriteseg,ax ; mov ax,savkeepoff mov keepoff,ax mov ax,savkeepseg mov keepseg,ax ; sti ; pop es IFDEF LOAD_DS pop ds ENDIF ret ; assume es:nothing ; tsk_remove_dos endp ; ; ;--------------------------------------------------------------------------- ;--------------------------------------------------------------------------- ; ; ; INT 10: Video BIOS interrupt ; @videntry proc far ; call tsk_switch_stack callp request_cresource,<,0,0> mov ax,caller_flags[bp] mov vid_flags,ax call tsk_old_stack pushf cli call savvid call tsk_switch_stack mov ax,vid_flags mov caller_flags[bp],ax callp release_resource,<> iret ; @videntry endp ; ;--------------------------------------------------------------------------- ; ; ; INT 13 and INT 40: Disk I/O BIOS interrupt ; @diskentry proc far ; call tsk_switch_stack test dl,80h ; fixed disk ? jz disk_floppy ; jump if not callp request_cresource,<,0,0> call tsk_old_stack pushf cli call savdisk call tsk_switch_stack callp release_resource,<> mov ax,entry_flags[bp] mov caller_flags[bp],ax iret ; disk_floppy: callp request_cresource,<,0,0> call tsk_old_stack pushf cli call savdisk call tsk_switch_stack callp release_resource,<> mov ax,entry_flags[bp] mov caller_flags[bp],ax iret ; @diskentry endp ; @fdiskentry proc far ; call tsk_switch_stack callp request_cresource,<,0,0> call tsk_old_stack pushf cli call savfdisk call tsk_switch_stack callp release_resource,<> mov ax,entry_flags[bp] mov caller_flags[bp],ax iret ; @fdiskentry endp ; ;--------------------------------------------------------------------------- ; ; Stack-Offsets relative to BP ; d_func = -2 ; ;--------------------------------------------------------------------------- ; ; INT 25: Absolute Disk Read ; ; This interrupt is channeled through the normal DOS-function ; processing, with function = 0x25 and special flag set. ; It is re-translated later after the necessary resources ; have been requested. INT 25 is handled like a function > 0C. ; Interrupts 25 und 26 leave the flag-word on the stack. ; Since flags are removed in normal processing, the flag-word ; has to be duplicated on the stack here. ; absread_int: sti push bp ; reserve space push bp ; save BP mov bp,sp push ax mov ax,4[bp] ; Move return offset, segment down mov 2[bp],ax mov ax,6[bp] mov 4[bp],ax mov ax,8[bp] ; duplicate flags mov 6[bp],ax pop ax pop bp call tsk_switch_stack mov ax,2501h mov cx,ax jmp short dosentry_2 ; ; ;--------------------------------------------------------------------------- ; ; INT 26: Absolute Disk Write ; ; This interrupt is channeled through the normal DOS-function ; processing, with function = 0x26 and special flag set. ; It is re-translated later after the necessary resources ; have been requested. INT 26 is handled like a function > 0C. ; Interrupts 25 und 26 leave the flag-word on the stack. ; Since flags are removed in normal processing, the flag-word ; has to be duplicated on the stack here. ; abswrite_int: sti push bp ; reserve space push bp ; save BP mov bp,sp push ax mov ax,4[bp] ; Move return offset, segment down mov 2[bp],ax mov ax,6[bp] mov 4[bp],ax mov ax,8[bp] ; duplicate flags mov 6[bp],ax pop ax pop bp call tsk_switch_stack mov ax,2601h mov cx,ax jmp short dosentry_2 ; ;--------------------------------------------------------------------------- ; ; INT 27: Terminate But Stay Resident Interrupt ; ; This interrupt is translated to INT 21, function 31. ; keep_int: call tsk_switch_stack ; add dx,0fh ; last addr + 0f to round sub dx,caller_cs[bp] ; minus CS (= PSP) mov cl,4 shr dx,cl ; div 16 = paragraphs mov ax,3100h ; Keep process mov save_ax[bp],ax mov cx,ax jmp short dosentry_2 ; ;--------------------------------------------------------------------------- ; ; INT 20: Terminate Program interrupt ; ; This interrupt is translated to INT 21, function 4c. ; terminate_int: call tsk_switch_stack mov ax,4c00h mov save_ax[bp],ax mov cx,ax jmp short dosentry_2 ; ;--------------------------------------------------------------------------- ; ; INT 21: DOS-Interrupt ; @dosentry proc far ; call tsk_switch_stack xor cl,cl mov ch,ah ; dosentry_2: push cx ; BP-2: func in CH, special flag in CL or cl,cl jnz dosent_x ; no function check if special ; ; Check if this is a special 'get dos version' call to determine ; if CTask is resident. ; ; A special call has ; ; AX = 3000h ; BX = 1234h ; DS:DX = pointer to version string ; cmp ax,3000h jne no_spdos cmp bx,1234h jne no_spdos cmp dx,0fff0h jae no_spdos push ds mov si,dx mov ds,save_ds[bp] mov di,offset tsk_glob_rec mov cx,8 repe cmpsb pop ds jz is_spdos jmp short no_spdos ; ; Special version call returns global variable block address ; in BX (offset) and CX (segment). ; is_spdos: add sp,2 call tsk_old_stack mov ax,cs:dos_version mov bx,offset tsk_glob_rec mov cx,@CTASK_DATA iret ; ;--------------------------------------------------------------------------- ; ; no_spdos: mov ax,d_func[bp] or ah,ah ; terminate? jne dosent_x mov save_ax[bp],4c00h ; translate to fn 4c, retcode 0 mov d_func[bp],4c00h ; ; Now the real fun begins. ; dosent_x: sti ; Interrupts allowed now ; ; DL is used as an "emergency" marker. If nonzero, no resources ; are to be requested, and the in-dos flag is not to be checked. ; There is an "emergency" if the task is marked as owning ; DOS resources, but re-enters INT 21. ; xor dl,dl les bx,tsk_glob_rec.current_task test es:t_indos[bx],OWN_UPPER OR OWN_LOWER jz no_reenter ; no trouble if task doesn't own DOS ; call @dosbusy ; this sets DL nonzero if busy jnz no_reenter ; Don't release resources if busy ; ; If the task owns DOS resources, but DOS is not busy, something ; strange is going on (this case should have been handled by the ; 'schedent' routine). Anyway, we assume that DOS is right about ; not being busy, and release the resources. Although the resources ; will be requested again a few instructions later, this is sensible ; to give other tasks that might be waiting on the resource a chance ; to execute. ; call @relres xor dl,dl ; ; The preliminaries are done with, we now have to distinguish ; between functions 00-0C and 0D-FF. ; no_reenter: mov ax,d_func[bp] cmp ah,0ch jbe lower_funcs jmp upper_funcs ; ; ; Functions 00-0C ; lower_funcs: ; ; first, request the "lower_dos" resource unless this is an emergency. ; or dl,dl jnz lower_emergency callp request_resource,<,0,0> ; ; we have it, now let's get the upper_dos resource, too ; callp request_resource,<,0,0> ; les bx,tsk_glob_rec.current_task mov es:t_indos[bx],OWN_UPPER or OWN_LOWER ; ; both resources gained, now we may execute the function if dos is free ; call @wait_dos_free ; ; Set the scheduler entry function ; cli mov word ptr es:sched_ent_func[bx],offset @schedent mov word ptr es:sched_ent_func+2[bx],cs sti ; lower_emergency: add sp,2 push caller_flags[bp] popf call tsk_old_stack ; calldos ; execute function ; ; Now we have to release the resources, unless DOS still indicates ; that it is busy. This would indicate that a critical error has ; occurred, and DOS processing is not really complete. ; call tsk_switch_stack ; call @dosbusy jnz no_relc ; return if DOS still busy ; ; Clear the flags in the TCB, and the scheduler entry function. ; les bx,tsk_glob_rec.current_task cli mov es:t_indos[bx],0 mov word ptr es:sched_ent_func[bx],0 mov word ptr es:sched_ent_func+2[bx],0 sti ; callp release_resource,<> callp release_resource,<> ; ; All done, restore registers and return. ; no_relc: cli mov ax,entry_flags[bp] mov caller_flags[bp],ax iret ; ;-------------------------------------------------------------------------- ; ; Functions 0D and above ; upper_funcs: ; ; first, request the "upper_dos" resource unless this is an emergency. ; or dl,dl jnz upper_emergency ; callp request_resource,<,0,0> ; les bx,tsk_glob_rec.current_task mov es:t_indos[bx],OWN_UPPER ; ; resource gained, now we may execute the function if dos is free ; call @wait_dos_free ; ; Set the scheduler entry function ; cli mov word ptr es:sched_ent_func[bx],offset @schedent mov word ptr es:sched_ent_func+2[bx],cs sti ; upper_emergency: cmp byte ptr d_func[bp],0 jne no_term mov ax,save_ax[bp] cmp ah,31h ; terminate resident? jne ckfunc1 jmp term_resident ckfunc1: cmp ax,4b00h ; spawn new process? jne ckfunc2 jmp terminate ckfunc2: cmp ah,4ch ; terminate program? jne no_term jmp terminate ; no_term: ; ; Filter special-functions 25/26 (Absolute Read/Write) ; pop ax cmp ax,2501h jne uf_exec1 push caller_flags[bp] popf call tsk_old_stack pushf cli call cs:savabsread pop cs:temp_1 ; remove flags jmp short uf_complete ; uf_exec1: cmp ax,2601h jne uf_exec2 push caller_flags[bp] popf call tsk_old_stack pushf cli call cs:savabswrite pop cs:temp_1 ; remove flags jmp short uf_complete ; uf_exec2: push caller_flags[bp] popf call tsk_old_stack ; calldos ; execute function ; ; Now we have to release the resources, unless DOS still indicates ; that it is busy. This would indicate that a critical error has ; occurred, and DOS processing is not really complete. ; However, if we were called from INT 28, DOS must still be busy, ; so we only check the in_error flag. ; uf_complete: call tsk_switch_stack ; cmp idle_active,0 jnz uf_nobsychk ; don't check busy if INT 28 call les bx,tsk_glob_rec.dos_in_use cmp es:byte ptr [bx],0 jne no_relc_uf ; don't release if busy ; uf_nobsychk: les bx,in_error cmp es:byte ptr [bx],0 jnz no_relc_uf ; don't release if in error ; les bx,tsk_glob_rec.current_task cli mov es:t_indos[bx],0 mov word ptr es:sched_ent_func[bx],0 mov word ptr es:sched_ent_func+2[bx],0 sti ; callp release_resource,<> ; ; All done, restore registers and return. ; no_relc_uf: cli mov ax,entry_flags[bp] mov caller_flags[bp],ax iret ; ;-------------------------------------------------------------------------- ; ; Terminate, TSR and Spawn calls go directly to DOS. ; TSR has to reset the exit address if it's the PSP of the ; current group, so the group is not deleted. ; term_resident: les bx,tsk_glob_rec.dos_vars mov ax,es:[bx+psp_offset] ; current PSP les bx,tsk_glob_rec.current_task les bx,es:tgroup[bx] cmp es:gcreate_psp[bx],ax jne terminate push ds mov ds,ax mov ax,word ptr es:grp_exit_addr[bx] mov word ptr ds:psp_exit_addr,ax mov ax,word ptr es:grp_exit_addr+2[bx] mov word ptr ds:psp_exit_addr+2,ax pop ds ; terminate: add sp,2 push caller_flags[bp] popf call tsk_old_stack cli jmp cs:savdos ; @dosentry endp ; ;-------------------------------------------------------------------------- ; ; 'dosbusy' checks the DOS busy flags. ; ; Entry: - ; Exit: DL is nonzero if DOS is busy. ; @dosbusy proc near ; push bx push es les bx,in_error mov dl,es:byte ptr [bx] les bx,tsk_glob_rec.dos_in_use or dl,es:byte ptr [bx] pop es pop bx ret ; @dosbusy endp ; ;-------------------------------------------------------------------------- ; ; Wait_dos_free uses busy waiting (calling _yield) in case ; some other background program has entered DOS without going ; through our INT 21 interface. ; @wait_dos_free proc near push es push bx ; in_use_loop: les bx,in_error cmp byte ptr es:[bx],0 jne is_in_use ; wait if in_error set ; cmp idle_active,0 ; idle interrupt active? je ck_inuse ; check for flag if no pop bx pop es ret ; else return immediately ; ck_inuse: les bx,tsk_glob_rec.dos_in_use cmp byte ptr es:[bx],0 jne is_in_use pop bx pop es ret ; is_in_use: call yield jmp in_use_loop ; @wait_dos_free endp ; ;---------------------------------------------------------------------------- ; ; 'relres' releases the DOS-resources of a task. ; ; Entry: ; ES:BX = TCB pointer ; @relres proc near ; test es:t_indos[bx],OWN_UPPER jz rel_lower xor es:t_indos[bx],OWN_UPPER push bx push es callp release_resource,<> pop es pop bx ; rel_lower: test es:t_indos[bx],OWN_LOWER jz rel_exit xor es:t_indos[bx],OWN_LOWER push bx push es callp release_resource,<> pop es pop bx ; rel_exit: ret ; @relres endp ; ;---------------------------------------------------------------------------- ; ; The 'schedent' function is called by the scheduler if the ; scheduler is entered and the current task owns one of the ; DOS resources. ; This routine checks whether the DOS busy flags are clear now, ; which would mean that the task left DOS without passing through ; the return address. If that's the case, we release the resources. ; ; The only catch is that there is a race condition if we just check ; the busy flags. There is a time slot where the function is set in ; the TCB, but the busy flag is clear because DOS has not yet been ; entered. Since we have no control over what will happen, and how ; long it will take to reach the point where the in_use flag is ; incremented, we can't simply disable preemption. So what we have ; to do is to wait for some other indication that DOS has begun ; executing the request, and mark this in the TCB. In all versions ; starting at 3.1, DOS will call the 'critical section' INT 2A ; with AX=8200 on functions > 0C after incrementing the in_use flag. ; We also have some indication that DOS is active if the 'idle' ; INT 28 is called, or if the in_use flag is set when we get here. ; ; Entry: ; DS CTask data segment ; BX Global data block offset ; ES:DI Task control block ; @schedent proc far ; call @dosbusy ; DOS still busy ? jz schedent_relres ; release resources if not busy or es:t_indos[di],DOS_ENTERED ret ; schedent_relres: test es:t_indos[di],DOS_ENTERED jz schedent_ret ; don't release if DOS not yet entered ; mov bx,di call @relres ; schedent_ret: ret ; @schedent endp ; ;---------------------------------------------------------------------------- ; ; INT 28: DOS Idle Interrupt ; @idleentry proc far ; call tsk_switch_stack ; ; check the in_use flag. If it's set, set the DOS_ENTERED flag ; in the current TCB. ; les bx,tsk_glob_rec.dos_in_use cmp byte ptr es:[bx],0 je no_entermark ; les bx,tsk_glob_rec.current_task or es:t_indos[bx],DOS_ENTERED ; ; Check if someone is waiting for upper_dos. If not, we can return ; immediately. ; no_entermark: les bx,upper_dos.rwaiting.q_first test es:q_kind[bx],Q_HEAD jnz idle_exit ; ; Also make sure this is not a second invocation of INT 28, and ; that the in_error flag is not set. ; Normally, this should never happen, but better safe than sorry. ; cmp idle_active,0 jne idle_exit les bx,in_error cmp byte ptr es:[bx],0 jne idle_exit ; inc idle_active ; ; someone is waiting, let's please him by releasing the resource. ; temporarily increase priority ; les bx,tsk_glob_rec.current_task push es:cqueue.q_el.q_prior[bx] push es:cqueue.q_el.q_ini_prior[bx] mov es:cqueue.q_el.q_prior[bx],0ffffh mov es:cqueue.q_el.q_ini_prior[bx],0ffffh push bx push es ; ; release resource & request it again ; callp release_resource,<> callp request_resource,<,0,0> ; ; ready, restore priority ; cli pop es pop bx pop es:cqueue.q_el.q_ini_prior[bx] pop es:cqueue.q_el.q_prior[bx] ; mov idle_active,0 ; idle_exit: push caller_flags[bp] popf call tsk_old_stack cli ; jmp cs:savidle ; chain to original interrupt ; @idleentry endp ; ;---------------------------------------------------------------------------- ; ; INT 2A: DOS Critical Section Interrupt ; ; Not documented. ; Is used by DOS PRINT to mark Critical Regions. ; Usage by PRINT: ; AX = 8700 - Begin Critical Region ; Returns: ; Carry set if already active. ; AX = 8701 - End Critical Region ; ; Other usage in DOS, function unknown: ; AH = 82 (AL undefined) ; seems to be called on DOS-Functions > 0C ; AH = 84 (AL undefined) ; seems to be called when DOS is idle ; ; We only handle function number 82 in this version, ; it is used to mark that DOS has been entered. All functions ; are passed on to the next in chain. ; @critsectint proc far ; cmp ah,82h jne cs_exit push ds push es push bx mov ds,cs:tsk_dgroup ; ; check the in_use flag. If it's set, set the DOS_ENTERED flag ; in the current TCB. ; les bx,tsk_glob_rec.dos_in_use cmp byte ptr es:[bx],0 je cs_no_enter ; les bx,tsk_glob_rec.current_task or es:t_indos[bx],DOS_ENTERED ; cs_no_enter: pop bx pop es pop ds ; cs_exit: jmp cs:savcsect ; @critsectint endp ; ;--------------------------------------------------------------------------- ;--------------------------------------------------------------------------- ; term_err_msg db 0dh,0ah,"Program terminated - CTask uninstalled" db 0dh,0ah,'$' ; group_term_msg db 0dh,0ah,"Program terminated - Task Group removed" db 0dh,0ah,'$' ; gcb_mixup_err db 0dh,0ah,"Group chain damaged - System halted" db 07h,'$' ; ;--------------------------------------------------------------------------- ; ; tsk_emergency_exit is entered by DOS when a task group exits ; without releasing the current group. ; Registers are set up, remove_group is called, and the program ; is terminated by jumping to the terminate_address. ; tsk_emergency_exit proc far ; pushf sub sp,4 ; make room for return addr push bp mov bp,sp push ax push bx push cx push dx push si push di push ds push es mov ax,@CTASK_DATA mov ds,ax ; les bx,tsk_glob_rec.current_task[bx] les bx,es:tgroup[bx] mov ax,word ptr es:grp_exit_addr[bx] mov 2[bp],ax mov ax,word ptr es:grp_exit_addr+2[bx] mov 4[bp],ax ; callp tsk_remove_group,<,0> ; mov dx,offset group_term_msg cmp ax,0 je emergency_end jb pg_fatal ; les bx,tsk_glob_rec.current_task[bx] les bx,es:tgroup[bx] callp tsk_kill_group,<> call tsk_remove_tasker ; mov dx,offset term_err_msg ; emergency_end: ; mov ax,cs mov ds,ax mov ah,9 int 21h ; pop es pop ds pop di pop si pop dx pop cx pop bx pop ax pop bp iret ; pg_fatal: mov si,offset gcb_mixup_err jmp short fatal_error ; tsk_emergency_exit endp ; ;-------------------------------------------------------------------------- ; ; fatal_error can be called if the system can't continue for ; some reason. It issues an error message and halts the system. ; SI must point to the '$'-terminated error message. ; ; tsk_fatal does the same, but assumes C calling sequence and ; a zero-terminated string. ; Globalfunc tsk_fatal, ; cld lds si,strp jmp short dis_err ; tsk_fatal endp ; fatal_error: mov ax,cs mov ds,ax ; dis_err: IF IBM xor ax,ax int 10h ; re-init display fatal_loop: lodsb cmp al,'$' je fatal_end or al,al jz fatal_end mov bx,7 mov ah,14 int 10h jmp fatal_loop ELSE mov dx,si mov ah,9 int 21h ENDIF fatal_end: sti jmp fatal_end ; ;-------------------------------------------------------------------------- ; ; void CGlobalfunc tsk_fatal_pmd (byteptr string, ...) ; ; tsk_fatal_pmd is only present in "checking" mode. ; It does a minimal "post mortem dump", and displays the ; error message passed as parameter. ; The messages are output to both the primary and secondary ; monitor via the "regen" printf routines in tskprf. ; The system is then halted, this routine never returns. ; IF CHECKING ; ; CGlobalfunc tsk_fatal_pmd, ; ; int 3 ; uncomment to break to debugger push sp push ss push es push ds push di push si push dx push cx push bx push ax ; mov ds,tsk_dgroup callp preempt_off callp tsk_set_currdis ; callp tsk_rputc,<0ch> les di,txt lea bx,argp call @fatal_dump ; callp tsk_set_dualdis or ax,ax jz no_second ; callp tsk_rputc,<0ch> les di,txt lea bx,argp call @fatal_dump ; no_second: jmp fatal_end ; tsk_fatal_pmd endp ; ; pmd1 db 0ah db 'AX = %04X BX = %04X CX = %04X DX = %04X',0ah db 'SI = %04X DI = %04X DS = %04X ES = %04X',0ah db 'SS = %04X SP = %04X BP = %04X',0ah db 'IP = %04X CS = %04X',0ah,0 pmd2 db 'Current Task = %FP' IF TSK_NAMED db ' (%s)' ENDIF db 0ah,0 pmde db 'System Halted',0 ; ; @fatal_dump proc near ; callp tsk_vrprintf,<,> mov bx,sp add bx,2 callp tsk_vrprintf,<,> ; IF SINGLE_DATA mov bx,offset tsk_glob_rec push ds pop es ELSE les bx,tsk_global ENDIF les bx,es:current_task[bx] IF TSK_NAMED lea dx,tname.nname[bx] callp tsk_rprintf,<,,> ELSE callp tsk_rprintf,<,> ENDIF callp tsk_rprintf,<> ret ; @fatal_dump endp ; ENDIF ; ;--------------------------------------------------------------- ; .tsk_ecode ; ENDIF ; end