home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_300 / 330_02 / tskasm.asm < prev    next >
Assembly Source File  |  1990-10-12  |  27KB  |  1,312 lines

  1. ;
  2. ;    --- Version 2.2 90-10-12 10:37 ---
  3. ;
  4. ;    CTask - Scheduler and miscellaneous utilities
  5. ;
  6. ;    Public Domain Software written by
  7. ;        Thomas Wagner
  8. ;        Ferrari electronic Gmbh
  9. ;        Beusselstrasse 27
  10. ;        D-1000 Berlin 21
  11. ;        Germany
  12. ;
  13. ;
  14. ; NOTE: Version 2.1 enables interrupts shortly after setting the in_sched
  15. ;    flag. This greatly reduces interrupt latency, but it has its
  16. ;    pitfalls. The main culprit is modification of the current TCB
  17. ;    while the scheduler is busy processing it. However, the situations
  18. ;    where a "foreign" TCB is modified are rare, so it is possible to
  19. ;    handle those cases in the appropriate places. Note that it was
  20. ;    never a good idea to make a task waiting or, worse yet, kill the
  21. ;    current task, while the scheduler was active. This was not detected
  22. ;    in previous releases, but would still cause crashes, even though
  23. ;    the interrupt was enabled only in the eligible waiting loop.
  24. ;
  25. ;    The only legitimate action is to make a task eligible while it
  26. ;    is being scheduled out. Since this only modifies the queue head
  27. ;    and task status, no adverse effects are possible. But making a task
  28. ;    waiting that is already scheduled out (as it was possible in
  29. ;    all versions up to 2.1) simply won't work. The wait action requires
  30. ;    that the scheduler switch the context away from the running task,
  31. ;    which it can't do when it's already active and there is no real
  32. ;    "running task". So the scheduler would immediately return, and 
  33. ;    the wait would never occur, most likely causing some very 
  34. ;    strange effects. Version 2.1 now explicitly checks for this error,
  35. ;    and will terminate the tasker if this should ever happen.
  36. ;
  37. ;    Other safeguards include not calling the INT 8 timer interrupt 
  38. ;    chain for early INT8 processing while the scheduler is active.
  39. ;    If INT8_EARLY is used, the tick chain is called from within
  40. ;    the interrupt handler. Since the interrupt could have hit 
  41. ;    while the scheduler was active, and the original INT 8
  42. ;    may have TSRs attached that call DOS, dangerous conflicts are
  43. ;    possible. For the same reason, the scheduler will set the In-DOS
  44. ;    flag while it is active, to keep TSR's activated by other
  45. ;    interrupts (esp. the keyboard) from calling DOS.
  46. ;    Using the same delayed processing as is used in INT 8 in 
  47. ;    the INT 9 handler was tried, but led to lost keystrokes depending
  48. ;    on system load.
  49. ;
  50. ;
  51. ;    Interrupts also are enabled in most other places in the scheduler,
  52. ;    except when traversing the queues.
  53. ;
  54. ;    Having interrupts enabled during the save/restore code also
  55. ;    eliminates a deadlock problem with floating point exceptions
  56. ;    on the 8087, which simplifies saving and restoring the '87 registers.
  57. ;
  58. ;
  59. ;    Version 2.2 eliminates a race condition problem introduced by
  60. ;    the above changes. If a task was made waiting, only the queue head
  61. ;    reflected this. Only after the scheduler had entered the task into
  62. ;    the appropriate queue would it have been safe to enable interrupts,
  63. ;    since an interrupt handler calling (indirectly) tsk_runable would
  64. ;    never see the task as waiting if it hit before this time.
  65. ;    To keep interrupts enabled, the logic was changed in that the
  66. ;    wait action now inserts the task into the appropriate queue
  67. ;    directly, without waiting for the scheduler to do it. Naturally,
  68. ;    this means a change here so that the scheduler doesn't touch
  69. ;    the queue if the task is already enqueued. Only if the task is not
  70. ;    in any queue is the queue head used to determine the place to
  71. ;    enqueue, which now can occur only when making a running task
  72. ;    eligible.
  73. ;
  74. ;**************************  CAUTION:  ***********************************
  75. ;
  76. ;    If the DOS flag is set, the scheduler uses an undocumented feature
  77. ;    of DOS versions >= 3.10 to save and restore certain variables
  78. ;    local to the DOS kernel. This allows for fast, secure task switching
  79. ;    with tasks owning different PSP's. To save and restore the complete
  80. ;    state of DOS on every task switch would not be feasible with any
  81. ;    other documented method.
  82. ;
  83. ;    NOTE that this method relies on certain inner workings of DOS
  84. ;    that may not be compatible with future releases or "compatible"
  85. ;    operating systems. It has been tested with all DOS versions
  86. ;    from 3.10 through 3.20, 3.30, up to PC/MS-DOS 4.01. It
  87. ;    also has been reported to work with DR-DOS 3.41.
  88. ;
  89. ;*************************************************************************
  90. ;
  91.     name    tskasm
  92. ;
  93.     include    tsk.mac
  94.     include    tskdeb.h
  95. ;
  96.     .tsk_model
  97. ;
  98.     public    tsk_scheduler
  99.     public    sched_int
  100. ;
  101.     Pubfunc    schedule
  102.     Pubfunc    yield
  103.     Pubfunc    c_schedule
  104. ;
  105.     Pubfunc    tsk_callfunc
  106.     Pubfunc    tsk_dis_int
  107.     Pubfunc    tsk_ena_int
  108.     Pubfunc    tsk_cli
  109.     Pubfunc    tsk_sti
  110.     Pubfunc    tsk_dseg
  111.     Pubfunc    tsk_flags
  112.     Pubfunc    tsk_outp
  113.     Pubfunc    tsk_inp
  114.     Pubfunc    tsk_inpw
  115.     Pubfunc    tsk_nop
  116.     Pubfunc    tsk_memcpy
  117. ;
  118.     IF    CLOCK_MSEC
  119.     Pubfunc    tsk_timeout
  120.     ENDIF
  121. ;
  122.     IF    CHECKING
  123.     CGlbext    tsk_fatal_pmd
  124.     ENDIF
  125. ;
  126.     IF    DEBUG AND DEB_TSKDIS
  127.     extrn    tsk_debtask: word
  128.     ENDIF
  129.     IF    DEBUG AND DEB_FLASHERS
  130.     Pubfunc    tsk_inccdis
  131.     extrn    tsk_debflash: word
  132.     ENDIF
  133.     IF    DEBUG
  134.     extrn    tsk_dtposn: dword
  135.     ENDIF
  136. ;
  137.     global_ext
  138. ;
  139. ;
  140. LSTACK_SIZE    =    512    ; local stack size in words
  141. ;
  142. INT_FLAG    =    2h    ; Int enable flag in upper byte of flag reg
  143. ;
  144. sr_flags    =    8    ; Offset of flag register on task stack
  145. sr_ds        =    2    ; Offset of DS on task stack
  146. ;
  147. psp_savoff    =    2eh    ; Offset of PSP stack pointer save dword
  148. ;
  149.     IF    FAR_STACKS
  150. ctask_stacks    segment word public 'CTASK_STACKS'
  151.     ELSE
  152.     .tsk_data
  153.     ENDIF
  154. ;
  155.     dw    LSTACK_SIZE dup(?)
  156. slocal_stack    label    word
  157. ;
  158.     IF    FAR_STACKS
  159. ctask_stacks    ends
  160.     .tsk_data
  161.     ENDIF
  162. ;
  163.     .tsk_edata
  164.     .tsk_code
  165. ;
  166. tsk_dgroup    dw    @CTASK_DATA
  167. ;
  168.     IF    FAR_STACKS
  169. stackseg    dw    SEG ctask_stacks
  170.     ELSE
  171. stackseg    equ    <tsk_dgroup>
  172.     ENDIF
  173. ;
  174. ;------------------------------------------------------------------------
  175. ;
  176. ;    enq    Enqueue tcb in queue. For local use only.
  177. ;        entry:    es:di = tcb to enqueue
  178. ;        exit:    -
  179. ;        uses:    ax, cx, si
  180. ;    NOTE:    Interrupts must be disabled on entry.
  181. ;
  182. enq    macro
  183. ;
  184.     push    ds
  185.     lds    cx,es:cqueue.q_next[di]    ; this is NULL if not in queue
  186.     mov    ax,ds
  187.     or    ax,cx
  188.     jnz    enq_end            ; don't touch if enqueued
  189.     lds    si,es:qhead[di]        ; queue head pointer
  190.     mov    ax,ds
  191.     or    ax,si    
  192.     jz    enq_end            ; nothing left to do if queue null
  193. ;
  194.     mov    cx,es:cqueue.q_el.q_prior[di]
  195.     mov    ax,es:cqueue.q_el.q_ini_prior[di]
  196.     mov    es:cqueue.q_el.q_prior[di],ax
  197.     lds    si,q_last[si]        ; last queue element
  198. ;
  199. enq_loop:
  200.     test    q_kind[si],Q_HEAD    ; at head?
  201.     jnz    enq_found        ; then insert
  202.     cmp    q_el.q_prior[si],cx    ; else check priority
  203.     jae    enq_found        ; if above or equal, insert
  204. ;
  205.     lds    si,q_prev[si]        ; backup one element
  206.     jmp    enq_loop        ; and try again
  207. ;
  208. enq_found:
  209.     mov    word ptr es:q_prev[di],si    ; elem->prev = curr
  210.     mov    word ptr es:q_prev+2[di],ds
  211.     mov    ax,word ptr q_next[si]        ; elem->next = curr->next;
  212.     mov    word ptr es:q_next[di],ax
  213.     mov    cx,word ptr q_next+2[si]
  214.     mov    word ptr es:q_next+2[di],cx
  215.     mov    word ptr q_next[si],di        ; curr->next = elem;
  216.     mov    word ptr q_next+2[si],es
  217.     mov    si,ax
  218.     mov    ds,cx
  219.     mov    word ptr q_prev[si],di        ; elem->next->prev = elem
  220.     mov    word ptr q_prev+2[si],es
  221. ;
  222. enq_end:
  223.     pop    ds
  224. ;
  225.     endm
  226. ;
  227. ;    upd_prior: Update priority of tasks in eligible queue.
  228. ;               Only activated if tsk_var_prior is nonzero.
  229. ;
  230. ;    entry:    ds:bx = global variable block
  231. ;    exit:    -
  232. ;    uses:    ax,di,es
  233. ;
  234. ;    NOTE:    Contrary to what earlier versions said, this loop
  235. ;        must be protected by interrupt disable.
  236. ;        Since it steps through a pointer chain, and this
  237. ;        pointer chain might be modified by external events
  238. ;        (a task becoming eligible to run), there is indeed
  239. ;        a danger of race conditions.
  240. ;
  241. upd_prior    macro
  242. ;
  243.     les    di,eligible_queue.q_first[bx]
  244. ;
  245. pinc_loop:
  246.     test    es:q_kind[di],Q_HEAD    ; queue head?
  247.     jnz    updp_end
  248.     inc    es:q_el.q_prior[di]
  249.     jnz    pinc_nxt
  250.     dec    es:q_el.q_prior[di]
  251. pinc_nxt:
  252.     les    di,es:q_next[di]
  253.     jmp    pinc_loop
  254. ;
  255. updp_end:
  256. ;
  257.     endm
  258. ;
  259. ;-------------------------------------------------------------------------
  260. ;
  261.     IF    DEBUG AND DEB_TSKDIS
  262. ;
  263. fill8    macro
  264.     mov    cx,10
  265.     mov    ax,0720h
  266.     rep stosw
  267.     endm
  268. ;
  269. @strcpy8    proc    near
  270.     mov    cx,8
  271.     mov    ah,7
  272. strcpyl:
  273.     lodsb
  274.     or    al,al
  275.     jz    strcpyfill
  276.     stosw
  277.     loop    strcpyl
  278. ;
  279. strcpyfill:
  280.     add    cx,2
  281.     mov    al,' '
  282.     rep stosw
  283.     ret
  284. ;
  285. @strcpy8    endp
  286. ;
  287.     ENDIF
  288. ;
  289.     IF    DEBUG AND DEB_FLASHERS
  290. ;
  291. ;    tsk_inccdis - increment counter on display
  292. ;
  293. ;    Entry:    AX is counter first digit offset
  294. ;        DS is CTask dgroup
  295. ;
  296. ;    Uses:    All registers preserved
  297. ;
  298. Localfunc    tsk_inccdis
  299. ;
  300.     push    ds
  301.     push    si
  302.     push    cx
  303.     lds    si,tsk_dtposn
  304.     add    si,ax
  305.     mov    cx,DEBFLASH_NDIGS
  306.     add    si,(DEBFLASH_NDIGS - 1) * 2
  307. incdis_loop:
  308.     inc    byte ptr [si]
  309.     cmp    byte ptr [si],'9'
  310.     jbe    incdis_end
  311.     mov    byte ptr [si],'0'
  312.     sub    si,2
  313.     loop    incdis_loop
  314. incdis_end:
  315.     pop    cx
  316.     pop    si
  317.     pop    ds
  318.     ret
  319. ;
  320. tsk_inccdis    endp
  321. ;
  322.     ENDIF
  323. ;
  324. ;-------------------------------------------------------------------------
  325. ;
  326. ;    The scheduler. Note that this routine is entered with the stack
  327. ;    set up as for an interrupt handler.
  328. ;
  329. tsk_scheduler    proc    far
  330. ;
  331.     cli            ; better safe than sorry
  332. ;
  333. ;    First, check if we're already in the scheduler. This might
  334. ;    happen if an interrupt handler schedules, and it would have
  335. ;    catastrophic results if the scheduler would be re-entered.
  336. ;    Previous versions used a code-segment based variable to store
  337. ;    this flag. Starting at version 2.0, the flag is located in the 
  338. ;    global variable block, to support ROM-based implementations, 
  339. ;    and to avoid multiple entries into the scheduler when code
  340. ;    sharing is not used in secondary invocations.
  341. ;
  342. ;    Version 2.1 sets the 'pretick' flag when the scheduler is busy.
  343. ;    This allows an immediate re-schedule if the schedule request
  344. ;    hit after loading the new task.
  345. ;
  346.     push    ds
  347.     push    bx
  348.     mov    ds,cs:tsk_dgroup
  349.     IF    SINGLE_DATA
  350.     cmp    tsk_glob_rec.in_sched,0    ; already in the scheduler?
  351.     je    sched_ok    ; continue if not
  352.     mov    tsk_glob_rec.pretick,1    ; mark schedule pending
  353.     ELSE
  354.     lds    bx,tsk_global
  355.     cmp    in_sched[bx],0    ; already in the scheduler?
  356.     je    sched_ok    ; continue if not
  357.     mov    pretick[bx],1    ; mark schedule pending
  358.     ENDIF
  359.     pop    bx
  360.     pop    ds        ; else return immediately
  361.     iret
  362. ;
  363. ;    Not the second time into the scheduler, set the in_sched flag,
  364. ;    and load the current task's TCB.
  365. ;    If we're running under DOS, additionally set the In-DOS flag
  366. ;    for the reasons outlined above.
  367. ;
  368. sched_ok:
  369.     IF    SINGLE_DATA
  370.     inc    tsk_glob_rec.in_sched
  371.     IF    DOS
  372.     push    ds
  373.     lds    bx,tsk_glob_rec.dos_in_use
  374.     inc    byte ptr ds:[bx]
  375.     pop    ds
  376.     ENDIF
  377.     lds    bx,tsk_glob_rec.current_task
  378.     ELSE
  379.     inc    in_sched[bx]
  380.     IF    DOS
  381.     push    ds
  382.     push    bx
  383.     lds    bx,dos_in_use[bx]
  384.     inc    byte ptr [bx]
  385.     pop    bx
  386.     pop    ds
  387.     ENDIF
  388.     lds    bx,current_task[bx]
  389.     ENDIF
  390.     push    ax
  391.     mov    ax,ds
  392.     or    ax,bx
  393.     pop    ax
  394. ;
  395. ;    Registers are stored in the TCB to keep stack usage minimal.
  396. ;    If there is no current task (i.e. it has been killed),
  397. ;    we can't store the registers.
  398. ;
  399.     IF    CHECKING
  400.     jnz    chk_rsave
  401.     jmp    no_rsave
  402. chk_rsave:
  403.     CHECK_TCBPTR_R    ds,bx,"scheduler: current task"
  404.     ELSE
  405.     jz    no_rsave
  406.     ENDIF
  407.     cmp    state[bx],ST_RUNNING
  408.     jne    store_regs
  409.     mov    state[bx],ST_ELIGIBLE
  410. ;
  411. store_regs:
  412.     sti
  413.     mov    t_sp[bx],sp
  414.     mov    t_ss[bx],ss
  415.     mov    t_ax[bx],ax
  416.     mov    t_cx[bx],cx
  417.     mov    t_dx[bx],dx
  418.     mov    t_bp[bx],bp
  419.     mov    t_si[bx],si
  420.     mov    t_di[bx],di
  421.     mov    t_es[bx],es
  422.     mov    bp,sp
  423.     or    byte ptr sr_flags+1[bp],INT_FLAG ; enable interrupts on return
  424.     mov    ax,sr_ds[bp]
  425.     mov    t_ds[bx],ax
  426. ;
  427. ;    This is the entry when restarting the scheduler.
  428. ;    Note that registers don't have to be saved again.
  429. ;
  430. sched_restart:
  431. no_rsave:
  432.     cli
  433.     mov    ss,cs:stackseg        ; switch to local stack
  434.     mov    sp,offset slocal_stack
  435.     sti
  436.     cld
  437.     mov    ds,cs:tsk_dgroup    ; establish addressing of our vars
  438.     IF    DEBUG AND DEB_FLASHERS
  439.     cmp    tsk_debflash,0
  440.     je    debdd0
  441.     mov    ax,DEBP_CNTSCHED
  442.     call    tsk_inccdis
  443. debdd0:
  444.     ENDIF
  445.     IF    DEBUG AND DEB_TSKDIS
  446.     cmp    tsk_debtask,0
  447.     je    debdd1
  448.     les    di,tsk_dtposn
  449.     mov    byte ptr es:[di],'*'
  450. debdd1:
  451.     ENDIF
  452.     IF    SINGLE_DATA
  453.     mov    bx,offset tsk_glob_rec
  454.     ELSE
  455.     lds    bx,tsk_global
  456.     ENDIF
  457. ;
  458.     cmp    var_prior[bx],0
  459.     je    no_var_pri
  460. ;
  461.     cli
  462.     upd_prior            ; update priorities in eligible queue
  463.     sti
  464. ;
  465. no_var_pri:
  466. ;
  467.     and    preempt[bx],1        ; Turn off temp preempt flag
  468. ;
  469.     les    di,current_task[bx]    ; get current tcb again
  470.     mov    ax,es            ; check if NULL (current task killed)
  471.     or    ax,di
  472.     IF    NOT CHECKING
  473.     jz    no_current
  474.     ELSE
  475.     jnz    do_chkstk
  476.     jmp    no_current
  477. ;
  478. do_chkstk:
  479.     push    ds
  480.     lds    si,es:stkbot[di]
  481.     mov    cx,8
  482.     xor    ah,ah
  483. chkstk:
  484.     lodsb
  485.     cmp    ah,al
  486.     jne    stkoflo
  487.     inc    ah
  488.     loop    chkstk
  489.     jmp    short chkstend
  490. ;
  491. stkmsg    db    "Stack Overflow",0
  492. ;
  493. stkoflo:
  494.     callp    tsk_fatal_pmd,<<cs,#stkmsg>>
  495. ;
  496. chkstend:
  497.     pop    ds
  498.     ENDIF
  499. ;
  500. ;    If there is a scheduler-entry routine in the current task's
  501. ;    TCB, call it. (This is used by the DOS handler module)
  502. ;    NOTE: Interrupts are still enabled.
  503. ;
  504.     IF    DOS
  505.     mov    ax,word ptr es:sched_ent_func[di]
  506.     or    ax,word ptr es:sched_ent_func[di]+2
  507.     jz    no_schentcall
  508. ;
  509.     push    es
  510.     push    di
  511.     push    ds
  512.     push    bx
  513.     call    es:sched_ent_func[di]
  514.     pop    bx
  515.     pop    ds
  516.     pop    di
  517.     pop    es
  518. ;
  519. no_schentcall:
  520.     ENDIF
  521. ;
  522.     cli
  523.     enq                ; Enqueue current task
  524.     sti
  525. ;
  526. no_current:
  527.     lea    si,eligible_queue[bx]
  528. ;
  529. ;    Now we enter an enabled loop if there is no task in the
  530. ;    eligible queue.
  531. ;
  532. wait_elig:
  533.     IF    DEBUG AND DEB_TSKDIS
  534.     push    ds
  535.     mov    ds,cs:tsk_dgroup
  536.     cmp    tsk_debtask,0
  537.     je    debdd2
  538.     les    di,tsk_dtposn
  539.     add    di,DEBP_ELIGTSK
  540.     pop    ds
  541.     push    ds
  542.     push    si
  543.     lds    si,q_first[si]
  544.     test    q_kind[si],Q_HEAD
  545.     jnz    deb101
  546.     push    si
  547.     lea    si,tname.nname[si]
  548.     call    @strcpy8
  549.     pop    si
  550.     lds    si,q_next[si]
  551.     test    q_kind[si],Q_HEAD
  552.     jnz    deb102
  553.     lea    si,tname.nname[si]
  554.     call    @strcpy8
  555.     jmp    short deb105
  556. ;
  557. deb101:
  558.     fill8
  559. deb102:
  560.     fill8
  561. deb105:
  562.     pop    si
  563. debdd2:
  564.     pop    ds
  565.     ENDIF
  566.     jmp    $+2            ; allow ints
  567.     cli
  568.     les    di,q_first[si]
  569.     test    es:q_kind[di],Q_HEAD
  570.     jz    not_empty        ; jump if not
  571. ;
  572.     sti                       ; enable interrupts
  573.     IF    DEBUG AND DEB_FLASHERS
  574.     cmp    tsk_debflash,0
  575.     je    debdidle
  576.     mov    ax,DEBP_CNTIDLE
  577.     call    tsk_inccdis
  578. debdidle:
  579.     ENDIF
  580.     jmp    $+2
  581.     jmp    wait_elig
  582. ;
  583. ;    Eligible queue not empty, activate first eligible task.
  584. ;    First, take it off the queue.
  585. ;    The preemption tick flag is cleared here. If it should be
  586. ;    set by an interrupt handler trying to schedule after this point,
  587. ;    we immediately re-schedule when hitting the end.
  588. ;
  589. not_empty:
  590.     CHECK_TCBPTR_R    es,di,"scheduler: eligible task"
  591.     mov    pretick[bx],0        ; No preemption tick
  592.     push    di
  593.     push    es
  594.     mov    bp,sp            ; address new pointer thru BP
  595. ;
  596.     mov    ax,word ptr es:cqueue.q_next[di] ; next in eligible queue
  597.     mov    cx,word ptr es:cqueue.q_next+2[di]
  598.     mov    word ptr es:cqueue.q_next[di],0    ; mark not in queue
  599.     mov    word ptr es:cqueue.q_next+2[di],0
  600.     mov    di,ax
  601.     mov    es,cx
  602.     mov    word ptr q_first[si],di
  603.     mov    word ptr q_first+2[si],es    ; eligible_queue.first = next
  604.     mov    word ptr es:q_prev[di],si
  605.     mov    word ptr es:q_prev+2[di],ds    ; next->prev = eligible_queue
  606. ;
  607. ;    Again, interrupts can be enabled here.
  608. ;
  609.     sti
  610. ;
  611. ;    Now check if the new task is the same as the old one.
  612. ;    If that's the case, we skip the save/restore function calls,
  613. ;    and the DOS variable copy.
  614. ;
  615.     les    di,current_task[bx]    ; the previously running task
  616.     mov    ax,es
  617.     cmp    ax,[bp]            ; same as the new one ?
  618.     jne    chk_sav            ; jump if not same segment
  619.     cmp    di,2[bp]
  620.     jne    chk_sav            ; jump if not same offset
  621.     jmp    sched_complete        ; don't save/restore if same task
  622. ;
  623. chk_sav:
  624.     or    ax,di
  625.     jz    set_new_task        ; don't save if no previous
  626. ;
  627. ;    First, call the save function if one is defined in the old TCB.
  628. ;
  629.     mov    ax,word ptr es:save_func[di]
  630.     or    ax,word ptr es:save_func+2[di]
  631.     jz    no_savfcall        ; jump if no save function
  632. ;
  633.     push    ds            ; save our registers
  634.     push    bx
  635.     push    es
  636.     push    di
  637. ;
  638.     push    es            ; push TCB address
  639.     push    es            ; push segment again
  640.     pop    ds            ; and put into DS
  641.     push    di
  642. ;    
  643.     call    es:save_func[di]    ; call function
  644.         add     sp,4
  645. ;
  646.         pop     di            ; restore registers
  647.         pop     es
  648.         pop     bx
  649.         pop     ds
  650. ;
  651. ;    Save the DOS variables, and the DOS-related interrupt vectors
  652. ;    in the old TCB.
  653. ;
  654. no_savfcall:
  655.     IF    DOS
  656.     mov    es:t_new[di],0        ; task is no longer new
  657. ;
  658.     push    ds
  659.     push    di
  660.     mov    ax,ds
  661.     mov    ds,es:base_psp[di]    ; get base PSP address
  662.     mov    si,psp_savoff        ; offset to save (caller's SS:SP)
  663.     add    di,psp_sssp        ; destination
  664.     movsw                ; save two words
  665.     movsw
  666.     mov    ds,ax
  667.     mov    cx,l_swap[bx]        ; swap area addr & size
  668.     jcxz    no_swap_sav
  669.     lds    si,dos_vars[bx]
  670.     rep movsb
  671. ;
  672. no_swap_sav:
  673.     xor    cx,cx            ; copy int21-24 interrupt vectors
  674.     mov    ds,cx
  675.     mov    si,21h*4
  676.     mov    cx,8
  677.     rep movsw
  678. ;
  679.     pop    di
  680.     pop    ds
  681.     ENDIF
  682. ;
  683. ;    if EMS support is installed, call the page map save function.
  684. ;
  685.     IF    EMS
  686.     mov    ax,word ptr ems_save[bx]
  687.     or    ax,word ptr ems_save+2[bx]
  688.     jz    no_emssav
  689.     call    ems_save[bx]
  690. ;
  691. no_emssav:
  692.     ENDIF
  693. ;
  694. ;    If the NDP is active, save the NDP context.
  695. ;
  696.     IF    NDP
  697.     test    es:flags[di],F_USES_NDP
  698.     jz    no_ndpsave
  699.     wait
  700.     fsave    es:ndpsave[di]
  701. ;
  702. no_ndpsave:
  703.     ENDIF
  704. ;
  705. ;    Save complete. The new TCB becomes the current task.
  706. ;
  707. set_new_task:
  708.     IF    DEBUG AND DEB_TSKDIS
  709.     push    ds
  710.     mov    ds,cs:tsk_dgroup
  711.     cmp    tsk_debtask,0
  712.     je    deb205
  713.     les    di,tsk_dtposn
  714.     pop    ax            ; saved DS
  715.     pop    ds            ; the new task's TCB
  716.     pop    si
  717.     push    si
  718.     push    ds
  719.     push    ax            ; saved DS
  720.     mov    byte ptr es:[di],'+'
  721.     add    di,DEBP_CURRTSK
  722.     lea    si,tname.nname[si]
  723.     call    @strcpy8
  724.     pop    ds
  725.     push    ds
  726.     lds    si,current_task[bx]    ; the previous task
  727.     lea    si,tname.nname[si]
  728.     call    @strcpy8
  729.     pop    ds
  730.     push    ds
  731.     lds    si,eligible_queue.q_first[bx]
  732.     test    q_kind[si],Q_HEAD
  733.     jnz    deb201
  734.     push    si
  735.     lea    si,tname.nname[si]
  736.     call    @strcpy8
  737.     pop    si
  738.     lds    si,q_next[si]
  739.     test    q_kind[si],Q_HEAD
  740.     jnz    deb202
  741.     lea    si,tname.nname[si]
  742.     call    @strcpy8
  743.     jmp    short deb205
  744. ;
  745. deb201:
  746.     fill8
  747. deb202:
  748.     fill8
  749. deb205:
  750.     pop    ds
  751.     ENDIF
  752.     pop    es
  753.     pop    di
  754.     cli
  755.     mov    word ptr current_task[bx],di     ; set tcb into current
  756.     mov    word ptr current_task+2[bx],es
  757.     sti
  758. ;
  759. ;    if EMS support is installed, call the page map restore function.
  760. ;
  761.     IF    EMS
  762.     mov    ax,word ptr ems_rest[bx]
  763.     or    ax,word ptr ems_rest+2[bx]
  764.     jz    no_emsrest
  765.     call    ems_rest[bx]
  766. no_emsrest:
  767.     ENDIF
  768. ;
  769. ;    Now check for a restore function in the new TCB.
  770. ;
  771.     mov    ax,word ptr es:rest_func[di]
  772.     or    ax,word ptr es:rest_func+2[di]
  773.     jz    no_resfcall            ; jump if no restore function
  774. ;
  775.     push    bx                ; save our regs
  776.     push    ds
  777.     push    di
  778.     push    es
  779. ;
  780.     push    es                ; push TCB addr
  781.     push    es                ; push segment again
  782.     pop    ds                ; and put into DS
  783.     push    di
  784. ;
  785.     call    es:rest_func[di]    ; call restore function
  786.         add     sp,4
  787. ;
  788.         pop     es
  789.         pop     di
  790.         pop     ds
  791.         pop     bx
  792. ;
  793. no_resfcall:
  794. ;
  795. ;    If NDP is enabled, restore 80x87 context.
  796. ;
  797.     IF    NDP
  798.     test    es:flags[di],F_USES_NDP
  799.     jz    no_ndprest
  800.     cmp    es:t_new[di],0        ; is this TCB a fresh one?
  801.     jne    no_ndprest        ; then NDP state isn't valid
  802. ;
  803.     wait
  804.     frstor    es:ndpsave[di]
  805. ;
  806. no_ndprest:
  807.     ENDIF
  808. ;
  809. ;    Restore DOS variables and int vectors from new TCB.
  810. ;
  811.     IF    DOS
  812.     cmp    es:t_new[di],0        ; is this TCB a fresh one?
  813.     jne    sched_complete        ; then the DOS info isn't valid.
  814. ;
  815.     push    di
  816.     push    es
  817.     push    ds
  818.     mov    dx,ds
  819.     mov    ax,es
  820.     mov    ds,ax
  821.     mov    si,di
  822.     add    si,base_psp
  823.     lodsw
  824.     mov    es,ax
  825.     mov    di,psp_savoff        ; offset to restore (caller's SS:SP)
  826.     cli
  827.     movsw                ; restore two words
  828.     movsw
  829.     sti
  830. ;
  831.     mov    ax,ds
  832.     mov    ds,dx
  833.     mov    cx,l_swap[bx]
  834.     jcxz    no_swap_rest
  835.     les    di,dos_vars[bx]
  836.     mov    ds,ax
  837.     rep movsb
  838. ;
  839. no_swap_rest:
  840.     mov    ds,ax
  841.     mov    di,21h*4        ; restore int21-24
  842.     xor    cx,cx
  843.     mov    es,cx
  844.     mov    cx,8
  845.     cli
  846.     rep movsw
  847.     sti
  848. ;
  849.     pop    ds
  850.     pop    es
  851.     pop    di
  852.     ENDIF
  853. ;
  854. ;    And that's it. Wrap it up by resetting the priority, 
  855. ;    restoring the registers, resetting the in_sched flag, 
  856. ;    and returning to the task.
  857. ;    Note: to allow keeping interrupts enabled as long as possible,
  858. ;    the in_sched flag is cleared after reloading the registers.
  859. ;
  860. sched_complete:
  861.     mov    ax,es:q_el.q_ini_prior[di]    ; reset current tasks priority
  862.     mov    es:q_el.q_prior[di],ax
  863.     mov    es:state[di],ST_RUNNING        ; set task state
  864. ;
  865.     push    es
  866.     push    di
  867.     IF    DEBUG AND DEB_TSKDIS
  868.     mov    ds,cs:tsk_dgroup
  869.     cmp    tsk_debtask,0
  870.     je    debddx
  871.     les    di,tsk_dtposn
  872.     mov    byte ptr es:[di],' '
  873. debddx:
  874.     ENDIF
  875.     pop    bx
  876.     pop    ds
  877.     mov    es,t_es[bx]            ; restore all registers
  878.     mov    di,t_di[bx]
  879.     mov    si,t_si[bx]
  880.     mov    bp,t_bp[bx]
  881.     mov    dx,t_dx[bx]
  882.     mov    cx,t_cx[bx]
  883.     mov    ax,t_ax[bx]
  884.     mov    ss,t_ss[bx]
  885.     mov    sp,t_sp[bx]
  886. ;
  887. ;    All done except for resetting the in_sched flag.
  888. ;    Now we check the preemption tick flag, and restart the schedule
  889. ;    if it is set. To avoid needless reschedules, the priority of the
  890. ;    current task is compared to the priority of the first task in
  891. ;    the eligible queue. If there is no task eligible, or if its
  892. ;    priority is less or equal to the current task, the current task
  893. ;    is allowed to continue.
  894. ;
  895.     mov    ds,cs:tsk_dgroup
  896.     cli
  897.     IF    SINGLE_DATA
  898.     cmp    tsk_glob_rec.pretick,0
  899.     je    sched_end
  900.     mov    bx,offset tsk_glob_rec
  901.     ELSE
  902.     lds    bx,tsk_global
  903.     cmp    pretick[bx],0
  904.     je    sched_end
  905.     ENDIF
  906. ;
  907.     push    es
  908.     push    di
  909.     les    di,current_task[bx]    ; get current tcb again
  910.     lds    bx,eligible_queue.q_first[bx]
  911.     test    q_kind[bx],Q_HEAD
  912.     jnz    no_restart
  913.     mov    bx,cqueue.q_el.q_prior[bx]    ; prior of eligible task
  914.     cmp    bx,es:cqueue.q_el.q_prior[di]    ; prior of current task
  915.     jbe    no_restart
  916. ;
  917. ;    Note: registers don't have to be popped, since the stack
  918. ;    is reset anyway at the sched_restart entry.
  919. ;
  920. do_rest:
  921.     jmp    sched_restart
  922. ;
  923. ;
  924. no_restart:
  925.     pop    di
  926.     pop    es
  927.     mov    ds,cs:tsk_dgroup
  928.     IF    NOT SINGLE_DATA
  929.     lds    bx,tsk_global
  930.     ENDIF
  931. ;
  932. sched_end:
  933.     IF    SINGLE_DATA
  934.     mov    tsk_glob_rec.in_sched,0
  935.     IF    DOS
  936.     lds    bx,tsk_glob_rec.dos_in_use
  937.     dec    byte ptr [bx]
  938.     ENDIF
  939.     ELSE
  940.     mov    in_sched[bx],0
  941.     IF    DOS
  942.     lds    bx,dos_in_use[bx]
  943.     dec    byte ptr [bx]
  944.     ENDIF
  945.     ENDIF
  946. ;
  947.     pop    bx
  948.     pop    ds
  949.     iret
  950. ;
  951. tsk_scheduler    endp
  952. ;
  953. ;
  954. ;--------------------------------------------------------------------------
  955. ;
  956. ;
  957. ;    sched_int  
  958. ;
  959. ;    Is the scheduler entry for interrupt handlers.
  960. ;    It checks if preemption is allowed, returning if not.
  961. ;    The stack is assumed to be set up as on interrupt entry.
  962. ;    
  963. sched_int    proc    far
  964. ;
  965.     push    ds
  966.     push    bx
  967.     mov    ds,cs:tsk_dgroup
  968.     IF    SINGLE_DATA
  969.     mov    bx,offset tsk_glob_rec
  970.     ELSE
  971.     lds    bx,tsk_global
  972.     ENDIF
  973.     cmp    preempt[bx],0        ; preempt flags 0?
  974.     jne    no_sched1        ; no scheduling if set
  975.     lds    bx,current_task[bx]    ; current running task
  976.     test    flags[bx],F_CRIT    ; preemption allowed for this task?
  977.     jnz    no_sched        ; no scheduling if flag set
  978.     pop    bx            ; else go schedule
  979.     pop    ds
  980.     jmp    tsk_scheduler
  981. ;
  982. no_sched:
  983.     mov    ds,cs:tsk_dgroup
  984.     IF    SINGLE_DATA
  985.     mov    bx,offset tsk_glob_rec
  986.     ELSE
  987.     lds    bx,tsk_global
  988.     ENDIF
  989. no_sched1:
  990.     mov    pretick[bx],1        ; Mark preemption pending
  991.     pop    bx
  992.     pop    ds
  993.     iret
  994. ;
  995. sched_int    endp
  996. ;
  997. ;
  998. ;    void far schedule (void)
  999. ;
  1000. ;    Entry for calling the scheduler. Rearranges the stack to
  1001. ;    contain flags.
  1002. ;    NOTE: Uses ax,bx.
  1003. ;
  1004. Globalfunc    schedule
  1005. ;
  1006.     IF    NEAR_CODE
  1007.     pop    ax
  1008.     pushf
  1009.     push    cs
  1010.     push    ax
  1011.     ELSE
  1012.     pop    ax
  1013.     pop    bx
  1014.     pushf
  1015.     push    bx
  1016.     push    ax
  1017.     ENDIF
  1018.     cli
  1019.     jmp    tsk_scheduler
  1020. ;
  1021. schedule    endp
  1022. ;
  1023. ;
  1024. ;    void far yield (void)
  1025. ;
  1026. ;    Entry for calling the scheduler with priority temporarily
  1027. ;       set to zero. Rearranges the stack to contain flags.
  1028. ;    NOTE: Uses ax,bx, es.
  1029. ;
  1030. Globalfunc    yield
  1031. ;
  1032.     IF    NEAR_CODE
  1033.     pop    ax
  1034.     pushf
  1035.     push    cs
  1036.     push    ax
  1037.     ELSE
  1038.     pop    ax
  1039.     pop    bx
  1040.     pushf
  1041.     push    bx
  1042.     push    ax
  1043.     ENDIF
  1044. ;
  1045.     IF    SINGLE_DATA
  1046.     IFDEF    LOAD_DS
  1047.     mov    es,cs:tsk_dgroup
  1048.     les    bx,es:tsk_glob_rec.current_task
  1049.     ELSE
  1050.     les    bx,tsk_glob_rec.current_task
  1051.     ENDIF
  1052.     ELSE
  1053.     IFDEF    LOAD_DS
  1054.     mov    es,cs:tsk_dgroup
  1055.     les    bx,es:tsk_global
  1056.     ELSE
  1057.     les    bx,tsk_global
  1058.     ENDIF
  1059.         les     bx,es:current_task[bx]
  1060.     ENDIF
  1061.         cli
  1062.         mov     es:q_el.q_prior[bx],0
  1063.     jmp    tsk_scheduler
  1064. ;
  1065. yield    endp
  1066. ;
  1067. ;
  1068. ;    void far c_schedule (void)
  1069. ;
  1070. ;    Entry for conditionally calling the scheduler. Rearranges 
  1071. ;    the stack to contain flags, then jumps to _sched_int.
  1072. ;    NOTE: Uses ax,bx.
  1073. ;
  1074. Globalfunc    c_schedule
  1075. ;
  1076.     IF    NEAR_CODE
  1077.     pop    ax
  1078.     pushf
  1079.     push    cs
  1080.     push    ax
  1081.     ELSE
  1082.     pop    ax
  1083.     pop    bx
  1084.     pushf
  1085.     push    bx
  1086.     push    ax
  1087.     ENDIF
  1088.     cli
  1089.     jmp    sched_int
  1090. ;
  1091. c_schedule    endp
  1092. ;
  1093. ;--------------------------------------------------------------------------
  1094. ;
  1095. ;    void tsk_callfunc (farptr funcad, farptr param)
  1096. ;
  1097. ;    Calls the given function, placing the segment address of
  1098. ;    the parameter into the DS register.
  1099. ;
  1100. Localfunc    tsk_callfunc,<uses ds, funcad: far ptr, param: far ptr>
  1101. ;
  1102.     lds    ax,param
  1103.     push    ds
  1104.     push    ax
  1105.     call    funcad
  1106.     add    sp,4
  1107.     ret
  1108. tsk_callfunc    endp
  1109. ;
  1110. ;
  1111. ;    word tsk_dseg (void)
  1112. ;
  1113. ;    Returns current contents of DS register.
  1114. ;
  1115. Globalfunc    tsk_dseg
  1116.     mov    ax,ds
  1117.     ret
  1118. tsk_dseg    endp
  1119. ;
  1120. ;
  1121. ;    word tsk_flags (void)
  1122. ;
  1123. ;    Returns current contents of Flag register.
  1124. ;
  1125. Globalfunc    tsk_flags
  1126.     pushf
  1127.     pop    ax
  1128.     ret
  1129. tsk_flags    endp
  1130. ;
  1131. ;
  1132. ;    int tsk_dis_int (void)
  1133. ;
  1134. ;    Returns current state of the interrupt flag (1 if ints were 
  1135. ;    enabled), then disables interrupts.
  1136. ;
  1137. Globalfunc    tsk_dis_int
  1138. ;
  1139.     pushf
  1140.     pop    ax
  1141.     mov    al,ah
  1142.     shr    al,1
  1143.     and    ax,1
  1144.     cli
  1145.     ret
  1146. ;
  1147. tsk_dis_int    endp
  1148. ;
  1149. ;
  1150. ;    void far tsk_ena_int (int state)
  1151. ;
  1152. ;    Enables interrupts if 'state' is nonzero.
  1153. ;
  1154. Globalfunc    tsk_ena_int,<istate: word>
  1155. ;
  1156.     cmp    istate,0
  1157.     je    teiend
  1158.     sti
  1159. teiend:
  1160.     ret
  1161. ;
  1162. tsk_ena_int    endp
  1163. ;
  1164. ;
  1165. ;    tsk_cli/tsk_sti: disable/enable int
  1166. ;    NOTE: These routines are normally replaced by intrinsics.
  1167. ;
  1168. Globalfunc    tsk_cli
  1169.     cli
  1170.     ret
  1171. tsk_cli    endp
  1172. ;
  1173. ;
  1174. Globalfunc tsk_sti
  1175.     sti
  1176.     ret
  1177. tsk_sti    endp
  1178. ;
  1179. ;
  1180. ;    tsk_inp/tsk_outp: input/output from/to port
  1181. ;    NOTE: These routines are normally replaced by intrinsics,
  1182. ;          except for Turbo C tsk_inpw.
  1183. ;
  1184. Globalfunc tsk_inp,<port: word>
  1185. ;
  1186.     mov    dx,port
  1187.     in    al,dx
  1188.     xor    ah,ah
  1189.     ret
  1190. ;
  1191. tsk_inp    endp
  1192. ;
  1193. ;
  1194. Globalfunc tsk_inpw,<port: word>
  1195. ;
  1196.     mov    dx,port
  1197.     in    ax,dx
  1198.     ret
  1199. ;
  1200. tsk_inpw    endp
  1201. ;
  1202. ;
  1203. Globalfunc tsk_outp,<port: word, val: word>
  1204. ;
  1205.     mov    dx,port
  1206.     mov    al,byte ptr(val)
  1207.     out    dx,al
  1208.     ret
  1209. ;
  1210. tsk_outp    endp
  1211. ;
  1212. ;
  1213. ;    void tsk_nop (void)
  1214. ;
  1215. ;    Do nothing. Used for very short delays.
  1216. ;
  1217. Globalfunc    tsk_nop
  1218. ;
  1219.     jmp    short tnop1
  1220. tnop1:
  1221.     jmp    short tnop2
  1222. tnop2:
  1223.     ret
  1224. ;
  1225. tsk_nop    endp
  1226. ;
  1227. ;
  1228. ;    void tsk_memcpy (farptr dest, farptr src, word nbytes)
  1229. ;
  1230. Globalfunc    tsk_memcpy,<uses ds si di, dest: far ptr, src: far ptr, len: word>
  1231. ;
  1232.     mov    cx,len
  1233.     lds    si,src
  1234.     les    di,dest
  1235.     xor    bx,bx
  1236.     shr    cx,1
  1237.     rcl    bx,1
  1238.     jcxz    cpy_byte
  1239.     rep movsw
  1240. cpy_byte:
  1241.     or    bx,bx
  1242.     jz    cpy_end
  1243.     movsb
  1244. cpy_end:
  1245.     ret
  1246. ;
  1247. tsk_memcpy    endp
  1248. ;
  1249. ;
  1250.     IF    CLOCK_MSEC
  1251. ;
  1252. ;    dword Localfunc tsk_timeout (dword tout)
  1253. ;
  1254. ;    Translates milliseconds to clock ticks using integer arithmetic.
  1255. ;    Routine provided by Chris Blum.
  1256. ;
  1257. Localfunc    tsk_timeout,<tout:dword>
  1258. ;
  1259.     mov    dh,byte ptr tout
  1260.     mov    dl,byte ptr tout+3
  1261.     mov    ax,word ptr tout+1
  1262.     or    dx,dx
  1263.     jnz    tsk_timmax
  1264.     or    ax,ax
  1265.     jz    tsk_timok
  1266. tsk_timmax:
  1267.     push    ds
  1268.     mov    ds,cs:tsk_dgroup
  1269.     IF    SINGLE_DATA
  1270.     mov    bx,offset tsk_glob_rec
  1271.     ELSE
  1272.     lds    bx,tsk_global
  1273.     ENDIF
  1274.     mov    cx,tick_factor[bx]
  1275.     pop    ds
  1276.     xor    dh,dh
  1277.     cmp    cx,dx
  1278.     jbe    tsk_timhi
  1279.     div    cx
  1280.     mov    bx,ax
  1281.     xor    al,al
  1282.     mov    ah,byte ptr tout
  1283.     div    cx
  1284.     inc    cx
  1285.     shr    cx,1
  1286.     cmp    dx,cx
  1287.     mov    dx,bx
  1288.     jb    tsk_timmin
  1289.     add    ax,1
  1290.     adc    dx,0
  1291.     jnc    tsk_timok
  1292. tsk_timhi:
  1293.     mov    ax,0ffffh
  1294.     mov    dx,ax
  1295.     jmp short tsk_timok
  1296. tsk_timmin:
  1297.     or    ax,ax
  1298.     jnz    tsk_timok
  1299.     or    dx,dx
  1300.     jnz    tsk_timok
  1301.     inc    ax
  1302. tsk_timok:
  1303.     ret
  1304. ;
  1305. tsk_timeout    endp
  1306. ;
  1307.     ENDIF
  1308. ;
  1309.     .tsk_ecode
  1310.     end
  1311.  
  1312.