unit kern_context;

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

interface

uses
 ucontext;

function sys_getcontext(ucp:Pointer):Integer;
function sys_setcontext(ucp:Pointer):Integer;
function sys_swapcontext(oucp,ucp:Pointer):Integer;

function sys_thr_get_ucontext(tid:Integer;ucp:Pointer):Integer;
function sys_thr_suspend_ucontext(tid:Integer):Integer;
function sys_thr_resume_ucontext(tid:Integer):Integer;

implementation

uses
 errno,
 systm,
 kern_thr,
 kern_proc,
 signal,
 kern_sig,
 sched_ule,
 machdep;

{
 * The first two fields of a ucontext_t are the signal mask and the machine
 * context.  The next field is uc_link; we want to avoid destroying the link
 * when copying out contexts.
 }
const
 UC_COPY_SIZE=ptrint(@ucontext_t(nil^).uc_link);
 {$IF UC_COPY_SIZE<>1216}{$STOP UC_COPY_SIZE<>1216}{$ENDIF}

function sys_getcontext(ucp:Pointer):Integer;
var
 td:p_kthread;
 uc:ucontext_t;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (ucp=nil) then Exit(EINVAL);

 bzero(@uc,SizeOf(ucontext_t));

 get_mcontext(td, @uc.uc_mcontext, GET_MC_CLEAR_RET);

 PROC_LOCK();
 uc.uc_sigmask:=td^.td_sigmask;
 PROC_UNLOCK();

 Result:=copyout(@uc, ucp, UC_COPY_SIZE);
end;

function sys_setcontext(ucp:Pointer):Integer;
var
 td:p_kthread;
 uc:ucontext_t;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (ucp=nil) then Exit(EINVAL);

 Result:=copyin(ucp, @uc, UC_COPY_SIZE);
 if (Result=0) then
 begin
  Result:=set_mcontext(td, @uc.uc_mcontext);
  if (Result=0) then
  begin
   kern_sigprocmask(td, SIG_SETMASK, @uc.uc_sigmask, nil, 0);
  end;
 end;

 if (Result=0) then Exit(EJUSTRETURN);
end;

function sys_swapcontext(oucp,ucp:Pointer):Integer;
var
 td:p_kthread;
 uc:ucontext_t;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (oucp=nil) or (ucp=nil) then Exit(EINVAL);

 bzero(@uc,SizeOf(ucontext_t));

 get_mcontext(td, @uc.uc_mcontext, GET_MC_CLEAR_RET);

 PROC_LOCK();
 uc.uc_sigmask:=td^.td_sigmask;
 PROC_UNLOCK();

 Result:=copyout(@uc, oucp, UC_COPY_SIZE);
 if (Result=0) then
 begin
  Result:=copyin(ucp, @uc, UC_COPY_SIZE);
  if (Result=0) then
  begin
   Result:=set_mcontext(td, @uc.uc_mcontext);
   if (Result=0) then
   begin
    kern_sigprocmask(td, SIG_SETMASK, @uc.uc_sigmask, nil, 0);
   end;
  end;
 end;

 if (Result=0) then Exit(EJUSTRETURN);
end;

function sys_thr_get_ucontext(tid:Integer;ucp:Pointer):Integer;
var
 tdf:p_kthread;
 uc:ucontext_t;
begin
 tdf:=tdfind(tid);

 repeat
  if (tdf=nil) then Exit(ESRCH);

  PROC_LOCK;
  thread_lock(tdf);

  if ((tdf^.td_inhibitors and TDI_SUSP_CTX)=0) and
     ((tdf^.td_flags and TDF_SUSP_CTX)=0) then
  begin
   thread_unlock(tdf);
   thread_dec_ref(tdf);
   PROC_UNLOCK;
   Exit(EINVAL);
  end;

  case tdf^.td_state of
   TDS_INACTIVE :; //try again?
   TDS_INHIBITED:
     begin
      bzero(@uc,SizeOf(ucontext_t));

      get_mcontext2(tdf, @uc.uc_mcontext, 0);

      thread_unlock(tdf);
      thread_dec_ref(tdf);
      PROC_UNLOCK;

      Result:=copyout(@uc,ucp,UC_COPY_SIZE);
      Exit;
     end
   else
     begin
      Writeln('get_ucontext: an illegal state ',tdf^.td_state);
      thread_unlock(tdf);
      thread_dec_ref(tdf);
      PROC_UNLOCK;
      Exit(ESRCH);
     end;
  end;

  thread_unlock(tdf);
  thread_dec_ref(tdf);
  PROC_UNLOCK;

  pause('ususp',1);
  tdf:=tdfind(tid);
 until false;
end;

function sys_thr_suspend_ucontext(tid:Integer):Integer;
label
 _exit;
var
 td,tdf,tdf2:p_kthread;
begin
 Result:=0;

 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (tid=td^.td_tid) then Exit(EDEADLK);

 tdf:=tdfind(tid);

 if (tdf=nil) then Exit(ESRCH);

 PROC_LOCK;
 thread_lock(tdf);

 if ((tdf^.td_inhibitors and TDI_SUSP_CTX)=0) and
    ((tdf^.td_flags and TDF_SUSP_CTX)=0) then
 begin
  if ((tdf^.td_inhibitors and TDI_SLEEPING)=0) or
     ((tdf^.td_flags and TDF_SINTR)=0) then
  begin
   tdf^.td_flags:=tdf^.td_flags or (TDF_ASTPENDING or TDF_SUSP_CTX);

   if TD_IS_RUNNING(tdf) then
   begin
    forward_signal(tdf);
   end;

   repeat
    if (tdf^.td_state<>TDS_RUNNING) and
       ((tdf^.td_inhibitors and TDI_SLEEPING)=0) then
    begin
     Result:=0;
     goto _exit;
    end;

    thread_unlock(tdf);
    thread_dec_ref(tdf);
    PROC_UNLOCK;

    pause('ususp',1);
    pause('ususp',1);

    tdf2:=tdfind(tid);

    if (tdf2=nil) then Exit(ESRCH);

    if (tdf<>tdf2) then
    begin
     thread_dec_ref(tdf2);
     Exit(ESRCH);
    end;

    PROC_LOCK;
    thread_lock(tdf);

   until not (((tdf^.td_inhibitors and TDI_SLEEPING)=0) or
              ((tdf^.td_flags and TDF_SINTR)=0));

   tdf^.td_flags:=tdf^.td_flags and (not (TDF_ASTPENDING or TDF_SUSP_CTX));
   tdf^.td_state:=TDS_INHIBITED;
   tdf^.td_inhibitors:=tdf^.td_inhibitors or TDI_SUSP_CTX;

   Result:=0;
  end else
  begin
   tdf^.td_state:=TDS_INHIBITED;
   tdf^.td_inhibitors:=tdf^.td_inhibitors or TDI_SUSP_CTX;

   Result:=0;
  end;
 end else
 begin
  Result:=EINVAL;
 end;

_exit:
 thread_unlock(tdf);
 thread_dec_ref(tdf);
 PROC_UNLOCK;
end;

function sys_thr_resume_ucontext(tid:Integer):Integer;
label
 _exit;
var
 td,tdf:p_kthread;
begin
 Result:=0;

 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (tid=td^.td_tid) then Exit(EINVAL);

 tdf:=tdfind(tid);

 if (tdf=nil) then Exit(ESRCH);

 PROC_LOCK;
 thread_lock(tdf);

 if ((tdf^.td_inhibitors and TDI_SUSP_CTX)=0) then
 begin
  if ((tdf^.td_flags and TDF_SUSP_CTX)=0) then
  begin
   Result:=EINVAL;
   goto _exit;
  end;
  tdf^.td_flags:=tdf^.td_flags and (not TDF_SUSP_CTX);
 end else
 begin
  if ((tdf^.td_flags and TDF_SUSP_CTX)<>0) then
  begin
   tdf^.td_flags:=tdf^.td_flags and (not TDF_SUSP_CTX);
   Result:=0;
   goto _exit;
  end;

  tdf^.td_inhibitors:=tdf^.td_inhibitors and (not TDI_SUSP_CTX);

  if (tdf^.td_inhibitors=0) then
  begin
   tdf^.td_state:=TDS_CAN_RUN;
  end;

  setrunnable(tdf);
 end;

 Result:=0;

_exit:
 thread_unlock(tdf);
 thread_dec_ref(tdf);
 PROC_UNLOCK;
end;


end.

