unit kern_thr;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface

uses
 mqueue,
 ucontext,
 signal,
 signalvar,
 kern_mtx;

const
 TDS_INACTIVE =0;
 TDS_INHIBITED=1;
 TDS_CAN_RUN  =2;
 TDS_RUNQ     =3;
 TDS_RUNNING  =4;

 TDF_BORROWING  =$00000001; // Thread is borrowing pri from another.
 TDF_INPANIC    =$00000002; // Caused a panic, let it drive crashdump.
 TDF_INMEM      =$00000004; // Thread's stack is in memory.
 TDF_SINTR      =$00000008; // Sleep is interruptible.
 TDF_TIMEOUT    =$00000010; // Timing out during sleep.
 TDF_IDLETD     =$00000020; // This is a per-CPU idle thread.
 TDF_CANSWAP    =$00000040; // Thread can be swapped.
 TDF_SLEEPABORT =$00000080; // sleepq_abort was called.
 TDF_KTH_SUSP   =$00000100; // kthread is suspended
 TDF_UNUSED09   =$00000200; // --available--
 TDF_BOUNDARY   =$00000400; // Thread suspended at user boundary
 TDF_ASTPENDING =$00000800; // Thread has some asynchronous events.
 TDF_TIMOFAIL   =$00001000; // Timeout from sleep after we were awake.
 TDF_SBDRY      =$00002000; // Stop only on usermode boundary.
 TDF_UPIBLOCKED =$00004000; // Thread blocked on user PI mutex.
 TDF_NEEDSUSPCHK=$00008000; // Thread may need to suspend.
 TDF_NEEDRESCHED=$00010000; // Thread needs to yield.
 TDF_NEEDSIGCHK =$00020000; // Thread may need signal delivery.
 TDF_NOLOAD     =$00040000; // Ignore during load avg calculations.
 TDF_UNUSED19   =$00080000; // --available--
 TDF_THRWAKEUP  =$00100000; // Libthr thread must not suspend itself.
 TDF_UNUSED21   =$00200000; // --available--
 TDF_SWAPINREQ  =$00400000; // Swapin request due to wakeup.
 TDF_UNUSED23   =$00800000; // --available--
 TDF_SCHED0     =$01000000; // Reserved for scheduler private use
 TDF_SCHED1     =$02000000; // Reserved for scheduler private use
 TDF_SCHED2     =$04000000; // Reserved for scheduler private use
 TDF_SCHED3     =$08000000; // Reserved for scheduler private use
 TDF_ALRMPEND   =$10000000; // Pending SIGVTALRM needs to be posted.
 TDF_PROFPEND   =$20000000; // Pending SIGPROF needs to be posted.
 TDF_MACPEND    =$40000000; // AST-based MAC event pending.

 TDF_SLICEEND   =TDF_SCHED2; // Thread time slice is over.

 TDF_SUSP_CTX   =$80000000;  // Sony extension

 //

 TDP_OLDMASK      =$00000001; // Need to restore mask after suspend.
 TDP_INKTR        =$00000002; // Thread is currently in KTR code.
 TDP_INKTRACE     =$00000004; // Thread is currently in KTRACE code.
 TDP_BUFNEED      =$00000008; // Do not recurse into the buf flush
 TDP_COWINPROGRESS=$00000010; // Snapshot copy-on-write in progress.
 TDP_ALTSTACK     =$00000020; // Have alternate signal stack.
 TDP_DEADLKTREAT  =$00000040; // Lock aquisition - deadlock treatment.
 TDP_NOFAULTING   =$00000080; // Do not handle page faults.
 TDP_NOSLEEPING   =$00000100; // Thread is not allowed to sleep on a sq.
 TDP_OWEUPC       =$00000200; // Call addupc() at next AST.
 TDP_ITHREAD      =$00000400; // Thread is an interrupt thread.
 TDP_SYNCIO       =$00000800; // Local override, disable async i/o.
 TDP_SCHED1       =$00001000; // Reserved for scheduler private use
 TDP_SCHED2       =$00002000; // Reserved for scheduler private use
 TDP_SCHED3       =$00004000; // Reserved for scheduler private use
 TDP_SCHED4       =$00008000; // Reserved for scheduler private use
 TDP_GEOM         =$00010000; // Settle GEOM before finishing syscall
 TDP_SOFTDEP      =$00020000; // Stuck processing softdep worklist
 TDP_NORUNNINGBUF =$00040000; // Ignore runningbufspace check
 TDP_WAKEUP       =$00080000; // Don't sleep in umtx cond_wait
 TDP_INBDFLUSH    =$00100000; // Already in BO_BDFLUSH, do not recurse
 TDP_KTHREAD      =$00200000; // This is an official kernel thread
 TDP_CALLCHAIN    =$00400000; // Capture thread's callchain
 TDP_IGNSUSP      =$00800000; // Permission to ignore the MNTK_SUSPEND*
 TDP_AUDITREC     =$01000000; // Audit record pending on thread
 TDP_RFPPWAIT     =$02000000; // Handle RFPPWAIT on syscall exit
 TDP_RESETSPUR    =$04000000; // Reset spurious page fault history.
 TDP_NERRNO       =$08000000; // Last errno is already in td_errno
 TDP_UIOHELD      =$10000000; // Current uio has pages held in td_ma
 TDP_DEVMEMIO     =$20000000; // Accessing memory for /dev/mem
 TDP_EXECVMSPC    =$40000000; // Execve destroyed old vmspace

 TDI_SUSPENDED    =$0001; // On suspension queue.
 TDI_SLEEPING     =$0002; // Actually asleep! (tricky).
 TDI_SWAPPED      =$0004; // Stack not in mem.  Bad juju if run.
 TDI_LOCK         =$0008; // Stopped on a lock.
 TDI_IWAIT        =$0010; // Awaiting interrupt.

 TDI_SUSP_CTX     =$0020; // Sony extension

 THR_SUSPENDED    =$0001;

 // These flags are kept in p_flag.
 P_ADVLOCK        =$00001; // Process may hold a POSIX advisory lock.
 P_CONTROLT       =$00002; // Has a controlling terminal.
 P_WEXIT          =$02000; // Working on exiting.
 P_EXEC           =$04000; // Process called exec.
 P_INEXEC       =$4000000; // Process is in execve().

 // Types and flags for mi_switch().
 SW_TYPE_MASK     =$ff; // First 8 bits are switch type
 SWT_NONE          = 0; // Unspecified switch.
 SWT_PREEMPT       = 1; // Switching due to preemption.
 SWT_OWEPREEMPT    = 2; // Switching due to opepreempt.
 SWT_TURNSTILE     = 3; // Turnstile contention.
 SWT_SLEEPQ        = 4; // Sleepq wait.
 SWT_SLEEPQTIMO    = 5; // Sleepq timeout wait.
 SWT_RELINQUISH    = 6; // yield call.
 SWT_NEEDRESCHED   = 7; // NEEDRESCHED was set.
 SWT_IDLE          = 8; // Switching from the idle thread.
 SWT_IWAIT         = 9; // Waiting for interrupts.
 SWT_SUSPEND       =10; // Thread suspended.
 SWT_REMOTEPREEMPT =11; // Remote processor preempted.
 SWT_REMOTEWAKEIDLE=12; // Remote processor preempted idle.
 SWT_COUNT         =13; // Number of switch types.

 // Flags
 SW_VOL    =$0100; // Voluntary switch.
 SW_INVOL  =$0200; // Involuntary switch.
 SW_PREEMPT=$0400; // The invol switch is a preemption

 PCB_FULL_IRET=1;
 PCB_IS_JIT   =2;
 PCB_IS_HLE   =4;

type
 p_teb=^teb;
 teb=packed record
  SEH              :Pointer;
  stack            :Pointer;
  sttop            :Pointer;
  SubSystemTib     :Pointer;
  FiberData        :Pointer;
  Arbitrary        :Pointer;
  TIB              :Pointer;
  _align1          :array[0..24] of QWORD;
  WOW64            :Pointer;                 //0x100
  _align2          :array[0..18] of QWORD;
  _resrv1          :array[0..3] of QWORD;
  thread           :Pointer;                 //0x1C0
  fsbase           :Pointer;                 //0x1C8
  gsbase           :Pointer;                 //0x1D0
  jitcall          :Pointer;                 //0x1D8
  jit_trp          :Pointer;                 //0x1E0
  jit____          :Pointer;                 //0x1E8
  iflag            :QWORD;                   //0x1F0
  _resrv2          :array[0..17] of QWORD;
  _align3          :array[0..7] of QWORD;
  actctx           :Pointer;                 //0x2C8
  _align4          :array[0..180] of QWORD;
  _align5          :array[0..383] of QWORD;
  DeallocationStack:Pointer;                 //0x1478
 end;

const
 teb_stack  =ptruint(@teb(nil^).stack  );
 teb_sttop  =ptruint(@teb(nil^).sttop  );
 teb_jitcall=ptruint(@teb(nil^).jitcall);
 teb_thread =ptruint(@teb(nil^).thread );
 teb_fsbase =ptruint(@teb(nil^).fsbase );
 teb_gsbase =ptruint(@teb(nil^).gsbase );
 teb_actctx =ptruint(@teb(nil^).actctx );
 teb_jit_trp=ptruint(@teb(nil^).jit_trp);
 teb_iflag  =ptruint(@teb(nil^).iflag  );
 teb_dealloc=ptruint(@teb(nil^).DeallocationStack);

 {$IF teb_jitcall<>$01D8}{$STOP teb_jitcall<>$01D8}{$ENDIF}
 {$IF teb_thread <>$01C0}{$STOP teb_thread <>$01C0}{$ENDIF}
 {$IF teb_actctx <>$02C8}{$STOP teb_actctx <>$02C8}{$ENDIF}
 {$IF teb_dealloc<>$1478}{$STOP teb_dealloc<>$1478}{$ENDIF}

type
 t_td_name=array[0..31] of AnsiChar;

 t_td_stack=packed record
  stack:Pointer;
  sttop:Pointer;
 end;

 t_td_buffer=packed record
  addr:Pointer;
  size:QWORD;
 end;

 p_td_jctx=^t_td_jctx;
 t_td_jctx=packed record
  //block:Pointer;
  rsp:Pointer;
  rbp:Pointer;
  local_cache:array[0..255] of Pointer;
  call_ret_cache:Pointer;
 end;

 pp_kthread=^p_kthread;
 p_kthread=^kthread;
 kthread=record
  td_plist        :TAILQ_ENTRY;
  td_umtxq        :Pointer; //p_umtx_q
  td_handle       :THandle; //nt thread
  td_teb          :p_teb;
  td_lock         :p_mtx;
  td_tid          :QWORD;
  td_sigstk       :stack_t;
  td_state        :Integer;
  td_pflags       :Integer;
  td_flags        :Integer;
  td_errno        :Integer;
  pcb_flags       :Integer;
  td_ref          :Integer;
  //
  td_priority     :Word;
  td_pri_class    :Word;
  td_base_pri     :Word;
  td_base_user_pri:Word;
  td_lend_user_pri:Word;
  td_user_pri     :Word;
  td_name         :t_td_name;
  //
  td_sigmask      :sigset_t;
  td_oldsigmask   :sigset_t;
  td_sigqueue     :sigqueue_t;
  td_frame        :trapframe;
  td_fpstate      :t_fpstate;
  td_retval       :array[0..1] of QWORD;
  td_jctx         :t_td_jctx;
  td_ustack       :t_td_stack;
  td_kstack       :t_td_stack;
  //
  td_cpuset       :Ptruint;
  td_sleepqueue   :Pointer;
  td_slpq         :TAILQ_ENTRY;
  td_zombie       :TAILQ_ENTRY;
  td_wchan        :Pointer;
  td_wmesg        :PChar;
  td_sqqueue      :Integer;
  td_intrval      :Integer;
  td_inhibitors   :Integer;
  td_dupfd        :Integer;
  td_ru           :packed record
   ru_inblock     :Int64;
   ru_oublock     :Int64;
   ru_nsignals    :Int64;
   ru_nvcsw       :Int64;
   ru_nivcsw      :Int64;
  end;
  //
  td_slptick      :Int64;
  td_slpcallout   :Pointer;
  tdq_lock        :mtx;
  {$IFDEF DEBUG_MTX}
  td_debug_mtx    :p_mtx;
  {$ENDIF}
  //
  td_rlqe         :Pointer;
  td_fpop         :Pointer;
  td_map_cookie   :Pointer;
  td_map_def_user :Pointer;
  td_rmap_def_user:Pointer;
  td_sel          :Pointer;
  td_vp_reserv    :Int64;
  pcb_fsbase      :Pointer;
  pcb_gsbase      :Pointer;
  pcb_onfault     :Pointer;
  td_guards       :array[0..1] of Pointer;
  td_temp         :t_td_buffer;
  td_padding      :t_td_buffer;
 end;

const
 kthread_fpstate=ptruint(@kthread(nil^).td_fpstate);

 {$IF (kthread_fpstate mod 64)<>0}
  {$STOP kthread.td_fpstate must be 64 aligned}
 {$ENDIF}

type
 p_thr_param=^thr_param;
 thr_param=packed record
  start_func:Pointer;
  arg       :Pointer;
  stack_base:Pointer;
  stack_size:Ptruint;
  tls_base  :Pointer;
  tls_size  :Ptruint;
  child_tid :PDWORD;
  parent_tid:PDWORD;
  flags     :Integer;
  align     :Integer;
  rtp       :Pointer;
  name      :PChar;
  spare     :array[0..1] of Pointer;
 end;
 {$IF sizeof(thr_param)<>104}{$STOP sizeof(thr_param)<>104}{$ENDIF}

function  curkthread:p_kthread;
procedure set_curkthread(td:p_kthread);

const
 SIG_ALTERABLE=$80000000;
 SIG_STI_LOCK =$40000000;

 NOT_PCB_FULL_IRET=not PCB_FULL_IRET;
 NOT_SIG_ALTERABLE=not SIG_ALTERABLE;
 NOT_SIG_STI_LOCK =not SIG_STI_LOCK;

 TDF_AST=TDF_ASTPENDING or TDF_NEEDRESCHED;

procedure sig_sta; assembler;
procedure sig_cla; assembler;
procedure sig_sti; assembler;
procedure sig_cli; assembler;

procedure set_pcb_flags(td:p_kthread;f:Integer);

function  TD_IS_SLEEPING(td:p_kthread):Boolean;
function  TD_ON_SLEEPQ(td:p_kthread):Boolean;
function  TD_IS_SUSPENDED(td:p_kthread):Boolean;
function  TD_IS_SWAPPED(td:p_kthread):Boolean;
function  TD_ON_LOCK(td:p_kthread):Boolean;
function  TD_AWAITING_INTR(td:p_kthread):Boolean;
function  TD_IS_RUNNING(td:p_kthread):Boolean;
function  TD_ON_RUNQ(td:p_kthread):Boolean;
function  TD_CAN_RUN(td:p_kthread):Boolean;
function  TD_IS_INHIBITED(td:p_kthread):Boolean;
function  TD_ON_UPILOCK(td:p_kthread):Boolean;
function  TD_IS_IDLETHREAD(td:p_kthread):Boolean;

procedure TD_SET_SLEEPING(td:p_kthread);
procedure TD_SET_SWAPPED(td:p_kthread);
procedure TD_SET_LOCK(td:p_kthread);
procedure TD_SET_SUSPENDED(td:p_kthread);
procedure TD_SET_IWAIT(td:p_kthread);

procedure TD_CLR_SLEEPING(td:p_kthread);
procedure TD_CLR_SWAPPED(td:p_kthread);
procedure TD_CLR_LOCK(td:p_kthread);
procedure TD_CLR_SUSPENDED(td:p_kthread);
procedure TD_CLR_IWAIT(td:p_kthread);

procedure TD_SET_RUNNING(td:p_kthread);
procedure TD_SET_RUNQ(td:p_kthread);
procedure TD_SET_CAN_RUN(td:p_kthread);

procedure THREAD_NO_SLEEPING();
procedure THREAD_SLEEPING_OK();
function  THREAD_IS_NOSLEEPING:Boolean;

function  SIGPENDING(td:p_kthread):Boolean; external;

function  curthread_pflags_set(flags:Integer):Integer;
procedure curthread_pflags_restore(save:Integer);
procedure curthread_set_pcb_onfault(v:Pointer);

procedure threads_lock;            external;
function  threads_trylock:Boolean; external;
procedure threads_unlock;          external;

function  get_p_threads:Pointer;   external;

procedure thread_inc_ref(td:p_kthread); external;
procedure thread_dec_ref(td:p_kthread); external;

procedure thread_lock_assert(td:p_kthread); external;
procedure thread_lock(td:p_kthread);   external;
procedure thread_unlock(td:p_kthread); external;

function  tdfind(tid:DWORD):p_kthread; external;

procedure thread_suspend_all(exclude:p_kthread); external;
procedure thread_resume_all (exclude:p_kthread); external;

function  kthread_add(func,arg:Pointer;newtdp:pp_kthread;pages:Word;name:PChar):Integer; external;
procedure kthread_exit(); external;

function  thread_get_local_buffer(td:p_kthread;size:QWORD):Pointer;
procedure thread_free_local_buffer(td:p_kthread);

implementation

function curkthread:p_kthread; assembler; nostackframe;
asm
 movqq %gs:teb.thread,Result
end;

procedure set_curkthread(td:p_kthread); assembler; nostackframe;
asm
 movqq td,%gs:teb.thread
end;

procedure sig_sta; assembler; nostackframe;
asm
 lock orl SIG_ALTERABLE,%gs:teb.iflag
end;

procedure sig_cla; assembler; nostackframe;
asm
 lock andl NOT_SIG_ALTERABLE,%gs:teb.iflag
end;

procedure sig_sti; assembler; nostackframe;
asm
 lock orl SIG_STI_LOCK,%gs:teb.iflag
end;

procedure sig_cli; assembler; nostackframe;
asm
 lock andl NOT_SIG_STI_LOCK,%gs:teb.iflag
end;

procedure set_pcb_flags(td:p_kthread;f:Integer);
begin
 td^.pcb_flags:=f or (td^.pcb_flags and (PCB_IS_JIT or PCB_IS_HLE));
end;

function TD_IS_SLEEPING(td:p_kthread):Boolean;
begin
 Result:=(td^.td_inhibitors and TDI_SLEEPING)<>0;
end;

function TD_ON_SLEEPQ(td:p_kthread):Boolean;
begin
 Result:=(td^.td_wchan<>nil);
end;

function TD_IS_SUSPENDED(td:p_kthread):Boolean;
begin
 Result:=(td^.td_inhibitors and TDI_SUSPENDED)<>0;
end;

function TD_IS_SWAPPED(td:p_kthread):Boolean;
begin
 Result:=(td^.td_inhibitors and TDI_SWAPPED)<>0;
end;

function TD_ON_LOCK(td:p_kthread):Boolean;
begin
 Result:=(td^.td_inhibitors and TDI_LOCK)<>0;
end;

function TD_AWAITING_INTR(td:p_kthread):Boolean;
begin
 Result:=(td^.td_inhibitors and TDI_IWAIT)<>0;
end;

function TD_IS_RUNNING(td:p_kthread):Boolean;
begin
 Result:=(td^.td_state=TDS_RUNNING);
end;

function TD_ON_RUNQ(td:p_kthread):Boolean;
begin
 Result:=(td^.td_state=TDS_RUNQ);
end;

function TD_CAN_RUN(td:p_kthread):Boolean;
begin
 Result:=(td^.td_state=TDS_CAN_RUN);
end;

function TD_IS_INHIBITED(td:p_kthread):Boolean;
begin
 Result:=(td^.td_state=TDS_INHIBITED);
end;

function TD_ON_UPILOCK(td:p_kthread):Boolean;
begin
 Result:=(td^.td_flags and TDF_UPIBLOCKED)<>0;
end;

function TD_IS_IDLETHREAD(td:p_kthread):Boolean;
begin
 Result:=(td^.td_flags and TDF_IDLETD)<>0;
end;

procedure TD_SET_INHIB(td:p_kthread;inhib:Integer); inline;
begin
 td^.td_state:=TDS_INHIBITED;
 td^.td_inhibitors:=td^.td_inhibitors or inhib;
end;

procedure TD_CLR_INHIB(td:p_kthread;inhib:Integer); inline;
begin
 if ((td^.td_inhibitors and inhib)<>0) then
 begin
  inhib:=td^.td_inhibitors and (not inhib);
  td^.td_inhibitors:=inhib;
  if (inhib=0) then
  begin
   td^.td_state:=TDS_CAN_RUN;
  end;
 end;
end;

procedure TD_SET_SLEEPING(td:p_kthread);
begin
 TD_SET_INHIB(td,TDI_SLEEPING);
end;

procedure TD_SET_SWAPPED(td:p_kthread);
begin
 TD_SET_INHIB(td,TDI_SWAPPED);
end;

procedure TD_SET_LOCK(td:p_kthread);
begin
 TD_SET_INHIB(td,TDI_LOCK);
end;

procedure TD_SET_SUSPENDED(td:p_kthread);
begin
 TD_SET_INHIB(td,TDI_SUSPENDED);
end;

procedure TD_SET_IWAIT(td:p_kthread);
begin
 TD_SET_INHIB(td,TDI_IWAIT);
end;


procedure TD_CLR_SLEEPING(td:p_kthread);
begin
 TD_CLR_INHIB(td,TDI_SLEEPING);
end;

procedure TD_CLR_SWAPPED(td:p_kthread);
begin
 TD_CLR_INHIB(td,TDI_SWAPPED);
end;

procedure TD_CLR_LOCK(td:p_kthread);
begin
 TD_CLR_INHIB(td,TDI_LOCK);
end;

procedure TD_CLR_SUSPENDED(td:p_kthread);
begin
 TD_CLR_INHIB(td,TDI_SUSPENDED);
end;

procedure TD_CLR_IWAIT(td:p_kthread);
begin
 TD_CLR_INHIB(td,TDI_IWAIT);
end;

procedure TD_SET_RUNNING(td:p_kthread);
begin
 td^.td_state:=TDS_RUNNING;
end;

procedure TD_SET_RUNQ(td:p_kthread);
begin
 td^.td_state:=TDS_RUNQ;
end;

procedure TD_SET_CAN_RUN(td:p_kthread);
begin
 td^.td_state:=TDS_CAN_RUN;
end;

procedure THREAD_NO_SLEEPING();
var
 td:p_kthread;
begin
 td:=curkthread;
 Assert((td^.td_pflags and TDP_NOSLEEPING)=0,'nested no sleeping');
 td^.td_pflags:=td^.td_pflags or TDP_NOSLEEPING;
end;

procedure THREAD_SLEEPING_OK();
var
 td:p_kthread;
begin
 td:=curkthread;
 Assert((td^.td_pflags and TDP_NOSLEEPING)<>0,'nested sleeping ok');
 td^.td_pflags:=td^.td_pflags and (not TDP_NOSLEEPING);
end;

function THREAD_IS_NOSLEEPING:Boolean;
var
 td:p_kthread;
begin
 td:=curkthread;
 Result:=(td^.td_pflags and TDP_NOSLEEPING)<>0;
end;

//

function curthread_pflags_set(flags:Integer):Integer;
var
 td:p_kthread;
begin
 td:=curkthread;
 if (td=nil) then Exit(0);
 Result:=(not flags) or (td^.td_pflags and flags);
 td^.td_pflags:=td^.td_pflags or flags;
end;

procedure curthread_pflags_restore(save:Integer);
var
 td:p_kthread;
begin
 td:=curkthread;
 if (td=nil) then Exit;
 td^.td_pflags:=td^.td_pflags and save;
end;

procedure curthread_set_pcb_onfault(v:Pointer);
var
 td:p_kthread;
begin
 td:=curkthread;
 if (td=nil) then Exit;
 td^.pcb_onfault:=v;
end;

//

function thread_get_local_buffer(td:p_kthread;size:QWORD):Pointer;
begin
 if (td=nil) then Exit(nil);

 if (size<=td^.td_padding.size) then
 begin
  Result:=td^.td_padding.addr;
 end else
 if (size<=td^.td_temp.size) then
 begin
  Result:=td^.td_temp.addr;
 end else
 begin
  if (td^.td_temp.addr<>nil) then
  begin
   FreeMem(td^.td_temp.addr);
  end;
  Result:=GetMem(size);
  td^.td_temp.addr:=Result;
  td^.td_temp.size:=MemSize(Result);
 end;

end;

procedure thread_free_local_buffer(td:p_kthread);
begin
 if (td=nil) then Exit;

 if (td^.td_temp.addr<>nil) then
 begin
  FreeMem(td^.td_temp.addr);
 end;
 td^.td_temp:=Default(t_td_buffer);
end;

end.

