; =============================================================================
;
; Litos - Scheduler
;
; =============================================================================
; Run structures of tasks:
; WakeUpList - tasks prepared for running, marked TASK_WAKEUP. WakeUpList
; is separate for each CPU. It is lockable.
; RunQueue - running tasks, marked TASK_RUNNING. Each CPU has its own
; RunQueue, it is not lockable. RunQueue contains 2 task queues,
; current and expired. After running out of all time quantum
; each task is moved from current queue into expired queue.
; If current queue contains no task, queues are exchange and
; tasks start run with new time quantum.
; SleepList - list of sleeping tasks (TASK_SLEEP state).
; PauseList - list of paused tasks (TASK_PAUSE state).
;
; Task run-cycle:
; - Only task can put itself into waiting state. It change its state from
; TASK_RUNNING to TASK_FALLASLEEP and call scheduler.
; - Scheduler moves fall-asleep task into sleep list.
; - Task can be waked-up with an alarm (if its sleep time elapses) or with
; another task (moving task into WakeUpList and marking TASK_WAKEUP)
; or with sending signal to the task.
; - Scheduler moves tasks from wake-up list into its run-queue. If task has
; a non-blocked signal it wakes it up, too.
;
; All system timing is refered to system time (100-nanosecs from system start).
; =============================================================================
CODE_SECTION 32
QUANTUM_MAX EQU 150*TIME_1MS ; maximal time quantum in 100-ns
QUANTUM_MIN EQU 2*TIME_1MS_REAL ; minimal time quantum in 100-ns
; -----------------------------------------------------------------------------
; Lock/unlock sleep list
; -----------------------------------------------------------------------------
; NOTES: Use macro SLEEPLOCK to lock, SLEEPUNLOCK to unlock.
; -----------------------------------------------------------------------------
; ------------- Sleep lock function
%ifdef SMP
LOCK_LockFnc SleepLock,SleepListLock ; declare sleep lock
%endif
; ------------- Macro - call sleep lock function
%macro SLEEPLOCK 0
%ifdef SMP
call SleepLock ; call sleep lock function
%endif
%endmacro
; ------------- Macro - sleep unlock
%macro SLEEPUNLOCK 0
%ifdef SMP
LOCK_Unlock SleepListLock ; unlock sleep list
%endif
%endmacro
; -----------------------------------------------------------------------------
; Lock/unlock pause list
; -----------------------------------------------------------------------------
; NOTES: Use macro PAUSELOCK to lock, PAUSEUNLOCK to unlock.
; -----------------------------------------------------------------------------
; ------------- Pause lock function
%ifdef SMP
LOCK_LockFnc PauseLock,PauseListLock ; declare pause lock
%endif
; ------------- Macro - call pause lock function
%macro PAUSELOCK 0
%ifdef SMP
call PauseLock ; call pause lock function
%endif
%endmacro
; ------------- Macro - pause unlock
%macro PAUSEUNLOCK 0
%ifdef SMP
LOCK_Unlock PauseListLock ; unlock pause list
%endif
%endmacro
; -----------------------------------------------------------------------------
; Initialize scheduler
; -----------------------------------------------------------------------------
; ------------- Allocate buffer for run-queues (528 to 16896 bytes)
SchedInit: mov eax,QUEUE_size*2 ; EAX <- size of task queue * 2
mov esi,[CPUNum] ; ESI <- number of CPUs
mul esi ; EAX <- size of buffer
call SysMemAlloc ; get system memory (no errors yet)
xchg eax,edi ; EDI <- pointer to queue buffer
; ------------- Initialize task queues
mov edx,edi ; EDX <- task queues
lea ebx,[esi*2] ; EBX <- number of queues * 2
xor ecx,ecx ; ECX <- 0
SchedInit2: mov [edx+QUEUE_Total],ecx ; clear total number of tasks
mov [edx+QUEUE_Mask],ecx ; clear mask of non-empty lists
add edx,byte QUEUE_List ; EDX <- lists of TASKs
; ------------- Initialize lists of TASKs (-> ECX=0)
mov cl,QUEUE_LISTS ; ECX <- number of task levels
SchedInit3: LISTINIT edx ; initialize list head
add edx,byte LIST_size ; EDX <- next list head
loop SchedInit3 ; initialize next list head
dec ebx ; counter of queues
jnz SchedInit2 ; initialize next queue
; ------------- Prepare to initialize table of run-queues
mov edx,RunQueue ; EDX <- table of run-queues
mov eax,RunQueueAddr; EAX <- addresses of run-queue tables
; ------------- Address of run-queue
SchedInit4: mov [eax],edx ; store address of run-queue
add eax,byte 4 ; increase address
; ------------- Current and expired task queue
mov [edx+RUNQ_Current],edi ; current task queue
add edi,QUEUE_size ; EDI <- next task queue
mov [edx+RUNQ_Expired],edi ; expired task queue
add edi,QUEUE_size ; EDI <- next task queue
; ------------- Other variables
mov [edx+RUNQ_NextSched],ecx ; reschedule request
mov [edx+RUNQ_Elapsed],ecx ; clear elapsed time
mov [edx+RUNQ_Total],ecx ; total tasks in run-queue
; ------------- Next run-queue
add edx,byte RUNQ_size ; EDX <- address of next run-queue
dec esi ; counter of CPUs
jnz SchedInit4 ; initialize table for next CPU
ret
; -----------------------------------------------------------------------------
; Insert task into task queue (at the end of the list)
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; ECX = index in the task list (0 to 31)
; EDX = task queue
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskEnqueue: push eax ; push EAX
push ecx ; push ECX
push edx ; push EDX
; ------------- Store pointer to new task queue
mov [ebx+TASK_TaskQueue],edx ; store pointer to task queue
mov [ebx+TASK_QueueLev],ecx ; current level in task queue
; ------------- Increase number of tasks
inc dword [edx+QUEUE_Total] ; increase number of tasks
; ------------- Set queue mask
xor eax,eax ; EAX <- 0
inc eax ; EAX <- 1
shl eax,cl ; EAX <- index mask
or [edx+QUEUE_Mask],eax ; set queue mask
; ------------- Store task into the list
lea eax,[edx+QUEUE_List+ecx*LIST_size] ; EAX <- list
lea ecx,[ebx+TASK_Queue] ; ECX <- task list entry
LISTLAST eax, ecx, edx ; add task to the end of queue
; ------------- Pop registers
pop edx ; pop EDX
pop ecx ; pop ECX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Delete task from task queue
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskDequeue: push eax ; push EAX
push ebx ; push EBX
push ecx ; push ECX
push edx ; push EDX
; ------------- Get task queue (-> EDX)
mov edx,[ebx+TASK_TaskQueue] ; EDX <- task queue
mov ecx,[ebx+TASK_QueueLev] ; ECX <- level in task queue
; ------------- Decrease number of tasks
dec dword [edx+QUEUE_Total] ; decrease number of tasks
; ------------- Detach task from the list
lea ebx,[ebx+TASK_Queue] ; EBX <- task list entry
LISTDEL ebx, eax, ebx ; detach task from the list
; ------------- Clear mask
cmp eax,ebx ; is list empty?
jne TaskDequeue2 ; list is not empty
xor eax,eax ; EAX <- 0
inc eax ; EAX <- 1
shl eax,cl ; EAX <- index mask
xor [edx+QUEUE_Mask],eax ; clear queue mask
; ------------- Pop registers
TaskDequeue2: pop edx ; pop EDX
pop ecx ; pop ECX
pop ebx ; pop EBX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Move task in task queue into another level
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; ECX = new index in the task list (0 to 31)
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskMovePrior: push eax ; push EAX
push edx ; push EDX
; ------------- Check if index changed
mov eax,[ebx+TASK_QueueLev] ; EAX <- level in task queue
cmp eax,ecx ; current index changed?
je TaskMovePrior4 ; current index not changed
; ------------- Requeue task
mov edx,[ebx+TASK_TaskQueue] ; EDX <- task queue
call TaskDequeue ; detach task from the queue
call TaskEnqueue ; attach task into the queue
; ------------- Pop registers
TaskMovePrior4: pop edx ; pop EDX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Move task from current task queue into expired task queue
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; ESI = run-queue
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskMoveExp: push ecx ; push ECX
push edx ; push EDX
; ------------- Prepare new time quantum
xchg eax,ecx ; ECX <- push EAX
call TaskQuantum ; calculate time quantum
xchg eax,ecx ; EAX <- pop EAX,ECX <- quantum
add [ebx+TASK_Remaining],ecx ; new remaining time
cmp dword [ebx+TASK_Remaining],QUANTUM_MAX ; max. time
jbe TaskMoveExp1
mov dword [ebx+TASK_Remaining],QUANTUM_MAX ; limit time
; ------------- Decrement temporaly priority
TaskMoveExp1: dec byte [ebx+TASK_AddPrio] ; decrement temporary priority
jns TaskMoveExp2 ; priority is OK
inc byte [ebx+TASK_AddPrio] ; return zero value
; ------------- Dequeue task from current task queue
TaskMoveExp2: mov ecx,[ebx+TASK_QueueLev] ; ECX <- level in task queue
call TaskDequeue ; detach task from the queue
; ------------- Enqueue task into expire task queue
mov edx,[esi+RUNQ_Expired] ; expired task queue
call TaskEnqueue ; enqueue task into the queue
; ------------- Pop registers
pop edx ; pop EDX
pop ecx ; pop ECX
ret
; -----------------------------------------------------------------------------
; Calculate task priority for running task
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; OUTPUT: EAX = priority
; -----------------------------------------------------------------------------
; ------------- Prepare bonus for running task (remaining time 2550000 max.)
TaskCalcPrio: mov eax,[ebx+TASK_Remaining] ; EAX <- remaining time
sar eax,16 ; 1 level per 6 ms, max. 38
jns TaskCalcPrio2 ; bonus is positive
xor eax,eax ; limit negative bonus
TaskCalcPrio2: cmp eax,byte 38 ; maximal bonus
jle TaskCalcPrio3 ; bonus is OK
xor eax,eax ; EAX <- 0
mov al,38 ; limit bonus
; ------------- Add static priority (-> -31 to +120)
TaskCalcPrio3: add al,[ebx+TASK_AbsPrio] ; AL <- absolute priority
add al,[ebx+TASK_RelPrio] ; AL <- add relative priority
add al,[ebx+TASK_AddPrio] ; AL <- add temporaly increment
; ------------- Increase priority for task with user focus (-> max. +126)
cmp byte [ebx+TASK_Focus],0 ; has it user focus?
je TaskCalcPrio4 ; it has not user focus
add al,6 ; increase priority
; ------------- Limit maximal priority
TaskCalcPrio4: cmp al,[ebx+TASK_MaxPrio] ; maximal priority
jle TaskCalcPrio6 ; priority is OK
mov al,[ebx+TASK_MaxPrio] ; limit maximal priority
; ------------- Limit minimal priority
TaskCalcPrio6: cmp al,PRIORITY_MIN ; minimal priority
jge TaskCalcPrio8 ; priority is OK
mov al,PRIORITY_MIN ; limit minimal priority
TaskCalcPrio8: ret
; -----------------------------------------------------------------------------
; Calculate task time quantum (from current priority)
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; ESI = run-queue (in which is the task placed)
; OUTPUT: EAX = task time quantum
; -----------------------------------------------------------------------------
; 1 task: 32 to 150 ms, normal 124 ms
; 2 tasks: 16 to 118 ms, mormal 62 ms
; 5 tasks: 6 to 47 ms, mormal 24 ms
; 10 tasks: 3 to 24 ms, mormal 12 ms
; 20 tasks: 2 to 12 ms, mormal 6 ms
; 50 tasks: 2 to 5 ms, mormal 3 ms
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskQuantum: push ecx ; push ECX
push edx ; push EDX
; ------------- Basic time quantum based on the task priority (-> 2M to 18M)
movzx eax,byte [ebx+TASK_Priority] ; EAX <- current priority
mov cl,al ; CL <- store priority
add al,5 ; EAX <- basic quantum
shl eax,16 ; EAX <- quantum << 16
; ------------- Real-time tasks can have maximal time quantums
cmp cl,PRIORITY_MAXUSR ; is it real-time task?
ja TaskQuantum2 ; it is real-time task, use max.quantum
; ------------- Decrease time quantum with number of running tasks
mov ecx,[esi+RUNQ_Total] ; ECX <- number of running tasks
xor edx,edx ; EDX <- 0
div ecx ; EAX <- time quantum
; ------------- Limit minimal and maximal time quantum
TaskQuantum2: cmp eax,QUANTUM_MIN ; check minimal time quantum
jae TaskQuantum4 ; time quantum is OK
mov eax,QUANTUM_MIN ; limit minimal time quantum
TaskQuantum4: cmp eax,QUANTUM_MAX ; check maximal time quantum
jbe TaskQuantum6 ; time quantum is OK
mov eax,QUANTUM_MAX ; limit maximal time quantum
; ------------- Pop registers
TaskQuantum6: pop edx ; pop EDX
pop ecx ; pop ECX
ret
; -----------------------------------------------------------------------------
; Store task into sleep list
; -----------------------------------------------------------------------------
; INPUT: EBX = task (in fall asleep state, its sleep time must be set)
; NOTES: Task must not be in any task list.
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskGoWait: push eax ; push EAX
push ebx ; push EBX
push ecx ; push ECX
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Lock sleep list
SLEEPLOCK ; lock sleep list
; ------------- Set state to SLEEP
mov byte [ebx+TASK_State],TASK_SLEEP ; set sleep state
; ------------- Store task into sleep list
mov eax,SleepList ; EAX <- sleep list
add ebx,TASK_Queue ; EBX <- link
LISTADD eax, ebx, ecx ; add task after found entry
; ------------- Start alarm
add ebx,byte TASK_Alarm-TASK_Queue ; EBX <- alarm
call AlarmStart ; start alarm
; ------------- Unlock sleep list
SLEEPUNLOCK ; unlock sleep list
; ------------- Enable interrupts
popf ; pop flags
; ------------- Pop registers
pop ecx ; pop ECX
pop ebx ; pop EBX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Store task into wake-up list
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; NOTES: Task must not be in any task list.
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskGoWakeUp: pusha ; push all registers
; ------------- Mark task as waking-up
mov byte [ebx+TASK_State],TASK_WAKEUP ; task is waking up
; ------------- Find CPU with lowest overhead (-> ESI)
mov edx,RunQueue ; EDX <- run-queue table
xor eax,eax ; EAX <- 0
dec eax ; EAX <- -1, initial number of tasks
xor ecx,ecx ; ECX <- 0, first CPU index
TaskGoWakeUp2: cmp eax,[edx+RUNQ_Total] ; check number of tasks
jbe TaskGoWakeUp4 ; it is not better CPU
mov eax,[edx+RUNQ_Total] ; EAX <- new number of tasks
mov esi,ecx ; ESI <- index of best CPU
TaskGoWakeUp4: add edx,byte RUNQ_size ; EDX <- new run-queue
inc ecx ; increase CPU number
cmp ecx,[CPUNum] ; all CPU?
jb TaskGoWakeUp2 ; test next CPU
; ------------- Prefer old CPU
movzx ecx,byte [ebx+TASK_CPU] ; ECX <- old CPU
mov edx,[RunQueueAddr+ecx*4] ; EDX <- run-queue of old CPU
cmp eax,[edx+RUNQ_Total] ; has this CPU equal tasks?
jne TaskGoWakeUp6 ; minimal CPU is better
mov esi,ecx ; ESI <- use old CPU
; ------------- Lock wake-up list
TaskGoWakeUp6: pushf ; push flags
cli ; disable interrupts
%ifdef SMP
lea edx,[WakeUpLock+esi*SPINLOCK_size] ; EDX <- lock
call SpinLockEDX ; lock wake-up list
%endif
; ------------- Store task into wake-up list
lea eax,[WakeUpList+esi*LIST_size] ; EAX <- wake-up list
add ebx,TASK_Queue ; EBX <- task queue
LISTLAST eax,ebx,ecx ; store task into list
; ------------- Reschedule run-queue request
mov eax,[RunQueueAddr+esi*4] ; EAX <- run-queue
RESCHEDULE eax ; schedule request
; ------------- Unlock wake-up list
%ifdef SMP
LOCK_Unlock edx ; unlock wake-up list
%endif
popf ; pop flags (and enable interrupts)
; ------------- Pop registers
popa ; pop all registers
ret
; -----------------------------------------------------------------------------
; Sleep callback function
; -----------------------------------------------------------------------------
; INPUT: ECX = task
; EBX = alarm structure
; -----------------------------------------------------------------------------
SleepWakeUp: and dword [ebx+ALARM_Expire],byte 0 ; mark as expired
and dword [ebx+ALARM_Expire+4],byte 0
mov ebx,ecx ; EBX <- task
; WakeUpTask must follow.
; -----------------------------------------------------------------------------
; Wake up task, if it is sleeping (add task to wake-up list)
; -----------------------------------------------------------------------------
; INPUT: EBX = task (can be NULL)
; -----------------------------------------------------------------------------
; ------------- Quick test if task is sleeping
WakeUpTask: or ebx,ebx ; is it NULL?
jz short WakeUpTask9 ; invalid task
cmp byte [ebx+TASK_State],TASK_SLEEP ; is task sleeping?
jne short WakeUpTask9 ; task is not sleeping
; ------------- Push registers
push eax ; push EAX
push ecx ; push ECX
push edx ; push EDX
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Lock sleep list
SLEEPLOCK ; lock sleep list
; ------------- Check if task is still sleeping
cmp byte [ebx+TASK_State],TASK_SLEEP ; is task sleeping?
je WakeUpTask2 ; task is still sleeping
; ------------- Someone woke it up, unlock sleep list and exit
SLEEPUNLOCK ; unlock sleep list
popf ; pop flags (and enable interrupts)
jmp short WakeUpTask8 ; exit
; ------------- Mark task as waking-up
WakeUpTask2: mov byte [ebx+TASK_State],TASK_WAKEUP ; task is waking up
; ------------- Remove task from the sleep list
lea eax,[ebx+TASK_Queue] ; EAX <- link to sleep list
LISTDEL eax,ecx,edx ; remove task from the sleep list
; ------------- Stop alarm (if it is running)
push ebx ; push EBX
add ebx,TASK_Alarm ; EBX <- alarm
call AlarmStop ; stop alarm
pop ebx ; pop EBX
; ------------- Unlock sleep list
SLEEPUNLOCK ; unlock sleep list
; ------------- Enable interrupts
popf ; pop flags (and enable interrupts)
; ------------- Store task (in EBX) into wake-up list
call TaskGoWakeUp ; store task into wake-up list
; ------------- Pop registers
WakeUpTask8: pop edx ; pop EDX
pop ecx ; pop ECX
pop eax ; pop EAX
WakeUpTask9: ret
; -----------------------------------------------------------------------------
; Run tasks from wake-up list (for current CPU)
; -----------------------------------------------------------------------------
; ------------- Push registers
RunWakeUp: pusha ; push all registers
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Get current CPU (-> ECX)
CURRENT ecx ; ECX <- get current task
movzx ecx,byte [ecx+TASK_CPU] ; ECX <- current CPU
; ------------- Get run-queue (-> ESI)
mov esi,[RunQueueAddr+ecx*4] ; ESI <- run-queue
; ------------- Lock wake-up list (-> EDX)
%ifdef SMP
lea edx,[WakeUpLock+ecx*SPINLOCK_size] ; EDX <- lock
LOCK_Lock edx ; lock wake-up list
%endif
; ------------- Get next task from wake-up list
lea edi,[WakeUpList+ecx*LIST_size] ; EDI <- wake-up list
RunWakeUp2: mov ebx,[edi+LIST_Next] ; EBX <- next task
cmp ebx,edi ; is wake-up list empty?
je RunWakeUp8 ; wake-up list is empty
LISTDEL ebx,ecx,eax ; remove task from the list
; ------------- Mark task as running
sub ebx,TASK_Queue ; EBX <- task
mov byte [ebx+TASK_State],TASK_RUNNING ; mark as running
; ------------- Calculate task priority
call TaskCalcPrio ; calculate task priority
push eax ; push EAX (task priority)
mov [ebx+TASK_Priority],al ; store current priority
; ------------- Set run-queue and current CPU
mov [ebx+TASK_RunQueue],esi ; set current run-queue
CURRENT ecx ; ECX <- get current task
mov cl,[ecx+TASK_CPU] ; CL <- current CPU
mov [ebx+TASK_CPU],cl ; set current CPU
; ------------- Insert task into run-queue
pop ecx ; pop ECX (task priority)
push edx ; push EDX
mov edx,[esi+RUNQ_Current] ; EDX <- current task queue
call TaskEnqueue ; insert task into run-queue
pop edx ; pop EDX
inc dword [esi+RUNQ_Total] ; increase number of tasks
jmp short RunWakeUp2 ; next task
; ------------- Unlock wake-up list
RunWakeUp8:
%ifdef SMP
LOCK_Unlock edx ; unlock wake-up list
%endif
; ------------- Enable interrupts
popf ; pop flags
; ------------- Pop registers
popa ; pop all registers
ret
; -----------------------------------------------------------------------------
; Store current running task into pause-list
; -----------------------------------------------------------------------------
; ------------- Push registers
TaskStop: push eax ; push EAX
push ebx ; push EBX
push edx ; push EDX
; ------------- Get current task (-> EBX)
CURRENT ebx ; EBX <- get current task
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Detach task from the running queue
fnsave [ebx+TASK_FPU] ; save FPU state
call TaskDequeue ; detach task from the queue
mov edx,[ebx+TASK_RunQueue] ; EDX <- run-queue
dec dword [edx+RUNQ_Total] ; decrease current tasks
; ------------- Lock pause-queue
PAUSELOCK ; lock pause-queue
; ------------- Set state to PAUSE and store task into the list
lea eax,[ebx+TASK_Queue] ; EAX <- link
mov byte [eax+TASK_State-TASK_Queue],TASK_PAUSE ; set pause
mov ebx,PauseList ; EBX <- pause list
call ListAdd ; add task into the list
; ------------- Unlock wait-queue
PAUSEUNLOCK ; unlock pause-queue
; ------------- Enable interrupts
popf ; pop flags
; ------------- Pop registers
pop edx ; pop EDX
pop ebx ; pop EBX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Wake up task from pause-list (add task to wake-up list)
; -----------------------------------------------------------------------------
; INPUT: EBX = task
; NOTES: It checks if the task is in the pause-list.
; -----------------------------------------------------------------------------
; ------------- Quick test if task is paused
TaskContinue: cmp byte [ebx+TASK_State],TASK_PAUSE ; is task paused?
jne TaskContinue9 ; task is not paused
; ------------- Push registers
push eax ; push EAX
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Lock pause-queue
PAUSELOCK ; lock pause-queue
; ------------- Check if task is still paused
cmp byte [ebx+TASK_State],TASK_PAUSE ; is task paused?
je TaskContinue2 ; task is still paused
; ------------- Someone woke it up, unlock pause-queue and exit
PAUSEUNLOCK ; unlock wait-queue
popf ; pop flags (and enable interrupts)
jmp short TaskContinue8 ; exit
; ------------- Mark task as waking-up
TaskContinue2: mov byte [ebx+TASK_State],TASK_WAKEUP ; task is waking up
; ------------- Remove task from the pause-queue
lea eax,[ebx+TASK_Queue] ; EAX <- link to pause-queue
call ListDel ; remove task from the pause-queue
; ------------- Unlock wait-queue
PAUSEUNLOCK ; unlock wait-queue
; ------------- Enable interrupts
popf ; pop flags (and enable interrupts)
; ------------- Store task (in EBX) into wake-up list
call TaskGoWakeUp ; store task into wake-up list
; ------------- Pop registers
TaskContinue8: pop eax ; pop EAX
TaskContinue9: ret
; -----------------------------------------------------------------------------
; Prepare to fall asleep current task (task must be running)
; -----------------------------------------------------------------------------
; INPUT: EDX:EAX = length of sleep (in 100-nanosec), negative=infinitely
; -----------------------------------------------------------------------------
; ------------- Push registers
PrepSleepTask: push eax ; push EAX
push ebx ; push EBX
push ecx ; push ECX
push edx ; push EDX
; ------------- Calculate time of end of sleep (-> EDX:EAX)
or edx,edx ; is it infinity?
js PrepSleepTask2 ; wait infinitely
xchg eax,ebx ; EBX <- required time LOW
mov ecx,edx ; ECX <- required time HIGH
call GetSystemTime ; get system time
add eax,ebx ; EAX <- end of sleep LOW
adc edx,ecx ; EDX <- end of sleep HIGH
; ------------- Get current task (-> EBX)
PrepSleepTask2: CURRENT ebx ; EBX <- get current task
; ------------- Store time of end of sleep
mov [ebx+TASK_Alarm+ALARM_Expire],eax ; end of sleep LOW
mov [ebx+TASK_Alarm+ALARM_Expire+4],edx ; end of sleep HIGH
; ------------- Clear lock-semaphore pointer
; and dword [ebx+TASK_LockSem],byte 0 ; clear lock-semaphore
; ------------- Mark task as falling asleep
mov byte [ebx+TASK_State],TASK_FALLASLEEP ; fall asleep
; ------------- Pop registers
pop edx ; pop EDX
pop ecx ; pop ECX
pop ebx ; pop EBX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Fall asleep current task (task must be running)
; -----------------------------------------------------------------------------
; INPUT: EDX:EAX = length of sleep (in 100-nanosec), negative=infinitely
; -----------------------------------------------------------------------------
; ------------- Prepare fall asleep task
SleepTask: call PrepSleepTask ; prepare to fall asleep task
; ------------- Call scheduler (and switch to another task)
jmp Schedule ; call scheduler
; -----------------------------------------------------------------------------
; Preparre to continue task sleep
; -----------------------------------------------------------------------------
; NOTES: Time of end of sleep must be set.
; -----------------------------------------------------------------------------
; ------------- Push registers
PrepSleepCont: push ebx ; push EBX
; ------------- Get current task (-> EBX)
CURRENT ebx ; EBX <- get current task
; ------------- Mark task as falling asleep
mov byte [ebx+TASK_State],TASK_FALLASLEEP ; fall asleep
; ------------- Pop registers
pop ebx ; pop EBX
ret
; -----------------------------------------------------------------------------
; Continue task sleep (e.i. after interrupt by signal)
; -----------------------------------------------------------------------------
; NOTES: Time of end of sleep must be set.
; -----------------------------------------------------------------------------
; ------------- Prepare to continue task sleep
SleepCont: call PrepSleepCont ; prepare to continue task sleep
; ------------- Call scheduler (and switch to another task)
jmp Schedule ; call scheduler
; -----------------------------------------------------------------------------
; Sleep current task for a time
; -----------------------------------------------------------------------------
; INPUT: EAX = length of sleep in [ms]
; -----------------------------------------------------------------------------
; ------------- Push registers
Sleep: push eax ; push EAX
push edx ; push EDX
; ------------- Recalculate time to 100-ns
mov edx,TIME_1MS ; EDX <- time 1 ms
mul edx ; recalc time to 100-ns
; ------------- Fall asleep
call SleepTask ; fall asleep with required time
; ------------- Check if time expired
Sleep2: CURRENT edx ; EDX <- get current task
xor eax,eax ; EAX <- 0
cmp eax,[edx+TASK_Alarm+ALARM_Expire]; check sleep time LOW
jne Sleep4 ; time didn't expire
cmp eax,[edx+TASK_Alarm+ALARM_Expire+4] ; sleep time HIGH
je Sleep8 ; sleep time expired
; ------------- Continue sleep (due to signal came)
Sleep4: call SleepCont ; continue sleep
jmp short Sleep2 ; next test
; ------------- Pop regigters
Sleep8: pop edx ; pop EDX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Sleep current task for a short time
; -----------------------------------------------------------------------------
; INPUT: AL = length of sleep in [ms]
; -----------------------------------------------------------------------------
SleepShort: push eax ; push EAX
movzx eax,al ; EAX <- sleep time
call Sleep ; sleep
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Sleep current task for an interruptible time
; -----------------------------------------------------------------------------
; INPUT: EAX = length of sleep in [ms]
; OUTPUT: EAX = remaining length of sleep in [ms], 0 = time expired (CY)
; CY = time expired (EAX = 0)
; NOTES: Other process can interrupt sleeping with WakeUpTask.
; To continue in non-expired sleep use SleepContInt.
; -----------------------------------------------------------------------------
; ------------- Push registers
SleepInt: push edx ; push EDX
; ------------- Recalculate time to 100-ns
mov edx,TIME_1MS ; EDX <- time 1 ms
mul edx ; recalc time to 100-ns
; ------------- Fall asleep
call SleepTask ; fall asleep with required time
; ------------- Recalculate remaining time to [ms]
SleepInt4: push ebx ; push EBX
CURRENT ebx ; EBX <- get current task
mov eax,[ebx+TASK_Alarm+ALARM_Expire]; get sleep time LOW
mov edx,[ebx+TASK_Alarm+ALARM_Expire+4] ; sleep time HIGH
mov ebx,TIME_1MS ; EBX <- time 1 ms
div ebx ; EAX <- remaining time
pop ebx ; pop EBX
cmp eax,byte 1 ; check if any time rests (CY=timeout)
; ------------- Pop regigters
pop edx ; pop EDX
ret
; -----------------------------------------------------------------------------
; Continue task sleep interruptible
; -----------------------------------------------------------------------------
; OUTPUT: EAX = remaining length of sleep in [ms], 0 = time expired (CY)
; CY = time expired (EAX = 0)
; NOTES: Time of end of sleep must be set.
; Other process can interrupt sleeping with WakeUpTask.
; -----------------------------------------------------------------------------
; ------------- Push registers
SleepIntCont: push edx ; push EDX
; ------------- Get current task (-> EDX)
CURRENT edx ; EDX <- get current task
; ------------- Mark task as falling asleep
mov byte [edx+TASK_State],TASK_FALLASLEEP ; fall asleep
; ------------- Call scheduler (and switch to another task)
call Schedule ; call scheduler
jmp short SleepInt4
; -----------------------------------------------------------------------------
; NMI service
; -----------------------------------------------------------------------------
; INPUT: EAX = elapsed time since last service (in 100-ns)
; EBX = current task
; ESI = current run-queue
; -----------------------------------------------------------------------------
; ------------- Signal memory error
DoNMI: btr dword [esi+RUNQ_Flags],0 ; signal memory error?
jnc DoNMI2 ; memory error not signaled
; !!! TODO display message
; ------------- Signal I/O error
DoNMI2: btr dword [esi+RUNQ_Flags],1 ; signal I/O error?
jnc DoNMI3 ; I/O error not signaled
; !!! TODO display message
; ------------- Signal watchdog
DoNMI3: btr dword [esi+RUNQ_Flags],2 ; signal watchdog?
jnc DoNMI5 ; I/O error not signaled
test byte [NMIWatchdog],B0 ; use watchdog?
jz DoNMI4 ; don't use watchdog
; !!! TODO serve watchdog
; ------------- Other error
DoNMI4:
; !!! TODO display message
; ------------- Count before reenabling memory and I/O check
DoNMI5: test byte [esi+RUNQ_Flags],B3 ; memory,I/O check disabled?
jz DoNMI6 ; check is not disabled
sub dword [esi+RUNQ_CountMem],eax ; count time
jns DoNMI6 ; time is not elapsed yet
; ------------- Reenable memory and I/O check
and byte [esi+RUNQ_Flags],~B3 ; memory,I/O check enabled
push eax ; push EAX
in al,61h ; AL <- get current state of port
and al,B0+B1 ; clear reserved bits, enable check
out 61h,al ; enable memory and I/O check
pop eax ; pop EAX
; ------------- Count before reenabling watchdog check
DoNMI6: test byte [esi+RUNQ_Flags],B4 ; watchdog check disabled?
jz DoNMI7 ; check is not disabled
sub dword [esi+RUNQ_CountDog],eax ; count time
jns DoNMI7 ; time is not elapsed yet
and byte [esi+RUNQ_Flags],~B4 ; watchdog check enabled
; ------------- Pop registers
DoNMI7: ret
; -----------------------------------------------------------------------------
; Scheduler
; -----------------------------------------------------------------------------
; Stack of Scheduler is hardcoded in TaskClone.
; [ESP+6*4]: return
; [ESP+5*4]: eax
; [ESP+4*4]: ebx
; [ESP+3*4]: ecx
; [ESP+2*4]: edx
; [ESP+1*4]: esi
; [ESP+0*4]: flags
; ------------- Push registers
Schedule: push eax ; push EAX
push ebx ; push EBX
push ecx ; push ECX
push edx ; push EDX
push esi ; push ESI
; ------------- Get current task (-> EBX)
Schedule1: CURRENT ebx ; EBX <- get current task
; ------------- Run slow interrupt handlers
call IRQRunSlow ; run slow interrupt handlers
; ------------- Run tasks from wake-up list
call RunWakeUp ; run tasks from wake-up list
; ------------- Disable interrupts
pushf ; push flags
cli ; disable interrupts
; ------------- Get current run-queue (-> ESI)
mov esi,[ebx+TASK_RunQueue] ; ESI <- run-queue
; ------------- Unmark request to scheduling
xor eax,eax ; EAX <- 0
inc eax ; EAX <- 1
mov [esi+RUNQ_NextSched],eax ; remove scheduling request
; ------------- Invalid task state (task is locked)
; cmp byte [ebx+TASK_State],TASK_LOCKED ; is task locked?
; je Schedule12 ; task is locked
; ------------- Fall asleep current task (idle task cannot sleep)
cmp byte [ebx+TASK_State],TASK_FALLASLEEP ; fall asleep?
jne Schedule13 ; task is running
cmp ebx,[esi+RUNQ_Idle] ; is it idle task?
jne Schedule11 ; it is not idle task
mov byte [ebx+TASK_State],TASK_RUNNING ; continue running
jmp short Schedule13 ; sleeping of idle task is not allowed
; ------------- Remove task from run-queue
Schedule11: fnsave [ebx+TASK_FPU] ; save FPU state
call TaskDequeue ; detach task from the queue
dec dword [esi+RUNQ_Total] ; decrease current tasks
; ------------- Store task into wait-queue
call TaskGoWait ; store task into wait-queue
Schedule12: xor ebx,ebx ; EBX <- 0, task is invalid
jmp short Schedule16 ; expire queue test
; ------------- Signals
Schedule13: call DoSignal ; service signals
jc Schedule12 ; task killed
; ------------- Get elapsed time since last service
xor eax,eax ; EAX <- new elapsed time
xchg eax,[esi+RUNQ_Elapsed] ; EAX <- elapsed time
; ------------- Shift task statistik
add [ebx+TASK_RunTime],eax ; shift running time of task
adc dword [ebx+TASK_RunTime+4],byte 0 ; carry
; ------------- Serve NMI
test byte [esi+RUNQ_Flags],B0+B1+B2+B3+B4 ; any event?
jz Schedule15 ; no event
call DoNMI ; NMI service
; ------------- Count remaining time of current task
Schedule15: sub [ebx+TASK_Remaining],eax ; decrease remaining time
jg Schedule2 ; remaining time is OK
call TaskMoveExp ; move task into expire array
Schedule16: mov eax,[esi+RUNQ_Current] ; EAX <- current queue
cmp dword [eax+QUEUE_Mask],byte 0 ; any other task?
jne Schedule3 ; there is any other task
xchg eax,[esi+RUNQ_Expired] ; exchange with expired queue
mov [esi+RUNQ_Current],eax ; new current queue
; ------------- Expire idle task if there are any other tasks
cmp dword [esi+RUNQ_Total],byte 1 ; more than 1 task?
je Schedule3 ; there is only idle task
push ebx ; push current task
mov ebx,[esi+RUNQ_Idle] ; EBX <- idle task
call TaskMoveExp ; move task into expire array
pop ebx ; pop current task
jmp short Schedule3
; ------------- Change priority of the task
Schedule2: call TaskCalcPrio ; recalc new priority
xchg eax,ecx ; ECX <- new priority
call TaskMovePrior ; change priority of the task
mov [ebx+TASK_Priority],cl ; store new priority
; ------------- Get new most priority task (-> EDX)
Schedule3: mov edx,[esi+RUNQ_Current] ; EDX <- current run-queue
mov ecx,[edx+QUEUE_Mask] ; ECX <- mask
bsr ecx,ecx ; find highest priority
mov edx,[edx+QUEUE_List+ecx*LIST_size+LIST_Next]
sub edx,TASK_Queue ; EDX <- new task
; ------------- Check if task changed
cmp edx,ebx ; task changed?
je Schedule5 ; task not changed
; ------------- If old task has low remaining time, move it to expired queue
or ebx,ebx ; is task valid?
jz Schedule42 ; task is not valid
cmp dword [ebx+TASK_Remaining],5*TIME_1MS ; low time?
jae Schedule4 ; enough time remains
cmp byte [ebx+TASK_State],TASK_RUNNING ; is it valid task?
jne Schedule4 ; task went sleep
mov eax,[ebx+TASK_TaskQueue] ; EAX <- task queue
cmp eax,[esi+RUNQ_Current] ; is it in current queue?
jne Schedule4 ; it is already expired
call TaskMoveExp ; move task into expire array
; ------------- Save FPU state
Schedule4: fnsave [ebx+TASK_FPU] ; save FPU state
; ------------- Switch task (it does not realy jump, it only sets TSS register)
Schedule42: jmp far [edx+TASK_TR-4] ; switch to next task
ScheduleRet: clts ; clear task-switched flag
; ------------- Get current task (-> EBX)
CURRENT ebx ; EBX <- get current task
; ------------- Get current run-queue (-> ESI)
mov esi,[ebx+TASK_RunQueue] ; ESI <- run-queue
; ------------- Restore FPU state
frstor [ebx+TASK_FPU] ; restore FPU state
; ------------- Set counter to next scheduling
Schedule5: mov ecx,[ebx+TASK_Remaining] ; ECX <- remaining time
xchg ecx,[esi+RUNQ_NextSched] ; set scheduling counter
or ecx,ecx ; any scheduling request?
jnz Schedule9
popf
jmp Schedule1 ; next schedule
; ------------- Enable interrupts
Schedule9: popf ; enable interrupts
; ------------- Pop registers
pop esi ; pop ESI
pop edx ; pop EDX
pop ecx ; pop ECX
pop ebx ; pop EBX
pop eax ; pop EAX
ret
; -----------------------------------------------------------------------------
; Data
; -----------------------------------------------------------------------------
DATA_SECTION
; ------------- Lists of wake-up tasks
align 4, db 0
WakeUpList:
%rep CPU_MAX ; wake-up lists
LISTHEAD
%endrep
%ifdef SMP
align 4, db 0
WakeUpLock:
%rep CPU_MAX ; wake-up locks
SPINLOCK
%endrep
%endif
; ------------- List of sleeping tasks
align 4, db 0
SleepList: LISTHEAD ; list of sleeping tasks
%ifdef SMP
SleepListLock: SPINLOCK ; lock of sleeping tasks
%endif
; ------------- List of paused tasks
align 4, db 0
PauseList: LISTHEAD ; list of paused tasks
%ifdef SMP
PauseListLock: SPINLOCK ; lock of paused tasks
%endif
; -----------------------------------------------------------------------------
; Uninitialized data
; -----------------------------------------------------------------------------
BSS_SECTION
; ------------- Table of run-queues (one run-queue per CPU)
align 4, resb 1
RunQueue: resb RUNQ_size*CPU_MAX ; table of run-queues
RunQueueAddr: resd CPU_MAX ; addresses of run-queue of CPUs
|