;
;        TCtask - Scheduler and miscellaneous utilities
;
;        V1.1        T.Wagner
;        V1.2        TECON Ltd.
;
        name        tcasm
;         .model  large
;
        public        set_vect_d_
        public        get_vect_d_
        public        scheduler
        public        schedule_
        public        _c_schedule
        public        _sched_int
;
        public        tsk_dis_int_
        public        tsk_ena_int_
        public        tsk_cli_
        public        tsk_sti_
        public        tsk_dseg_
        public        tsk_cseg_
        public        tsk_flags_
        public        tsk_outp_
        public        tsk_inp_
        public        tsk_nop_
        public        test_
;
;
        include tsk.mac
;
INT_FLAG        =        2h        ; Int enable flag in upper byte of flag reg
r_eflags        =        44        ; Offset of flag register on task stack
;
        .data?
;
        extrn        _tsk_current: dword
        extrn        _tsk_eligible: dword
        extrn        _tsk_preempt: byte
        extrn        _tsk_pretick: byte
        extrn        _tsk_var_prior: byte
;
        .data
;
in_sched        db        0
;
        .code
;
;_dgroup         dw         seg dgroup         ;"++"
;
;
;        upd_prior: Update priority of tasks in eligible queue.
;                   Only activated if tsk_var_prior is nonzero.
;
;        NOTE:        This loop is not protected by interrupt disable.
;                Since it does not modify the queue itself, there
;                is no danger of race conditions.
;
upd_prior        macro
;
        les        di,_tsk_eligible
pinc_loop:
        mov        ax,es
        or        ax,di                        ; end of chain?
        jz        updp_end
        inc        es:prior[di]
        jnz        pinc_nxt
        dec        es:prior[di]
pinc_nxt:
        les        di,es:next[di]
        jmp        pinc_loop
;
updp_end:
;
        endm
;
;
;        The scheduler. Note that this routine is entered with the stack
;        set up as for an interrupt handler.
;
scheduler        proc
;
        cli                        ; better safe than sorry
        push    ds
        push    eax
        mov     ax,seg in_sched
        mov     ds,ax
        pop     eax

        cmp     in_sched,0        ; already in the scheduler?

        je      sched_ok        ; continue if not
        pop     ds
        iretd
;
sched_ok:
        inc     in_sched
        pop     ds
        sti                        ; we can now safely enable interrupts
        push        eax
        push        ecx
        push        edx
        push        ebx
        push        ebp
        push        esi
        push        edi
        db 66h
        push        fs
        db 66h
        push        gs
        db 66h
        push        ds
        db 66h
        push        es

;         cmp         _tsk_var_prior,0
;         je         no_var_pri
;         upd_prior                         ; update priority
;
;no_var_pri:
        cli                                ; the following is critical
;
        mov        ax,seg _tsk_current
        mov        ds,ax

        mov        edi,_tsk_current         ; get current tcb
        or         edi,edi
        jz         no_current
;
        mov        stack[edi],esp ; store stack pointer & seg
;
;------------------------------------------------------------------------
;
;        enq        Enqueue tcb in queue. For local use only.
;                entry:        es:di = tcb to enqueue
;                exit:        -
;                uses:        ax, cx, dx, bp, di
;
        mov        ebx,queue[edi]                ; queue head pointer
        or        ebx,ebx
        jz        enq_end                 ; Выйти если очередь пуста
        mov        dx,prior[edi]                ; Загрузить приоритет задачи
enq_l:
        mov        ecx,ebx                 ; Сохранить последний указатель
        mov        ebx,next[ebx]                ; Загрузить следующий
        or        ebx,ebx                 ; Последний в очереди
        jz        enq_found                ;
        cmp        dx,prior[ebx]               ; Сравнить приоритеты
        jbe        enq_l                        ; Если приоритет тек. задачи меньше
                                        ; чем у следующей то повторить
;
enq_found:
        mov        next[edi],ebx              ; Вставить следующей найденую
                                               ; задачу
        mov        ebx,ecx                        ; Последний более приоритетный
        mov        next[ebx],edi                       ; last->next = task
enq_end:

;
no_current:
        mov        _tsk_pretick,0                ; No preemption tick
        and        _tsk_preempt,1                ; Turn off temp preempt flag
;
wait_elig:
        cli
        mov        edi,_tsk_eligible
;
;        If eligible queue empty, enter waiting loop
;
        or        edi,edi                 ; Если очерьдь пуста то цыклить
        jnz        not_empty                ;
        sti                                ; enable interrupts
        nop
        nop
        jmp        wait_elig
;
;        Eligible queue not empty, activate first eligible task.
;
not_empty:
        mov        eax,next[edi]                        ; load next pointer
        mov        _tsk_eligible,eax                ; remove from queue
;
        mov        _tsk_current,edi                ; set tcb into current
        mov        ax,iniprior[edi]                ; reset current tasks priority
        mov        prior[edi],ax
;
        mov        in_sched,0                        ; reset scheduler active flag
;
        mov        esi,stack[edi]                        ; load stack
        or        byte ptr r_eflags+1[esi],INT_FLAG ; enable interrupts
        mov        state[edi],ST_RUNNING                ; set task state
        mov        esp,esi                           ; switch stack
        db 66h
        pop        es                        ; restore all registers
        db 66h
        pop        ds
        db 66h
        pop        gs
        db 66h
        pop        fs
        pop        edi
        pop        esi
        pop        ebp
        pop        ebx
        pop        edx
        pop        ecx
        pop        eax
        iretd
;
scheduler        endp
;
;
;--------------------------------------------------------------------------
;
;
;        _sched_int
;
;        Is the scheduler entry for interrupt handlers.
;        It checks if preemption is allowed, returning if not.
;        The stack is assumed to be set up as on interrupt entry.
;
_sched_int        proc
;
        push        ebx
        cmp        _tsk_preempt,0                ; preempt flags 0?
        jne        no_sched                ; no scheduling if set
        mov        ebx,_tsk_current        ; current running task
        test        flags[ebx],F_CRIT        ; preemption allowed for this task?
        jnz        no_sched                ; no scheduling if flag set
        pop        ebx                         ; else go schedule
        pop        ds
        jmp        scheduler
;
no_sched:
        mov        _tsk_pretick,1                ; Mark preemption pending
        pop        ebx
        pop        ds
        iretd
;
_sched_int        endp
;
;
;        void far schedule (void)
;
;        Entry for calling the scheduler. Rearranges the stack to
;        contain flags.
;        NOTE: Uses ax,bx.
;
schedule_        proc
;
        pop        eax
        pushfd
        push        cs
        push        eax
        cli
        jmp        scheduler
;
schedule_        endp
;
;
;        void far c_schedule (void)
;
;        Entry for conditionally calling the scheduler. Rearranges
;        the stack to contain flags, then jumps to _sched_int.
;        NOTE: Uses ax,bx.
;
_c_schedule        proc
;
        pop        eax
        pushfd
        push        cs
        push        eax
        cli
        jmp        _sched_int
;
_c_schedule        endp
;
;--------------------------------------------------------------------------
;
;        word tsk_dseg (void)
;
;        Returns current contents of DS register.
;
tsk_dseg_        proc
        mov        ax,ds
        ret
tsk_dseg_        endp
;
;        word tsk_cseg (void)
;
;        Returns current contents of DS register.
;
tsk_cseg_        proc
        mov        ax,cs
        ret
tsk_cseg_        endp

;
;        word tsk_flags (void)
;
;        Returns current contents of Flag register.
;
tsk_flags_        proc
        pushfd
        pop        eax
        ret
tsk_flags_        endp
;
;
;        int tsk_dis_int (void)
;
;        Returns current state of the interrupt flag (1 if ints were
;        enabled), then disables interrupts.
;
tsk_dis_int_        proc                ;"++"
;
        pushf
        pop        ax
        mov        cl,9
        shr        ax,cl
        and        ax,1
        cli
        ret
;
tsk_dis_int_        endp
;
;
;        void far tsk_ena_int (int state)
;
;        Enables interrupts if 'state' is nonzero.
;
tsk_ena_int_        proc                 ;"++"
        or         ax,ax
        jz         teiend
        sti
teiend:
        ret
;
tsk_ena_int_        endp
;
;
;        tsk_cli/tsk_sti: disable/enable int
;
tsk_cli_        proc
        cli
        ret
tsk_cli_        endp
;
;
tsk_sti_        proc
        sti
        ret
tsk_sti_        endp
;
;
;        tsk_inp/tsk_outp: input/output from/to port
;
tsk_inp_        proc
;
        push        dx
        mov        dx,ax
        in        al,dx
        xor        ah,ah
        pop        dx
        ret
;
tsk_inp_        endp
;
;
tsk_outp_        proc
;
        push        dx
        mov        dx,ax
        pop        ax
        out        dx,al
        ret
;
tsk_outp_        endp
;
;
;        void tsk_nop (void)
;
;        Do nothing. Used for very short delays.
;
tsk_nop_        proc
        push        ax              ; было:
        push        cx              ;            jmp         tnop1
        push        dx              ;   tnop1:   jmp         tnop2
;        mov        ah,86h              ;   tnop2:   ret
;        xor        cx,cx
;        mov        dx,976
;        int        15h
        pop        dx
        pop        cx
        pop        ax
        ret
tsk_nop_        endp

test_                proc
;********************** TEST ********************
        push        ds
        push        eax
        mov        ax,seg in_sched
        mov        ds,ax
        mov        eax,0b8C80h
        mov        word ptr ds:[eax],1234h
        pop        eax
        pop        ds
;********************** TEST ********************
test_                endp

;------ fartr get_vect_d(byte int)
get_vect_d_        proc
        mov        bl,al
        mov        ax,204h                 ;Чтение вектора прерывания 8 = es:ebx
        int        31h
        mov        eax,edx
        movzx        edx,cx
        ret
get_vect_d_        endp


;------ void set_vect_d(byte int, fartr)
set_vect_d_        proc              ;al - int, cx - seg, ebx - offs
        cli
        mov        edx,ebx
        mov        bl,al
        mov        ax,205h                  ;Установка вектора 8 на DS:EDX
        int        31h
        sti
        ret
set_vect_d_        endp

        end