;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; ;; Copyright (C) KolibriOS team 2004-2021. All rights reserved. ;; ;; Copyright (C) MenuetOS 2000-2004 Ville Mikael Turjanmaa ;; ;; Distributed under terms of the GNU General Public License ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; $Revision$ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; IRQ0 HANDLER (TIMER INTERRUPT) ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; align 32 irq0: pushad mov ax, app_data mov ds, ax mov es, ax inc [timer_ticks] mov eax, [timer_ticks] call playNote ; <<<--- Speaker driver sub eax, [next_usage_update] cmp eax, 100 jb .nocounter add [next_usage_update], 100 call updatecputimes .nocounter: xor ecx, ecx ; send End Of Interrupt signal call irq_eoi mov bl, SCHEDULE_ANY_PRIORITY call find_next_task jz .return ; if there is only one running process call do_change_task .return: popad iretd align 4 change_task: pushfd cli pushad mov bl, SCHEDULE_ANY_PRIORITY call find_next_task jz .return ; the same task -> skip switch call do_change_task .return: popad popfd ret uglobal align 4 ; far_jump: ; .offs dd ? ; .sel dw ? context_counter dd 0 ;noname & halyavin next_usage_update dd 0 timer_ticks dd 0 ; prev_slot dd ? ; event_sched dd ? endg align 4 update_counters: ;mov edi, [TASK_BASE] mov esi, [current_slot] rdtsc ;sub eax, [edi+TASKDATA.counter_add] ; time stamp counter add ;add [edi+TASKDATA.counter_sum], eax ; counter sum sub eax, [esi-sizeof.APPDATA+APPDATA.counter_add] ; time stamp counter add add [esi-sizeof.APPDATA+APPDATA.counter_sum], eax ; counter sum ret align 4 updatecputimes: mov ecx, [thread_count] ;mov edi, TASK_DATA mov esi, SLOT_BASE .newupdate: xor eax, eax ;xchg eax, [edi+TASKDATA.counter_sum] ;mov [edi+TASKDATA.cpu_usage], eax xchg eax, [esi+APPDATA.counter_sum] ;add edi, 0x20 mov [esi+APPDATA.cpu_usage], eax add esi, sizeof.APPDATA loop .newupdate ret ;TODO: Надо бы убрать использование do_change_task из V86... ; и после этого перенести обработку TASKDATA.counter_add/sum в do_change_task align 4 do_change_task: ;param: ; ebx = address of the APPDATA for incoming task (new) ;warning: ; [current_slot_idx] and [TASK_BASE] must be changed before (e.g. in find_next_task) ; [current_slot] is the outcoming (old), and set here to a new value (ebx) ;scratched: eax,ecx,esi mov esi, ebx xchg esi, [current_slot] ; set new stack after saving old mov [esi+APPDATA.saved_esp], esp mov esp, [ebx+APPDATA.saved_esp] ; set new thread io-map mov eax, [ebx+APPDATA.io_map] mov dword [page_tabs+((tss._io_map_0 and -4096) shr 10)], eax mov eax, [ebx+APPDATA.io_map+4] mov dword [page_tabs+((tss._io_map_1 and -4096) shr 10)], eax ; set new thread memory-map mov eax, [ebx+APPDATA.process] cmp eax, [current_process] je @f mov [current_process], eax mov eax, [eax+PROC.pdt_0_phys] mov cr3, eax @@: ; set tss.esp0 mov eax, [ebx+APPDATA.saved_esp0] mov [tss._esp0], eax mov edx, [ebx+APPDATA.tls_base] mov [tls_data_l+2], dx shr edx, 16 mov [tls_data_l+4], dl mov [tls_data_l+7], dh mov dx, app_tls mov fs, dx ; set gs selector unconditionally Mov ax, graph_data Mov gs, ax ; TS flag is not triggered by AVX* instructions, therefore ; we have to xsave/xrstor SIMD registers each task change bt [cpu_caps+(CAPS_OSXSAVE/32)*4], CAPS_OSXSAVE mod 32 jnc .no_xsave mov ecx, [esi+APPDATA.fpu_state] mov eax, [xsave_eax] mov edx, [xsave_edx] xsave [ecx] mov ecx, [current_slot_idx] mov [fpu_owner], ecx mov ecx, [current_slot] mov ecx, [ecx+APPDATA.fpu_state] xrstor [ecx] .no_xsave: ; set CR0.TS cmp bh, byte[fpu_owner] ;bh == incoming task (new) clts ;clear a task switch flag je @f mov eax, cr0 ;and set it again if the owner or eax, CR0_TS ;of a fpu has changed mov cr0, eax @@: ; set context_counter (only for user pleasure ???) inc [context_counter] ;noname & halyavin ; set debug-registers, if it's necessary test byte[ebx+APPDATA.dbg_state], 1 jz @f xor eax, eax mov dr6, eax lea esi, [ebx+APPDATA.dbg_regs] cld macro lodsReg [reg] { lodsd mov reg, eax } lodsReg dr0, dr1, dr2, dr3, dr7 purge lodsReg @@: ret ;end. MAX_PRIORITY = 0 ; highest, used for kernel tasks USER_PRIORITY = 1 ; default IDLE_PRIORITY = 2 ; lowest, only IDLE thread goes here NR_SCHED_QUEUES = 3 ; MUST equal IDLE_PRIORYTY + 1 uglobal ; [scheduler_current + i*4] = zero if there are no threads with priority i, ; pointer to APPDATA of the current thread with priority i otherwise. align 4 scheduler_current rd NR_SCHED_QUEUES endg ; Add the given thread to the given priority list for the scheduler. ; in: edx -> APPDATA, ecx = priority proc scheduler_add_thread ; 1. Acquire the lock. spin_lock_irqsave SchedulerLock ; 2. Store the priority in APPDATA structure. mov [edx+APPDATA.priority], ecx ; 3. There are two different cases: the given list is empty or not empty. ; In first case, go to 6. Otherwise, advance to 4. mov eax, [scheduler_current+ecx*4] test eax, eax jz .new_list ; 4. Insert the new item immediately before the current item. mov ecx, [eax+APPDATA.in_schedule.prev] mov [edx+APPDATA.in_schedule.next], eax mov [edx+APPDATA.in_schedule.prev], ecx mov [eax+APPDATA.in_schedule.prev], edx mov [ecx+APPDATA.in_schedule.next], edx ; 5. Release the lock and return. spin_unlock_irqrestore SchedulerLock ret .new_list: ; 6. Initialize the list with one item and make it the current item. mov [edx+APPDATA.in_schedule.next], edx mov [edx+APPDATA.in_schedule.prev], edx mov [scheduler_current+ecx*4], edx ; 7. Release the lock and return. spin_unlock_irqrestore SchedulerLock ret endp ; Remove the given thread from the corresponding priority list for the scheduler. ; in: edx -> APPDATA proc scheduler_remove_thread ; 1. Acquire the lock. spin_lock_irqsave SchedulerLock ; 2. Remove the item from the corresponding list. mov eax, [edx+APPDATA.in_schedule.next] mov ecx, [edx+APPDATA.in_schedule.prev] mov [eax+APPDATA.in_schedule.prev], ecx mov [ecx+APPDATA.in_schedule.next], eax ; 3. If the given thread is the current item in the list, ; advance the current item. ; 3a. Check whether the given thread is the current item; ; if no, skip the rest of this step. mov ecx, [edx+APPDATA.priority] cmp [scheduler_current+ecx*4], edx jnz .return ; 3b. Set the current item to eax; step 2 has set eax = next item. mov [scheduler_current+ecx*4], eax ; 3c. If there were only one item in the list, zero the current item. cmp eax, edx jnz .return mov [scheduler_current+ecx*4], 0 .return: ; 4. Release the lock and return. spin_unlock_irqrestore SchedulerLock ret endp SCHEDULE_ANY_PRIORITY = 0 SCHEDULE_HIGHER_PRIORITY = 1 ;info: ; Find next task to execute ;in: ; bl = SCHEDULE_ANY_PRIORITY: ; consider threads with any priority ; bl = SCHEDULE_HIGHER_PRIORITY: ; consider only threads with strictly higher priority than the current one, ; keep running the current thread if other ready threads have the same or lower priority ;retval: ; ebx = address of the APPDATA for the selected task (slot-base) ; edi = address of the TASKDATA for the selected task ; ZF = 1 if the task is the same ;warning: ; [current_slot_idx] = bh , [TASK_BASE] = edi -- as result ; [current_slot] is not set to new value (ebx)!!! ;scratched: eax,ecx proc find_next_task call update_counters spin_lock_irqsave SchedulerLock push NR_SCHED_QUEUES ; If bl == SCHEDULE_ANY_PRIORITY = 0, loop over all NR_SCHED lists. ; Otherwise, loop over first [APPDATA.priority] lists. test bl, bl jz .start mov ebx, [current_slot] mov edi, [TASK_BASE] mov eax, [ebx+APPDATA.priority] test eax, eax jz .unlock_found mov [esp], eax .start: xor ecx, ecx .priority_loop: mov ebx, [scheduler_current+ecx*4] test ebx, ebx jz .priority_next .task_loop: mov ebx, [ebx+APPDATA.in_schedule.next] mov edi, ebx shr edi, 3 add edi, TASK_TABLE - (SLOT_BASE shr 3) mov al, [edi+TASKDATA.state] test al, al jz .task_found ; state == 0 cmp al, 5 jne .task_next ; state == 1,2,3,4,9 ; state == 5 pushad ; more freedom for [APPDATA.wait_test] call [ebx+APPDATA.wait_test] mov [esp+28], eax popad or eax, eax jnz @f ; testing for timeout mov eax, [timer_ticks] sub eax, [ebx+APPDATA.wait_begin] cmp eax, [ebx+APPDATA.wait_timeout] jb .task_next xor eax, eax @@: mov [ebx+APPDATA.wait_param], eax ; retval for wait mov [edi+TASKDATA.state], TSTATE_RUNNING .task_found: mov [scheduler_current+ecx*4], ebx ; If we have selected a thread with higher priority ; AND rescheduling is due to IRQ, ; turn the current scheduler list one entry back, ; so the current thread will be next after high-priority thread is done. mov ecx, [esp] cmp ecx, NR_SCHED_QUEUES jz .unlock_found mov eax, [current_slot] mov eax, [eax+APPDATA.in_schedule.prev] mov [scheduler_current+ecx*4], eax .unlock_found: pop ecx spin_unlock_irqrestore SchedulerLock .found: ; the line below assumes APPDATA is 256 bytes long and SLOT_BASE is ; aligned on 0x10000 mov byte [current_slot_idx], bh mov [TASK_BASE], edi rdtsc ;call _rdtsc mov [ebx-sizeof.APPDATA+APPDATA.counter_add], eax; for next using update_counters ;mov [edi+TASKDATA.counter_add], eax; for next using update_counters cmp ebx, [current_slot] ret .task_next: cmp ebx, [scheduler_current+ecx*4] jnz .task_loop .priority_next: inc ecx cmp ecx, [esp] jb .priority_loop mov ebx, [current_slot] mov edi, [TASK_BASE] jmp .unlock_found endp if 0 struc TIMER { .next dd ? .exp_time dd ? .func dd ? .arg dd ? } uglobal rdy_head rd 16 endg align 4 pick_task: xor eax, eax .pick: mov ebx, [rdy_head+eax*4] test ebx, ebx jz .next mov [next_task], ebx test [ebx+flags.billable] jz @F mov [bill_task], ebx @@: ret .next: inc eax jmp .pick ; param ; eax= task ; ; retval ; eax= task ; ebx= queue ; ecx= front if 1 or back if 0 align 4 shed: cmp [eax+.tics_left], 0;signed compare mov ebx, [eax+.priority] setg ecx jg @F mov edx, [eax+.tics_quantum] mov [eax+.ticks_left], edx cmp ebx, (IDLE_PRIORITY-1) je @F inc ebx @@: ret ; param ; eax= task align 4 enqueue: call shed;eax cmp [rdy_head+ebx*4], 0 jnz @F mov [rdy_head+ebx*4], eax mov [rdy_tail+ebx*4], eax mov [eax+.next_ready], 0 jmp .pick @@: test ecx, ecx jz .back mov ecx, [rdy_head+ebx*4] mov [eax+.next_ready], ecx mov [rdy_head+ebx*4], eax jmp .pick .back: mov ecx, [rdy_tail+ebx*4] mov [ecx+.next_ready], eax mov [rdy_tail+ebx*4], eax mov [eax+.next_ready], 0 .pick: call pick_proc;select next task ret end if