/*
See TempleOS MultiTasking.

Tasks are ordered in a circular queue
by front-to-back window stack order with
the window mgr as the head (paints
the wallpaper).  The scheduler is
round-robin.  It checking if a task
is ready and runs it or skips it.
Swapping tasks just involves storing and
restoring regs (no disk I/O for virtual
memory and no addr map changes).  It is
always fully identity-mapped on all cores.
Tasks can be switched in half a microsecond.

The scheduler checks if a task is
waiting for a certain time or waiting
on a message and skips if not ready.
A task runs until it voluntarily yields
ctrl with a call to Yield() or gets
preempted by the fixed 1kHz JIFFY_FREQ
timer interrupt. See SYS_TIMER_HANDLER.
Tasks can ask not to be preempted with
Preempt(ON/OFF).        Tasks waiting on I/O
often loop, checking for a status and
Yielding.  This does not really degrade
performance, but pegs the CPU Load.

The scheduler checks for a few critical
key-strokes such as <CTRL-ALT-x>, which
kills a task; <CTRL-ALT-DEL>, which reboots;
<CTRL-ALT-t>, which changes focus and
<CTRL-ALT-c>, which breaks execution
of a program.

Each core has its own circular task queue.
For AP processors, they have a "Seth" task
which stays in a loop waiting for jobs or
requests to spawn tasks.  See MPSethServerTask().
*/

asm {
USE64
// ************************************
TASK_CONTEXT_SAVE::
//OUT:  RSI=FS
        PUSH    RSI
        PUSHFD
        XOR     RSI,RSI
        MOV     RSI,FS:CTask.addr[RSI]
        POP     U64 CTask.rflags[RSI]
        POP     U64 CTask.rsi[RSI]
        MOV     U64 CTask.rax[RSI],RAX

/*
We divert the stk to the Task memory
and push onto it and divert it back.
It's a little faster.
*/
        MOV     RAX,RSP
        LEA     RSP,U64 CTask.r15+8[RSI]
        PUSH    R15
        PUSH    R14
        PUSH    R13
        PUSH    R12
        PUSH    R11
        PUSH    R10
        PUSH    R9
        PUSH    R8
        PUSH    RDI
        PUSH    RBP
        PUSH    RBX
        PUSH    RDX
        PUSH    RCX
        MOV     RSP,RAX

        RDTSC
        SHL     RDX,32
        ADD     RAX,RDX
        SUB     RAX,U64 CTask.time_slice_start[RSI]
        ADD     U64 CTask.total_time[RSI],RAX

        MOV     RAX,U64 CTask.fpu_mmx[RSI]
        FXSAVE  U64 [RAX]

        MOV     RDX,U64 CTask.bpt_lst[RSI]
@@05:   TEST    RDX,RDX
        JZ      @@10
        MOV     RDI,U64 CBpt.addr[RDX]
        MOV     AL,U8 CBpt.val[RDX]
        MOV     U8 [RDI],AL
        MOV     RDX,U64 CBpt.next[RDX]
        JMP     @@05
@@10:   RET
// ************************************
_TASK_CONTEXT_RESTORE::
        XOR     RAX,RAX
        INC     U64 GS:CCPU.swap_cnter[RAX]
        MOV     RSI,FS:CTask.addr[RAX]
        BT      U32 CTask.rflags[RSI],RFLAGf_INT
        JNC     @@05
        BTS     U32 GS:CCPU.cpu_flags[RAX],CPUf_RAN_A_TASK
@@05:   BT      U64 CTask.task_flags[RSI],TASKf_DISABLE_BPTS
        JC      @@15
        MOV     RDX,U64 CTask.bpt_lst[RSI]
@@10:   TEST    RDX,RDX
        JZ      @@15
        MOV     RDI,U64 CBpt.addr[RDX]
        MOV     U8 [RDI],OC_BPT
        MOV     RDX,U64 CBpt.next[RDX]
        JMP     @@10

@@15:   INC     U64 CTask.swap_cnter[RSI]

        MOV     RAX,U64 CTask.fpu_mmx[RSI]
        FXRSTOR U64 [RAX]

        RDTSC
        SHL     RDX,32
        ADD     RAX,RDX
        MOV     U64 CTask.time_slice_start[RSI],RAX

        MOV     RAX,RSP
        LEA     RSP,U64 CTask.rcx[RSI]
        POP     RCX
        POP     RDX
        POP     RBX
        POP     RBP
        POP     RDI
        POP     R8
        POP     R9
        POP     R10
        POP     R11
        POP     R12
        POP     R13
        POP     R14
        POP     R15
        MOV     RSP,RAX

        MOV     RAX,U64 CTask.rax[RSI]
        PUSH    CGDT.ds
        PUSH    U64 CTask.rsp[RSI]
        PUSH    U64 CTask.rflags[RSI]
        PUSH    CGDT.cs64
        PUSH    U64 CTask.rip[RSI]
        MOV     RSI,U64 CTask.rsi[RSI]
        IRET
// ************************************
END_RSI_TASK:
        MOV     RAX,RSI
        CALL    SET_FS_BASE
_TASK_END_NOW::
        CALL    &TaskEnd
        MOV     RSI,RAX
        CALL    SET_FS_BASE
        JMP     I8 RESTORE_RSI_TASK_WITH_KEY_CHECK

RESTORE_RSI_NO_PREEMPT_TASK:
        BT      U32 [SYS_CTRL_ALT_FLAGS],SYSf_CTRL_ALT_DEL
        JC      I32 HANDLE_SWAP_KEY_EVENT
        XOR     RAX,RAX
        CMP     U64 GS:CCPU.idle_task[RAX],RSI  //IDLE TASK HAS PREEMPT-OFF
        JNE     RESTORE_RSI_TASK_NO_KEY_CHECK
        JMP     I32 SWAP_IN_SETH_TASK_IF_READY

_YIELD::
        PUSHFD
        TEST    U8 [SYS_SEMAS+SYS_SEMA_SINGLE_USER*SEMA_STRUCT_SIZE],1
        JNZ     @@05    //If single user, don't change task.

        CLI
        PUSH    RSI
        XOR     RSI,RSI
        MOV     RSI,FS:CTask.addr[RSI]
        CMP     U64 CTask.next_task[RSI],RSI
        JNE     @@15    //Jmp if not just one task on core.

        PUSH    RAX
        MOV     RAX,U64 [SYS_JIFFIES]
        CMP     U64 CTask.wake_jiffy[RSI],RAX
        JG      @@10    //If needs to sleep, jmp and swap-out.

        POP     RAX
        POP     RSI
@@05:   POPFD
        RET

@@10:   POP     RAX
@@15:   POP     RSI
        CALL    TASK_CONTEXT_SAVE

        MOV     EBX,U32 _RET
        MOV     U64 CTask.rip[RSI],RBX
        POP     U64 CTask.rflags[RSI]
        MOV     U64 CTask.rsp[RSI],RSP

RESTORE_NEXT_RSI_TASK:
        MOV     RSI,U64 CTask.next_task[RSI]

RESTORE_RSI_TASK_WITH_KEY_CHECK:
        TEST    U64 [SYS_CTRL_ALT_FLAGS],1<<SYSf_CTRL_ALT_DEL|
1<<SYSf_CTRL_ALT_TAB|1<<SYSf_CTRL_ALT_X|1<<SYSf_CTRL_ALT_C
        JNZ     HANDLE_SWAP_KEY_EVENT

RESTORE_RSI_TASK_NO_KEY_CHECK:
@@20:   BT      U64 CTask.task_flags[RSI],TASKf_KILL_TASK
        JC      I32 END_RSI_TASK
        TEST    U64 CTask.task_flags[RSI],1<<TASKf_AWAITING_MSG|1<<TASKf_SUSPENDED
        JNZ     @@25

        MOV     RAX,U64 [SYS_JIFFIES]
        CMP     U64 CTask.wake_jiffy[RSI],RAX
        JG      @@25    //Jmp if not ready, yet.

        CMP     U32 CTask.task_in_que_signature[RSI],TASK_IN_QUE_SIGNATURE_VAL
        JNE     SWAP_IN_SETH_TASK_IF_READY
        MOV     RAX,RSI
        CALL    SET_FS_BASE
        JMP     I32 _TASK_CONTEXT_RESTORE

@@25:   MOV     RSI,U64 CTask.next_task[RSI]
        XOR     RAX,RAX
        CMP     U64 GS:CCPU.seth_task[RAX],RSI
        JNE     @@20    //Jmp if not Seth
        BTR     U32 GS:CCPU.cpu_flags[RAX],CPUf_RAN_A_TASK
        JC      @@20    //Jmp if had chance for IRQ already
        MOV     RAX,U64 GS:CCPU.idle_task[RAX]
        MOV     RSP,U64 CTask.stk[RAX]
        ADD     RSP,DFT_STK+CTaskStk.stk_base   //Rst to top
        CALL    SET_FS_BASE
        STI     //Swap-in idle task so we can unmask IRQs.
        HLT
SYS_IDLE_PT::
        CLI

SWAP_IN_SETH_TASK_IF_READY:
        XOR     RAX,RAX
        MOV     RSI,GS:CCPU.seth_task[RAX]
        JMP     I32 RESTORE_RSI_TASK_WITH_KEY_CHECK

HANDLE_SWAP_KEY_EVENT:
        MOV     RAX,RSI
        CALL    SET_FS_BASE
        XOR     RAX,RAX
        MOV     RAX,GS:CCPU.num[RAX]
        TEST    RAX,RAX
        JNZ     I32 RESTORE_RSI_TASK_NO_KEY_CHECK

        MOV     EAX,U32 SYS_CTRL_ALT_FLAGS
        LOCK
        BTR     U32 [RAX],SYSf_CTRL_ALT_DEL
        JC      I32 &Reboot

        LOCK
        BTR     U32 [RAX],SYSf_CTRL_ALT_TAB
        JNC     @@05
        PUSH    RSI
        MOV     RSI,U64 [SYS_FOCUS_TASK]
        MOV     U64 [SYS_FOCUS_TASK],0
        CALL    FOCUS_RSI_NEXT_USER
        POP     RSI
        JMP     I32 RESTORE_FS_TASK_WITH_KEY_CHECK

@@05:   LOCK
        BTR     U32 [RAX],SYSf_CTRL_ALT_X
        JC      END_FOCUS_USER
        LOCK
        BTR     U32 [RAX],SYSf_CTRL_ALT_C
        JNC     I32 RESTORE_RSI_TASK_WITH_KEY_CHECK

BREAK_FOCUS_USER:
        MOV     RSI,U64 [SYS_FOCUS_TASK]
        TEST    RSI,RSI
        JZ      SWAP_IN_SETH_TASK_IF_READY
        BT      U64 CTask.win_inhibit[RSI],WIf_SELF_FOCUS
        JC      I32 RESTORE_RSI_TASK_NO_KEY_CHECK
        LOCK
        BTR     U64 CTask.task_flags[RSI],TASKf_BREAK_LOCKED
        JNC     @@10
        LOCK
        BTS     U64 CTask.task_flags[RSI],TASKf_PENDING_BREAK
        JMP     I32 RESTORE_RSI_TASK_NO_KEY_CHECK

@@10:   MOV     RAX,&SysThrowBreak
        MOV     U64 CTask.rip[RSI],RAX
        BT      U64 CTask.task_flags[RSI],TASKf_BREAK_TO_SHIFT_ESC
        JC      @@15

//We do these two now, in case interrupt happens
        MOV     U64 CTask.wake_jiffy[RSI],0
        PUSH    RSI
        CALL    &TaskRstAwaitingMsg

@@15:   JMP     I32 RESTORE_RSI_TASK_NO_KEY_CHECK

END_FOCUS_USER:
        MOV     RSI,U64 [SYS_FOCUS_TASK]
        MOV     U64 [SYS_FOCUS_TASK],0
        PUSH    RSI
        CALL    FOCUS_RSI_NEXT_USER
        POP     RSI
        TEST    RSI,RSI
        JZ      I32 SWAP_IN_SETH_TASK_IF_READY
        MOV     RAX,RSI
        CALL    SET_FS_BASE
        BT      U64 CTask.win_inhibit[RSI],WIf_SELF_FOCUS
        JC      I32 RESTORE_RSI_TASK_WITH_KEY_CHECK
        LOCK
        BTS     U64 CTask.task_flags[RSI],TASKf_KILL_TASK
        JMP     I32 END_RSI_TASK

RESTORE_FS_TASK_WITH_KEY_CHECK:
        XOR     RSI,RSI
        MOV     RSI,FS:CTask.addr[RSI]
        JMP     I32 RESTORE_RSI_TASK_WITH_KEY_CHECK

FOCUS_RSI_NEXT_USER:
        TEST    RSI,RSI
        JNZ     @@20
        MOV     ESI,U32 SYS_FIXED_AREA+CSysFixedArea.adam
@@20:   MOV     RDI,RSI
        MOV     RSI,U64 CTask.next_task[RSI]
@@25:   BT      U64 CTask.win_inhibit[RSI],WIf_SELF_FOCUS
        JC      @@30
        CMP     U64 [SYS_FOCUS_TASK],RSI
        JE      @@30
        MOV     U64 [SYS_FOCUS_TASK],RSI
        MOV     RAX,U64 [SYS_EXTERN_TABLE]
        MOV     RAX,U64 EXT_WIN_TO_TOP*8[RAX]
        TEST    RAX,RAX
        JZ      @@35
        PUSH    TRUE
        PUSH    RSI
        CALL    RAX
        JMP     @@35
@@30:   MOV     RSI,U64 CTask.next_task[RSI]
        CMP     RSI,RDI
        JNE     @@25
        MOV     U64 [SYS_FOCUS_TASK],0
@@35:   RET

}

_extern _TASK_CONTEXT_RESTORE U0 TaskContextRestore();
_extern _YIELD U0 Yield();
_extern _TASK_END_NOW U0 TaskEndNow();

U0 TaskQueIns(CTask *task,CTask *pred=NULL)
{//Insert a task in the scheduler running task queue.
//You have no business with this, probably.
  CTask *last;
  PUSHFD
  CLI
  if (!pred) pred=Fs;
  last=pred->last_task;
  last->next_task=pred->last_task=task;
  task->last_task=last;
  task->next_task=pred;
  task->task_in_que_signature=TASK_IN_QUE_SIGNATURE_VAL;
  POPFD
}

U0 TaskQueRem(CTask *task)
{//Remove a task from the scheduler running task queue.
//Use Suspend().
  CTask *next,*last;
  PUSHFD
  CLI
  task->task_in_que_signature=0;
  next=task->next_task;
  last=task->last_task;
  last->next_task=next;
  next->last_task=last;
  POPFD
}

U0 TaskQueInsChild(CTask *task)
{
  CTask *last,*pred;
  PUSHFD
  CLI
  pred=task->parent_task->last_child_task;
  last=pred->last_sibling_task;
  last->next_sibling_task=pred->last_sibling_task=task;
  task->last_sibling_task=last;
  task->next_sibling_task=pred;
  POPFD
}