/* TCMAIN.C - TCtask - Main routines for task handling. TCtask - a Multitasking Kernel for C V1.1 T.Wagner V1.2 TECON Ltd. */ #include #include "tsk.h" #include "tclocal.h" #define STACKSIZE 3000 /* The task queues: All tasks using a timeout, either through "t_delay" or an event wait, are enqueued into the "tsk_timer" queue, using the "timerq" link. All tasks eligible for running are enqueued in "tsk_eligible". The tcb-address of the current running task is stored in "tsk_current". */ extern word POOLSIZE_TCT; tlinkptr _Near tsk_timer; tcbptr _Near tsk_eligible; tcbptr _Near tsk_current; /* System flags: tsk_preempt is zero if preemption is allowed. Bit 0 is set if preemption has been disabled globally. Bit 1 is set for temporary disabling preemption. Temporary preemption is automatically removed by the scheduler. tsk_pretick is nonzero if a schedule request from an interrupt handler was rejected due to tsk_preempt nonzero. This allows an immediate scheduling whenever tsk_preempt is set to 0. tsk_var_prior Can be set nonzero to enable variable priority. Variable priority will increase the priority of eligible tasks on each scheduler call while they are waiting to be executed, so that low priority tasks will slowly get to the head of the eligible queue, getting a chance to be run. With variable priority off, lower priority tasks will never be executed while higher priority tasks are eligible. */ byte _Near tsk_preempt; byte _Near tsk_pretick; byte _Near tsk_var_prior; /* --------------------------------------------------------------------- */ /* The tcb's of the standard tasks. timer_tcb is the tcb for the timer task. This task waits for the tsk_timer_counter, which is increased on every timer tick. It processes the entries in the timeout queue. int9_tcb is the tcb for the int9 chain task. This task waits for the tsk_int9_counter, which is increased on every system timer tick. It then chains to the previous timer interrupt entry. main_tcb is the "main" task which called "install_tasker". This task has no separate stack, rather the stack on entry to the scheduler is used. */ counter _Near tsk_timer_counter; local tcb timer_tcb; local tcb main_tcb; local char timer_stack [STACKSIZE]; //#if (IBM) //counter _Near tsk_int9_counter; //local tcb int9_tcb; //local char int9_stack [STACKSIZE]; //#endif #if (CLOCK_MSEC) double tick_factor; #endif word _Near ticks_per_sec; #if (TSK_NAMED) namerec tsk_name_list; #endif /* Un-Install-Function pointers for the optional serial and printer drivers. If ports are installed, the driver inserts the address of a remove-function here, to be called on removal of the main tasker. */ funcptr mpo8_remove_func = NULL; funcptr ad8_remove_func = NULL; funcptr v24_remove_func = NULL; funcptr prt_remove_func = NULL; /* --------------------------------------------------------------------- */ #pragma check_stack(off) /* Killretn kills the current active task. It is used internally, but can also be called from outside. */ void killretn (void) { tsk_cli (); tsk_kill (tsk_current); tsk_current = NULL; schedule (); } /* tsk_timer_action performs the necessary action when a timeout occurred. */ local void tsk_timer_action (tlinkptr elem) { tcbptr task; byte st; switch (elem->tkind & 0x7f) { case TKIND_WAKE: case TKIND_TASK: task = (tcbptr) elem->strucp; st = task->state; if (st == ST_WAITING || st == ST_DELAYED || st == ST_STOPPED) /***/ { task->retptr = TTIMEOUT; tsk_wakeup (task); } break; case TKIND_FLAG: set_flag ((flagptr) elem->strucp); break; case TKIND_COUNTER: inc_counter ((counterptr) elem->strucp); break; case TKIND_PROC: ((funcptr) elem->strucp)(); break; default: break; } } /* The timer task handles all timeouts. It maintains a single timer queue, which contains elements of the "tlink" structure. No other task is allowed to manipulate this queue, except for the insertion of new elements at the queue head. This allows stepping through the queue with interrupts enabled. CAUTION: This assumes that the operation of loading a far pointer is indivisible! */ local void timer (void) { tlinkptr curr; tlinkptr last; byte state; CRITICAL; while (1) { wait_counter_set (&tsk_timer_counter, 0L); last = (tlinkptr) &tsk_timer; while ((curr = last->next) != NULL) { /* Enter critical section for access to state and timeout variables. The timer action is also critical. */ C_ENTER; if ((state = curr->tstate) >= TSTAT_COUNTDOWN) if (!--curr->timeout) { if (state == TSTAT_COUNTDOWN) state = (byte) TSTAT_REMOVE; else curr->timeout = curr->reload; tsk_timer_action (curr); } if (state == (byte) TSTAT_REMOVE) { last->next = curr->next; curr->tstate = TSTAT_IDLE; #if (TSK_DYNAMIC) if (curr->tkind & TKIND_TEMP) { if ((curr->tkind & 0x7f) == TKIND_TASK) tsk_kill ((tcbptr) curr->strucp); else tsk_free (curr); } #endif curr = last; } C_LEAVE; last = curr; } } } /* int9 is the timer interrupt chain task. */ //#if (IBM) //local void int9 (void) //{ // while (1) // { // wait_counter_set (&tsk_int9_counter, 0L); // tsk_chain_timer (); // } //} //#endif /* ---------------------------------------------------------------------- */ /* create_task Initialises a tcb. The task is in stopped state initially. */ tcbptr create_task (tcbptr task, funcptr func, charptr stack, word stksz, word prior, nearptr arg #if (TSK_NAMEPAR) ,charptr name #endif ) { struct task_stack *stk; #if (TSK_DYNAMIC) if (task == NULL) { if ((task = (tcbptr) tsk_alloc (sizeof(tcb))) == NULL) return NULL; task->flags = F_TEMP; } else task->flags = 0; if (stack == NULL) { if ((stack = (charptr) tsk_alloc (stksz)) == NULL) { if (task->flags & F_TEMP) tsk_free (task); return NULL; } task->flags |= F_STTEMP; } #else task->flags = 0; #endif stk = (struct task_stack *)(stack + stksz - sizeof (struct task_stack)); stk->r_ds = stk->r_es = stk->r_gs = stk->r_fs = tsk_dseg (); stk->r_ebp = 0; stk->r_eflags = tsk_flags (); stk->r_eip = func; stk->r__cs = tsk_cseg (); stk->dummyret = (funcptr)killretn; *(nearptr *)&stk->r_eax = arg; task->stkbot = stack; task->stack = (charptr) stk; task->next = NULL; task->queue = NULL; task->state = ST_STOPPED; task->prior = task->initprior = prior; task->timerq.timeout = task->timerq.reload = 0; task->timerq.strucp = (nearptr) task; task->timerq.tkind = TKIND_TASK; task->timerq.tstate = TSTAT_IDLE; #if (TSK_NAMED) tsk_add_name (&task->name, name, TYP_TCB, task); #endif return task; } /* kill_task Removes a task from the system. CAUTION: The task control block may *not* be immediately re-used if it was enqueued in the timer queue. Check for task->timerq.tstate == TSTAT_IDLE before modifying the tcb. */ void kill_task (tcbptr task) { byte st; CRITICAL; C_ENTER; if ((st = task->state) != ST_RUNNING) tsk_unqueue (task); task->queue = NULL; tsk_kill (task); if (st == ST_RUNNING) { tsk_current = NULL; schedule (); } C_LEAVE; } /* start_task Starts a stopped task. Returns -1 if the task was not stopped. */ word_s start_task (tcbptr task) { CRITICAL; if (task == NULL) task = &main_tcb; if (task->state == ST_STOPPED) { task->state = ST_ELIGIBLE; C_ENTER; tsk_enqueue (task, &tsk_eligible); C_LEAVE; return 0; } return -1; } /* wake_task Restarts a task waiting for an event or timeout. Returns -1 if the task was not waiting or stopped. */ word_s wake_task (tcbptr task) { CRITICAL; if (task == NULL) task = &main_tcb; C_ENTER; if( task->state != ST_WAITING /***/ && task->state != ST_DELAYED /***/ && task->state != ST_STOPPED) /***/ { C_LEAVE; return -1; } task->retptr = TWAKE; tsk_wakeup (task); C_LEAVE; return 0; } /* get_priority Returns the priority of a task. */ word get_priority (tcbptr task) { if (task == NULL) task = &main_tcb; return task->prior; } /* set_priority Changes the priority of a task. If the task is enqueued in a queue, its position in the queue is affected. */ void set_priority (tcbptr task, word prior) { tqueptr que; CRITICAL; if (task == NULL) task = &main_tcb; C_ENTER; task->prior = task->initprior = prior; if ((task->state != ST_RUNNING) && ((que = task->queue) != NULL)) { tsk_unqueue (task); tsk_enqueue (task, que); } C_LEAVE; } /* set_task_flags Changes the user modifiable flags of the task. */ void set_task_flags (tcbptr task, byte flags) { CRITICAL; if (task == NULL) task = &main_tcb; C_ENTER; task->flags = (task->flags & FL_SYSM) | (flags & FL_USRM); C_LEAVE; } /* --------------------------------------------------------------------- */ /* t_delay delay the current task by "ticks" clock ticks. If ticks is zero, the task is stopped. */ word_s t_delay (dword ticks) { tsk_cli (); tsk_current->queue = NULL; if (ticks) { tsk_current->state = ST_DELAYED; tsk_enqtimer (tsk_current, ticks); } else tsk_current->state = ST_STOPPED; schedule (); return (word_s)tsk_current->retptr; } /* --------------------------------------------------------------------- */ /* install_tasker Installs the Ctask system. The internal tasks are created, the queues are initialised, and the interrupt handler installation routines are called. Task preemption is initially off. Handling of the speedup parameter is system dependent. */ void install_tasker (byte varpri, int speedup) { extern poolptr pool; word divisor, sys_ticks, i; tsk_current = &main_tcb; tsk_eligible = NULL; tsk_timer = NULL; tsk_preempt = 1; tsk_pretick = 0; tsk_var_prior = varpri; #if (TSK_NAMED) tsk_name_list.follow = tsk_name_list.prev = &tsk_name_list; tsk_name_list.nkind = 0; tsk_name_list.name [0] = 0; tsk_add_name (&main_tcb.name,"-MAIN-", TYP_TCB, &main_tcb); /***/ #endif for(i=0; i 8) { divisor = 0; sys_ticks = 1; } else { divisor = 0x8000 >> (speedup - 1); sys_ticks = 1 << speedup; } ticks_per_sec = 18 * sys_ticks; /* rough number only */ #if (CLOCK_MSEC) tick_factor = (65536.0/(double)sys_ticks)/1193.18; #endif tsk_install_timer(divisor, sys_ticks); tsk_install_kbd (); #endif #if (AT_BIOS) // tsk_install_bios (); #endif #if (DOS) // tsk_install_dos (); #endif } /* preempt_off Turns off task preemption (will stay off until explicitly enabled). */ void preempt_off (void) { tsk_preempt = 1; } /* preempt_on Resets permanent and temporary task preemption flag. If preemption is pending, the scheduler is called. */ void preempt_on (void) { tsk_preempt = 0; tsk_cli (); if (tsk_pretick) schedule (); tsk_sti (); } /* remove_tasker Calls the interrupt handler un-install routines. */ void remove_tasker (void) { tsk_preempt = 0; #if (AT_BIOS) // tsk_remove_bios (); #endif #if (IBM) if (mpo8_remove_func != NULL) mpo8_remove_func (); if (ad8_remove_func != NULL) ad8_remove_func (); if (v24_remove_func != NULL) v24_remove_func (); if (prt_remove_func != NULL) prt_remove_func (); /* Allow all stored clock ticks to be processed */ // set_priority (&int9_tcb, 0xffff); // while (check_counter (&tsk_int9_counter)) // schedule(); tsk_remove_timer (); tsk_remove_kbd (); #endif #if (DOS) // tsk_remove_dos (); #endif } /* tsk_dis_preempt Turns off task preemption (temporary, till tsk_ena_preempt() or sheduler() call). */ void tsk_dis_preempt (void) { tsk_cli (); tsk_preempt |= 2; tsk_sti (); } /* tsk_ena_preempt Resets temporary task preemption flag. If preemption is pending, the scheduler is called. */ void tsk_ena_preempt (void) { tsk_cli (); if (!(tsk_preempt &= ~2)) if (tsk_pretick) schedule (); tsk_sti (); } /* --------------------------------------------------------------------- */ /* create_timer Creates a timer queue element. The element is inserted into the timeout queue. */ tlinkptr create_timer (tlinkptr elem, dword tout, nearptr strucp, byte kind, byte rept) { CRITICAL; if (kind <= TKIND_TASK || kind > TKIND_COUNTER) return NULL; #if (TSK_DYNAMIC) if (elem == NULL) { if ((elem = tsk_alloc (sizeof (tlink))) == NULL) return NULL; kind |= TKIND_TEMP; } #endif elem->tkind = kind; elem->strucp = strucp; elem->tstate = (byte)((rept) ? TSTAT_REPEAT : TSTAT_COUNTDOWN); elem->timeout = elem->reload = tsk_timeout(tout); C_ENTER; elem->next = tsk_timer; tsk_timer = elem; C_LEAVE; return elem; } /* delete_timer Deletes a timeout element. */ void delete_timer (tlinkptr elem) { CRITICAL; C_ENTER; if (elem->tstate != TSTAT_IDLE) { elem->tstate = (byte)TSTAT_REMOVE; C_LEAVE; return; } C_LEAVE; #if (TSK_DYNAMIC) if (elem->tkind & TKIND_TEMP) tsk_free (elem); #endif } /* change_timer Changes the timeout and/or repeat-flag in a timer element. If the timer was idle, it is inserted into the timeout queue. If 0 is passed as timeout, the element is removed from the timeout queue (same as delete_timer). This routine should *not* be used for dynamically allocated timer elements. */ void change_timer (tlinkptr elem, dword tout, byte rept) { byte st; CRITICAL; if (!tout) { delete_timer (elem); return; } C_ENTER; elem->timeout = elem->reload = tsk_timeout(tout); st = elem->tstate; elem->tstate = (byte)((rept) ? TSTAT_REPEAT : TSTAT_COUNTDOWN); if (st == TSTAT_IDLE) { elem->next = tsk_timer; tsk_timer = elem; } C_LEAVE; }