unit kern_umtx;

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

interface

uses
 atomic,
 mqueue,
 kern_mtx,
 time,
 kern_thr,
 _umtx,
 rtprio;

procedure _umutex_init(mtx:p_umutex); inline;

procedure umtx_thread_init(td:p_kthread);
procedure umtx_thread_exit(td:p_kthread);
procedure umtx_thread_fini(td:p_kthread);

function  sys__umtx_lock(mtx:Pointer):Integer;
function  sys__umtx_unlock(mtx:Pointer):Integer;
function  sys__umtx_op(obj:Pointer;op:Integer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;

//

function  kern_umtx_wake(td:p_kthread;umtx:p_umtx;n_wake,priv:Integer):Integer;

function  umtx_copyin_timeout(addr:Pointer;tsp:p_timespec):Integer;

procedure umtxq_sysinit; //SYSINIT(umtx, SI_SUB_EVENTHANDLER+1, SI_ORDER_MIDDLE, umtxq_sysinit, NULL);

const
 _UMUTEX_TRY =1;
 _UMUTEX_WAIT=2;

 UQF_UMTXQ=$0001;

 UMTX_SHARED_QUEUE   =0;
 UMTX_EXCLUSIVE_QUEUE=1;

 GOLDEN_RATIO_PRIME=2654404609;
 UMTX_CHAINS       =512;
 UMTX_SHIFTS       =(32 - 9);

 BUSY_SPINS        =200;

type
 // Priority inheritance mutex info.
 p_umtx_pi=^umtx_pi;
 umtx_pi=record
  // Owner thread
  pi_owner:p_kthread;

  // Reference count
  pi_refcount:Integer;

  // List entry to link umtx holding by thread
  pi_link:TAILQ_ENTRY; //umtx_pi

  // List entry in hash
  pi_hashlink:TAILQ_ENTRY; //umtx_pi

  // List for waiters
  pi_blocked:TAILQ_HEAD; //umtx_q

  // Identify a userland lock object
  pi_key:umtx_key;
 end;

 p_umtxq_queue=^umtxq_queue;

 // A userland synchronous object user.
 pp_umtx_q=^p_umtx_q;
 p_umtx_q=^umtx_q;
 umtx_q=record
  // Linked list for the hash.
  uq_link:TAILQ_ENTRY; //umtx_q

  // Umtx key.
  uq_key:umtx_key;

  // Umtx flags.
  uq_flags:Integer;

  // Inherited priority from PP mutex
  uq_inherited_pri:Integer;

  // The thread waits on.
  uq_thread:p_kthread;

  {
   Blocked on PI mutex. read can use chain lock
   or umtx_lock, write must have both chain lock and
   umtx_lock being hold.
  }

  uq_pi_blocked:p_umtx_pi;

  // On blocked list
  uq_lockq:TAILQ_ENTRY; //umtx_q

  // Thread contending with us
  uq_pi_contested:TAILQ_HEAD; //umtx_pi

  // Spare queue ready to be reused
  uq_spare_queue:p_umtxq_queue;

  // The queue we on
  uq_cur_queue:p_umtxq_queue;
 end;

 umtxq_head=TAILQ_HEAD; //umtx_q

 // Per-key wait-queue
 umtxq_queue=record
  head  :umtxq_head;
  key   :umtx_key;
  link  :LIST_ENTRY; //umtxq_queue
  length:Integer;
 end;

 umtxq_list=LIST_HEAD; //umtxq_queue

 // Userland lock object's wait-queue chain
 p_umtxq_chain=^umtxq_chain;
 umtxq_chain=record
  // Lock for this chain.
  uc_lock:mtx;

  // List of sleep queues.
  uc_queue:array[0..1] of umtxq_list;

  uc_spare_queue:LIST_HEAD; //umtxq_queue

  // Busy flag
  uc_busy:Integer;

  // Chain lock waiters
  uc_waiters:Integer;

  // All PI in the list
  uc_pi_list:TAILQ_HEAD; //umtx_pi
 end;

var
 umtxq_chains:array[0..1,0..UMTX_CHAINS-1] of umtxq_chain;
 umtx_pi_allocated:Integer=0;

 size_of_umtx_q:Integer=sizeof(umtx_q); public;

implementation

uses
 errno,
 systm,
 md_time,
 kern_proc,
 kern_param,
 sched_ule,
 vm,
 vm_map,
 sys_vm_object;

procedure UMTXQ_LOCKED_ASSERT(uc:p_umtxq_chain); inline;
begin
 mtx_assert(uc^.uc_lock);
end;

procedure UMTXQ_BUSY_ASSERT(uc:p_umtxq_chain); inline;
begin
 Assert(uc^.uc_busy<>0,'umtx chain is not busy');
end;

function UPRI(td:p_kthread):Integer; inline;
begin
 Result:=td^.td_user_pri;
 if ((Result and Integer($ffffff80)) = 768) then
 begin
  Result:=895;
 end;
end;

function GET_SHARE(flags:Integer):Integer; inline;
begin
 if ((flags and USYNC_PROCESS_SHARED)=0) then
 begin
  Result:=THREAD_SHARE;
 end else
 begin
  Result:=PROCESS_SHARE;
 end;
end;

function GET_PRIV_SHARE(priv:Integer):Integer; inline;
begin
 if (priv<>0) then
 begin
  Result:=THREAD_SHARE;
 end else
 begin
  Result:=AUTO_SHARE;
 end;
end;

//

procedure umtxq_insert_queue(uq:p_umtx_q;q:Integer); forward;
procedure umtxq_remove_queue(uq:p_umtx_q;q:Integer); forward;
function  umtxq_signal_queue(key:p_umtx_key;n_wake,q:Integer):Integer; forward;
procedure umtx_thread_cleanup(td:p_kthread); forward;
procedure umtx_pi_clearowner(pi:p_umtx_pi); forward;

//

function umtxq_signal(key:p_umtx_key;n_wake:Integer):Integer; inline;
begin
 Result:=umtxq_signal_queue(key,n_wake,UMTX_SHARED_QUEUE);
end;

procedure umtxq_insert(uq:p_umtx_q); inline;
begin
 umtxq_insert_queue(uq,UMTX_SHARED_QUEUE);
end;

procedure umtxq_remove(uq:p_umtx_q); inline;
begin
 umtxq_remove_queue(uq,UMTX_SHARED_QUEUE);
end;

var
 umtx_lock:mtx;

procedure umtxq_sysinit;
var
 i,j:Integer;
begin
 For i:=0 to 1 do
 begin
  For j:=0 to UMTX_CHAINS-1 do
  begin
   mtx_init  ( umtxq_chains[i][j].uc_lock, 'umtxql');
   LIST_INIT (@umtxq_chains[i][j].uc_queue[0]);
   LIST_INIT (@umtxq_chains[i][j].uc_queue[1]);
   LIST_INIT (@umtxq_chains[i][j].uc_spare_queue);
   TAILQ_INIT(@umtxq_chains[i][j].uc_pi_list);
   umtxq_chains[i][j].uc_busy   :=0;
   umtxq_chains[i][j].uc_waiters:=0;
  end;
 end;

 mtx_init(umtx_lock, 'umtx lock');
 //EVENTHANDLER_REGISTER(process_exec, umtx_exec_hook, NULL, EVENTHANDLER_PRI_ANY);
end;

procedure umtxq_alloc(uq:p_umtx_q);
begin
 Assert(uq<>nil);
 //uq:=AllocMem(sizeof(umtx_q));

 //do not allocate uq_spare_queue satically, can migrate to another thread
 uq^.uq_spare_queue:=AllocMem(sizeof(umtxq_queue));
 //
 TAILQ_INIT(@uq^.uq_spare_queue^.head);
 TAILQ_INIT(@uq^.uq_pi_contested);
 uq^.uq_inherited_pri:=PRI_MAX;
end;

procedure umtxq_free(uq:p_umtx_q);
begin
 Assert(uq^.uq_spare_queue<>nil);
 FreeMem(uq^.uq_spare_queue);
 //FreeMem(uq);
end;

procedure umtx_thread_init(td:p_kthread); public;
var
 uq:p_umtx_q;
begin
 uq:=td^.td_umtxq;
 umtxq_alloc(uq);
 uq^.uq_thread:=td;
end;

procedure umtx_thread_exit(td:p_kthread); public;
begin
 umtx_thread_cleanup(td);
end;

procedure umtx_thread_fini(td:p_kthread); public;
begin
 umtxq_free(td^.td_umtxq);
end;

procedure umtx_thread_cleanup(td:p_kthread);
var
 uq:p_umtx_q;
 pi:p_umtx_pi;
begin
 uq:=td^.td_umtxq;

 if (uq=nil) then Exit;

 mtx_lock(umtx_lock);

 uq^.uq_inherited_pri:=PRI_MAX;

 pi:=TAILQ_FIRST(@uq^.uq_pi_contested);
 while (pi<>nil) do
 begin
  umtx_pi_clearowner(pi);
  //
  TAILQ_REMOVE(@uq^.uq_pi_contested, pi, @pi^.pi_link);
  //
  pi:=TAILQ_FIRST(@uq^.uq_pi_contested);
 end;

 mtx_unlock(umtx_lock);

 thread_lock(td);
 sched_lend_user_prio(td, PRI_MAX);
 thread_unlock(td);
end;

procedure umtxq_hash(key:p_umtx_key);
var
 n:QWORD;
begin
 n:=QWORD(key^.info.both.a) + key^.info.both.b;
 key^.hash:=((n * GOLDEN_RATIO_PRIME) shr UMTX_SHIFTS) mod UMTX_CHAINS;
end;

function umtxq_getchain(key:p_umtx_key):p_umtxq_chain;
begin
 if (key^._type <= TYPE_SEM) then
 begin
  Exit(@umtxq_chains[1][key^.hash]);
 end;
 Exit(@umtxq_chains[0][key^.hash]);
end;

procedure umtxq_lock(key:p_umtx_key); inline;
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(key);
 mtx_lock(uc^.uc_lock);
end;

procedure umtxq_unlock(key:p_umtx_key); inline;
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(key);
 mtx_unlock(uc^.uc_lock);
end;

procedure umtxq_busy(key:p_umtx_key);
var
 uc:p_umtxq_chain;
 count:Integer;
begin
 uc:=umtxq_getchain(key);
 mtx_assert(uc^.uc_lock);
 if (uc^.uc_busy<>0) then
 begin

  //if (smp_cpus > 1) then
  begin
   count:=BUSY_SPINS;
   if (count > 0) then
   begin
    umtxq_unlock(key);
    while (uc^.uc_busy<>0) and (count > 0) do
    begin
     Dec(count);
     spin_pause;
    end;
    umtxq_lock(key);
   end;
  end;

  while (uc^.uc_busy<>0) do
  begin
   Inc(uc^.uc_waiters);
   msleep(uc, @uc^.uc_lock, 0, 'umtxqb', 0);
   Dec(uc^.uc_waiters);
  end;
 end;
 uc^.uc_busy:=1;
end;

procedure umtxq_unbusy(key:p_umtx_key);
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(key);
 mtx_assert(uc^.uc_lock);
 Assert(uc^.uc_busy<>0, 'not busy');
 uc^.uc_busy:=0;
 if (uc^.uc_waiters<>0) then
 begin
  wakeup_one(uc);
 end;
end;

function umtxq_queue_lookup(key:p_umtx_key;q:Integer):p_umtxq_queue;
var
 uh:p_umtxq_queue;
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);

 uh:=LIST_FIRST(@uc^.uc_queue[q]);
 while (uh<>nil) do
 begin
  if ((umtx_key_match(@uh^.key, key))<>0) then
  begin
   Exit(uh);
  end;
  uh:=LIST_NEXT(uh,@uh^.link);
 end;

 Exit(nil);
end;

procedure umtxq_insert_queue(uq:p_umtx_q;q:Integer);
var
 uh:p_umtxq_queue;
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(@uq^.uq_key);
 UMTXQ_LOCKED_ASSERT(uc);
 Assert((uq^.uq_flags and UQF_UMTXQ)=0, 'umtx_q is already on queue');
 uh:=umtxq_queue_lookup(@uq^.uq_key, q);
 if (uh<>nil) then
 begin
  LIST_INSERT_HEAD(@uc^.uc_spare_queue, uq^.uq_spare_queue, @uq^.uq_spare_queue^.link);
 end else
 begin
  uh:=uq^.uq_spare_queue;
  uh^.key:=uq^.uq_key;
  LIST_INSERT_HEAD(@uc^.uc_queue[q], uh, @uh^.link);
 end;
 uq^.uq_spare_queue:=nil;

 TAILQ_INSERT_TAIL(@uh^.head, uq, @uq^.uq_link);
 Inc(uh^.length);

 uq^.uq_flags:=uq^.uq_flags or UQF_UMTXQ;
 uq^.uq_cur_queue:=uh;
end;

procedure umtxq_remove_queue(uq:p_umtx_q;q:Integer);
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
begin
 uc:=umtxq_getchain(@uq^.uq_key);
 UMTXQ_LOCKED_ASSERT(uc);
 if ((uq^.uq_flags and UQF_UMTXQ)<>0) then
 begin
  uh:=uq^.uq_cur_queue;
  TAILQ_REMOVE(@uh^.head, uq, @uq^.uq_link);
  Dec(uh^.length);

  uq^.uq_flags:=uq^.uq_flags and (not UQF_UMTXQ);
  if (TAILQ_EMPTY(@uh^.head)) then
  begin
   Assert(uh^.length=0,'inconsistent umtxq_queue length');
   LIST_REMOVE(uh,@uh^.link);
  end else
  begin
   uh:=LIST_FIRST(@uc^.uc_spare_queue);
   Assert(uh<>nil,'uc_spare_queue is empty');
   LIST_REMOVE(uh,@uh^.link);
  end;
  uq^.uq_spare_queue:=uh;
  uq^.uq_cur_queue  :=nil;
 end;
end;

function umtxq_count(key:p_umtx_key):Integer;
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
begin
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);
 uh:=umtxq_queue_lookup(key, UMTX_SHARED_QUEUE);
 if (uh<>nil) then
 begin
  Exit(uh^.length);
 end;
 Exit(0);
end;

function umtxq_count_pi(key:p_umtx_key;first:pp_umtx_q):Integer;
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
begin
 first^:=nil;
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);
 uh:=umtxq_queue_lookup(key, UMTX_SHARED_QUEUE);
 if (uh<>nil) then
 begin
  first^:=TAILQ_FIRST(@uh^.head);
  Exit(uh^.length);
 end;
 Exit(0);
end;

function umtxq_check_susp(td:p_kthread):Integer;
begin
 Exit(0);
end;

function umtxq_signal_queue(key:p_umtx_key;n_wake,q:Integer):Integer;
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
 uq:p_umtx_q;
 ret:Integer;
begin
 ret:=0;
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);
 uh:=umtxq_queue_lookup(key, q);
 if (uh<>nil) then
 begin
  uq:=TAILQ_FIRST(@uh^.head);
  while (uq<>nil) do
  begin
   umtxq_remove_queue(uq, q);
   wakeup(uq);
   Inc(ret);
   if (ret >= n_wake) then
   begin
    Exit(ret);
   end;
   //
   uq:=TAILQ_FIRST(@uh^.head);
  end;
 end;
 Exit(ret);
end;

function umtxq_signal_by_tid(key:p_umtx_key;tid,q:Integer):Boolean;
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
 uq:p_umtx_q;
begin
 Result:=False;
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);
 uh:=umtxq_queue_lookup(key, q);
 if (uh<>nil) then
 begin
  uq:=TAILQ_FIRST(@uh^.head);
  while (uq<>nil) do
  begin
   if (uq^.uq_thread^.td_tid=tid) then
   begin
    umtxq_remove_queue(uq, q);
    wakeup(uq);
    Result:=True;
    Break;
   end;
   //
   uq:=TAILQ_FIRST(@uh^.head);
  end;
 end;
end;

function umtxq_first_tid(key:p_umtx_key;q:Integer):Integer;
var
 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
 uq:p_umtx_q;
begin
 Result:=-1;
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);
 uh:=umtxq_queue_lookup(key, q);
 if (uh<>nil) then
 begin
  uq:=TAILQ_FIRST(@uh^.head);
  if (uq<>nil) then
  begin
   Result:=uq^.uq_thread^.td_tid;
  end;
 end;
end;

procedure umtxq_signal_thread(uq:p_umtx_q);
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(@uq^.uq_key);
 UMTXQ_LOCKED_ASSERT(uc);
 umtxq_remove(uq);
 wakeup(uq);
end;

function umtxq_sleep(uq:p_umtx_q;wmesg:pchar;timo:Int64):Integer;
var
 uc:p_umtxq_chain;
 error:Integer;
begin
 uc:=umtxq_getchain(@uq^.uq_key);
 UMTXQ_LOCKED_ASSERT(uc);
 if ((uq^.uq_flags and UQF_UMTXQ)=0) then
 begin
  Exit(0);
 end;
 error:=msleep(uq, @uc^.uc_lock, PCATCH, wmesg, timo);
 if (error=EWOULDBLOCK) then
 begin
  error:=ETIMEDOUT;
 end;
 Exit(error);
end;

function umtx_key_get(addr:Pointer;_type,share:Integer;key:p_umtx_key):Integer;
var
 td:p_kthread;
 map:vm_map_t;
 entry:vm_map_entry_t;
 pindex:vm_pindex_t;
 prot:vm_prot_t;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 if (ptrint(addr)<$1000) then Exit(EFAULT);

 key^._type:=_type;

 if (share=THREAD_SHARE) then
 begin
  key^.shared:=0;
  key^.info.private.vs  :=p_proc.p_vmspace;
  key^.info.private.addr:=QWORD(addr);
 end else
 begin
  Assert((share=PROCESS_SHARE) or (share=AUTO_SHARE));
  map:=@p_vmspace(p_proc.p_vmspace)^.vm_map;
  if (vm_map_lookup(@map, vm_offset_t(addr), VM_PROT_WRITE,
       @entry, @key^.info.shared.vm_obj, @pindex, @prot
       )<>KERN_SUCCESS) then
  begin
   Exit(EFAULT);
  end;

  if (share=PROCESS_SHARE) or
     ((share=AUTO_SHARE) and
      (VM_INHERIT_SHARE=entry^.inheritance)) then
  begin
   key^.shared:=1;
   key^.info.shared.offset:=entry^.offset + entry^.start - vm_offset_t(addr);
   vm_object_reference(key^.info.shared.vm_obj);
  end else
  begin
   key^.shared:=0;
   key^.info.private.vs  :=p_proc.p_vmspace;
   key^.info.private.addr:=QWORD(addr);
  end;
  vm_map_lookup_done(map, entry);
 end;

 umtxq_hash(key);
 Exit(0);
end;

procedure umtx_key_release(key:p_umtx_key); inline;
begin
 if (key^.shared<>0) then
 begin
  vm_object_deallocate(key^.info.shared.vm_obj);
 end;
end;

//

function _do_lock_umtx(td:p_kthread;umtx:p_umtx;id:QWORD;timo:Int64):Integer;
var
 uq:p_umtx_q;
 owner,old:QWORD;
begin
 Result:=0;

 //Writeln('_do_lock_umtx(',HexStr(umtx),',',id,',',timo,')');

 uq:=td^.td_umtxq;

 repeat
  owner:=fuword64(umtx^.u_owner);

  if (owner=QWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  owner:=casuword64(umtx^.u_owner,UMTX_UNOWNED,id);

  if (owner=UMTX_UNOWNED) then
  begin
   Exit(0);
  end;

  if (owner=QWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (owner=QWORD($40000000)) then
  begin
   Exit(EINVAL);
  end;

  if (owner=UMTX_CONTESTED) then
  begin
   owner:=casuword64(umtx^.u_owner,UMTX_CONTESTED,id or UMTX_CONTESTED);

   if (owner=UMTX_CONTESTED) then
   begin
    Exit(0);
   end;

   if (owner=QWORD(-1)) then
   begin
    Exit(EFAULT);
   end;

   Continue;
  end;

  if (Result<>0) then Exit;

  Result:=umtx_key_get(umtx, TYPE_SIMPLE_LOCK, AUTO_SHARE, @uq^.uq_key);
  if (Result<>0) then Exit;

  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_insert(uq);
  umtxq_unbusy(@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  old:=casuword64(umtx^.u_owner,owner,owner or UMTX_CONTESTED);

  if (old=QWORD(-1)) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_remove(uq);
   umtxq_unlock(@uq^.uq_key);

   umtx_key_release(@uq^.uq_key);
   Exit(EFAULT);
  end;

  umtxq_lock(@uq^.uq_key);

  if (old=owner) then
  begin
   Result:=umtxq_sleep(uq,'umtx',timo);
  end;

  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);

  umtx_key_release(@uq^.uq_key);
 until false;
end;

function do_lock_umtx(td:p_kthread;umtx:p_umtx;id:QWORD;timeout:p_timespec):Integer;
var
 tv,ts,ts2:Int64;
begin
 Result:=0;

 if (timeout=nil) then
 begin
  Result:=_do_lock_umtx(td,umtx,id,0);
  if (Result=EINTR) then
  begin
   Result:=ERESTART;
  end;
 end else
 begin
  tv:=TIMESPEC_TO_UNIT(timeout);
  ts:=get_unit_uptime;
  ts:=ts+tv;

  repeat
   Result:=_do_lock_umtx(td,umtx,id,tvtohz(tv));
   if (Result<>ETIMEDOUT) then Break;

   ts2:=get_unit_uptime;
   if (ts2>=ts) then
   begin
    Result:=ETIMEDOUT;
    Break;
   end;

   tv:=ts-ts2;
  until false;

  if (Result=ERESTART) then
  begin
   Result:=EINTR;
  end;
 end;
end;

function do_unlock_umtx(td:p_kthread;umtx:p_umtx;id:QWORD):Integer;
var
 key:umtx_key;
 owner,old,t:QWORD;
 count:Integer;
begin
 Result:=0;

 //Writeln('do_unlock_umtx(',HexStr(umtx),',',id,')');

 owner:=fuword64(umtx^.u_owner);

 if (owner=QWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (owner=QWORD($40000000)) then
 begin
  Exit(EINVAL);
 end;

 if ((owner and (not UMTX_CONTESTED))<>id) then
 begin
  Exit(EPERM);
 end;

 if ((owner and UMTX_CONTESTED)=0) then
 begin
  old:=casuword64(umtx^.u_owner,owner,UMTX_UNOWNED);

  if (old=QWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (old=owner) then
  begin
   Exit(0);
  end;

  owner:=old;
 end;

 Result:=umtx_key_get(umtx, TYPE_SIMPLE_LOCK, AUTO_SHARE, @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);
 count:=umtxq_count(@key);
 umtxq_unlock(@key);

 if (count<=1) then
 begin
  t:=UMTX_UNOWNED;
 end else
 begin
  t:=UMTX_CONTESTED;
 end;

 old:=casuword64(umtx^.u_owner,owner,t);

 umtxq_lock  (@key);
 umtxq_signal(@key,1);
 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);

 if (old=QWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (old<>owner) then
 begin
  Exit(EINVAL);
 end;
end;

function do_wait(td      :p_kthread;
                 addr    :Pointer;
                 id      :QWORD;
                 timeout :p_timespec;
                 compat32:Integer;
                 priv    :Integer
                ):Integer;
var
 uq:p_umtx_q;
 tv,ts,ts2:Int64;
 tmp:QWORD;
begin
 Result:=0;

 //Writeln('do_wait(',HexStr(addr),',',id,')');

 uq:=td^.td_umtxq;

 Result:=umtx_key_get(addr, TYPE_SIMPLE_WAIT, GET_PRIV_SHARE(priv), @uq^.uq_key);
 if (Result<>0) then Exit;

 umtxq_lock  (@uq^.uq_key);
 umtxq_insert(uq);
 umtxq_unlock(@uq^.uq_key);

 if (compat32=0) then
 begin
  tmp:=fuword64(PQWORD(addr)^);
 end else
 begin
  tmp:=fuword32(PDWORD(addr)^);
 end;

 if (tmp<>id) then
 begin
  umtxq_lock  (@uq^.uq_key);
  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);
 end else
 if (timeout=nil) then
 begin
  umtxq_lock(@uq^.uq_key);
  Result:=umtxq_sleep(uq, 'uwait', 0);
  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);
 end else
 begin
  tv:=TIMESPEC_TO_UNIT(timeout);
  ts:=get_unit_uptime;
  ts:=ts+tv;

  repeat
   umtxq_lock(@uq^.uq_key);

   Result:=umtxq_sleep(uq,'uwait',tvtohz(tv));

   if ((uq^.uq_flags and UQF_UMTXQ)=0) then
   begin
    Result:=0;
    Break;
   end;

   if (Result<>ETIMEDOUT) then Break;

   umtxq_unlock(@uq^.uq_key);

   ts2:=get_unit_uptime;
   if (ts2>=ts) then
   begin
    Result:=ETIMEDOUT;
    umtxq_lock(@uq^.uq_key);
    Break;
   end;

   tv:=ts-ts2;
  until false;

  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);
 end;

 umtx_key_release(@uq^.uq_key);

 if (Result=ERESTART) then
 begin
  Result:=EINTR;
 end;
end;

function kern_umtx_wake(td:p_kthread;umtx:p_umtx;n_wake,priv:Integer):Integer; public;
var
 key:umtx_key;
begin
 Result:=0;

 //Writeln('kern_umtx_wake(',HexStr(umtx),',',n_wake,')');

 Result:=umtx_key_get(umtx, TYPE_SIMPLE_WAIT, GET_PRIV_SHARE(priv), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);

 Result:=umtxq_signal(@key, n_wake);

 umtxq_unlock(@key);

 umtx_key_release(@key);
 Exit(0);
end;

//

function _do_lock_normal(td:p_kthread;m:p_umutex;flags:Integer;timo:Int64;mode:Integer):Integer;
var
 uq:p_umtx_q;
 id,owner,old:DWORD;
begin
 Result:=0;

 //Writeln('_do_lock_normal(',HexStr(m),',',flags,',',timo,',',mode,')');

 id:=td^.td_tid;
 uq:=td^.td_umtxq;

 repeat
  owner:=fuword32(m^.m_owner);

  if (owner=DWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (owner and (not UMUTEX_CONTESTED) = id) then
  begin
   Exit(0);
  end;

  if (owner=DWORD($40000000)) then
  begin
   Exit(EINVAL);
  end;

  if (mode=_UMUTEX_WAIT) then
  begin
   if (owner=UMUTEX_UNOWNED) or (owner=UMUTEX_CONTESTED) then
   begin
    Exit(0);
   end;
  end else
  begin
   owner:=casuword32(m^.m_owner,UMUTEX_UNOWNED,id);

   if (owner=UMUTEX_UNOWNED) then
   begin
    Exit(0);
   end;

   if (owner=DWORD(-1)) then
   begin
    Exit(EFAULT);
   end;

   if (owner=UMUTEX_CONTESTED) then
   begin
    owner:=casuword32(m^.m_owner,UMUTEX_CONTESTED,id or UMUTEX_CONTESTED);

    if (owner=UMUTEX_CONTESTED) then
    begin
     Exit(0);
    end;

    if (owner=DWORD(-1)) then
    begin
     Exit(EFAULT);
    end;

    Continue;
   end;
  end;

  if ((flags and UMUTEX_ERROR_CHECK)<>0) and
     ((owner and (not UMUTEX_CONTESTED)) = id) then
  begin
   Exit(EDEADLK);
  end;

  if (mode=_UMUTEX_TRY) then
  begin
   Exit(EBUSY);
  end;

  if (Result<>0) then Exit;

  Result:=umtx_key_get(m, TYPE_NORMAL_UMUTEX, GET_SHARE(flags), @uq^.uq_key);
  if (Result<>0) then Exit;

  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_insert(uq);
  umtxq_unlock(@uq^.uq_key);

  old:=casuword32(m^.m_owner,owner,owner or UMUTEX_CONTESTED);

  if (old=DWORD(-1)) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_remove(uq);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);

   umtx_key_release(@uq^.uq_key);

   Exit(EFAULT);
  end;

  umtxq_lock  (@uq^.uq_key);
  umtxq_unbusy(@uq^.uq_key);

  if (old=owner) then
  begin
   Result:=umtxq_sleep(uq,'umtxn',timo);
  end;

  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);

  umtx_key_release(@uq^.uq_key);

 until false;

 Exit(0);
end;

function do_unlock_normal(td:p_kthread;m:p_umutex;flags:Integer):Integer;
var
 key:umtx_key;
 id,owner,old,t:DWORD;
 count:Integer;

 tid:Integer;
begin
 Result:=0;

 //Writeln('do_unlock_normal(',HexStr(m),',',flags,')');

 id:=td^.td_tid;

 owner:=fuword32(m^.m_owner);

 if (owner=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (owner=DWORD($40000000)) then
 begin
  Exit(EINVAL);
 end;

 if ((owner and (not UMUTEX_CONTESTED))<>id) then
 begin
  Exit(EPERM);
 end;

 if ((owner and UMUTEX_CONTESTED)=0) then
 begin
  old:=casuword32(m^.m_owner,owner,UMUTEX_UNOWNED);

  if (old=DWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (old=owner) then
  begin
   Exit(0);
  end;

  owner:=old;
 end;

 Result:=umtx_key_get(m, TYPE_NORMAL_UMUTEX, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);
 count:=umtxq_count(@key);
 umtxq_unlock(@key);

 if (count<=1) then
 begin
  t:=UMUTEX_UNOWNED;
 end else
 begin
  t:=UMUTEX_CONTESTED;
 end;

 if ((flags and $80) <> 0) and (count <> 0) then
 begin
  umtxq_lock(@key);

  tid:=umtxq_first_tid(@key,UMTX_SHARED_QUEUE);

  umtxq_unlock(@key);

  if (tid=-1) then
  begin
   old:=0;
  end else
  begin
   old:=tid;
  end;

  old:=casuword32(m^.m_owner,owner,old or t);

  umtxq_lock(@key);

  if (tid <> -1) and (old <> DWORD(-1)) then
  begin
   umtxq_signal_by_tid(@key,tid,UMTX_SHARED_QUEUE);
  end;

 end else
 begin
  old:=casuword32(m^.m_owner,owner,t);

  umtxq_lock  (@key);
  umtxq_signal(@key,1);
 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);

 if (old=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (old<>owner) then
 begin
  Exit(EINVAL);
 end;

 Exit(0);
end;

function do_wake_umutex(td:p_kthread;m:p_umutex):Integer;
var
 key:umtx_key;
 owner:DWORD;
 count:Integer;
 flags:DWORD;
begin
 Result:=0;

 //Writeln('do_wake_umutex(',HexStr(m),')');

 owner:=fuword32(m^.m_owner);

 if (owner=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if ((owner and (not UMUTEX_CONTESTED))<>0) then
 begin
  Exit(0);
 end;

 flags:=fuword32(m^.m_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(m, TYPE_NORMAL_UMUTEX, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);
 count:=umtxq_count(@key);
 umtxq_unlock(@key);

 if (count <= 1) then
 begin
  owner:=casuword32(m^.m_owner,UMUTEX_CONTESTED,UMUTEX_UNOWNED);
 end;

 umtxq_lock(@key);

 if (count<>0) and ((owner and (not UMUTEX_CONTESTED))=0) then
 begin
  umtxq_signal(@key, 1);
 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);

 Exit(0);
end;

function do_cv_signalto(td:p_kthread;cv:p_ucond;tid:Integer):Integer;
var
 key:umtx_key;
 count,flags:Integer;
begin
 Result:=0;

 //Writeln('do_cv_signalto(',HexStr(cv),',',tid,')');

 flags:=fuword32(cv^.c_flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(cv, TYPE_CV, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);

 count:=umtxq_count(@key);

 if (count <> 0) then
 begin
  if umtxq_signal_by_tid(@key,tid,UMTX_SHARED_QUEUE) then
  if (count = 1) then
  begin
   umtxq_unlock(@key);
   Result:=suword32(cv^.c_has_waiters,0);
   umtxq_lock(@key);
   if (Result<>0) then Result:=EFAULT;
  end;
 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);
end;

function do_wake3_umutex(td:p_kthread;m:p_umutex;flags:DWORD):Integer;
label
 _owning,
 _signaling,
 _umtxq_count_end;
var
 key:umtx_key;
 ktype:Integer;
 owner,old:DWORD;
 count:ptrint;

 uc:p_umtxq_chain;
 uh:p_umtxq_queue;
 uq:p_umtx_q;

 tid:Integer;

 is_signal:Boolean;
 is_cur_td:Boolean;
begin
 Result:=0;

 //Writeln('do_wake3_umutex(',HexStr(m),',',flags,')');

 Case (flags and (UMUTEX_PRIO_INHERIT or UMUTEX_PRIO_PROTECT)) of
                    0:ktype:=TYPE_NORMAL_UMUTEX;
  UMUTEX_PRIO_INHERIT:ktype:=TYPE_PI_UMUTEX;
  UMUTEX_PRIO_PROTECT:ktype:=TYPE_PP_UMUTEX;
  else
   Exit(EINVAL);
 end;

 Result:=umtx_key_get(m, ktype, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 owner:=0;
 tid  :=0;

 umtxq_lock(@key);
 umtxq_busy(@key);

 //[umtxq_count] analog
  uc:=umtxq_getchain(@key);
  UMTXQ_LOCKED_ASSERT(uc);
  uh:=umtxq_queue_lookup(@key, UMTX_SHARED_QUEUE);
  if (uh<>nil) then
  begin
   count    :=uh^.length;
   tid      :=-1;
   is_signal:=(count<>0);

   if ((flags and $80) = 0) or (count = 0) then
   begin
    goto _umtxq_count_end;
   end;

   uh:=uc^.uc_queue[0].lh_first;

   if (uh=nil) then
   begin
    is_signal:=true;
    flags    :=flags or $80;
    goto _umtxq_count_end;
   end;

   while (uh<>nil) do
   begin
    if ((umtx_key_match(@uh^.key, @key))<>0) then
    begin
     uq:=TAILQ_FIRST(@uh^.head);
     if (uq<>nil) then
     begin
      tid:=uq^.uq_thread^.td_tid;
     end;
     Break;
    end;
    uh:=LIST_NEXT(uh,@uh^.link);
   end;

   is_signal:=true;
   flags    :=flags or $80;
   goto _umtxq_count_end;
  end;

  tid      :=-1;
  is_signal:=False;
  count    :=0;
 //
 _umtxq_count_end:
 //[umtxq_count]

 is_cur_td:=false;

 umtxq_unlock(@key);

 if (count < 2) then
 begin
  if (count = 1) then
  begin
   if ((flags and $80) = 0) then
   begin
    owner:=fuword32(m^.m_owner);
    if (owner = DWORD(-1)) then
    begin
     Result:=EFAULT;
     owner :=DWORD(-1);
     goto _signaling;
    end;
   end else
   begin
    owner:=casuword32(m^.m_owner,0,tid);
    if (owner = DWORD(-1)) then
    begin
     is_cur_td:=false;
     Result   :=EFAULT;
     goto _signaling;
    end;
    if (owner = 0) then
    begin
     is_cur_td:=true;
     owner    :=0;
     Result   :=0;
     goto _signaling;
    end;
   end;
   is_cur_td:=false;

   old:=owner;

   repeat
    if ((old and UMUTEX_CONTESTED) <> 0) or ((old and (not UMUTEX_CONTESTED)) = 0) then
    begin
     owner    :=old;
     Result   :=0;
     is_cur_td:=false;
     goto _signaling;
    end;

    owner:=casuword32(m^.m_owner,old,old or UMUTEX_CONTESTED);

    if (owner = DWORD(-1)) then
    begin
     owner    :=old;
     Result   :=EFAULT;
     is_cur_td:=false;
     goto _signaling;
    end;

    if (owner = old) then
    begin
     owner    :=old;
     Result   :=0;
     is_cur_td:=false;
     goto _signaling;
    end;

    Result:=umtxq_check_susp(td);

    old:=owner;
   until not (Result = 0);

  end else
  begin //if (count = 1)
   is_cur_td:=false;
   owner    :=0;
   Result   :=0;
  end;
 end else //(count < 2)
 if ((flags and $80) = 0) then
 begin
  owner:=fuword32(m^.m_owner);
  if (owner <> DWORD(-1)) then
  begin
   goto _owning;
  end;
  Result:=EFAULT;
  owner :=DWORD(-1);
 end else
 begin
  owner:=casuword32(m^.m_owner,0,tid or UMUTEX_CONTESTED);

  if (owner = DWORD(-1)) then
  begin
   is_cur_td:=false;
   Result   :=EFAULT;
  end else
  begin
   if (owner = 0) then
   begin
    is_cur_td:=true;
    owner    :=0;
    Result   :=0;
    goto _signaling;
   end;

   ///////
   _owning:
   ///////

   is_cur_td:=false;

   old:=owner;
   repeat
    if ((old and UMUTEX_CONTESTED) <> 0) then
    begin
     owner    :=old;
     count    :=0;
     is_cur_td:=false;
     goto _signaling;
    end;

    owner:=casuword32(m^.m_owner,old,old or UMUTEX_CONTESTED);

    if (owner = DWORD(-1)) then
    begin
     owner    :=old;
     Result   :=EFAULT;
     is_cur_td:=false;
     goto _signaling;
    end;

    if (owner = old) then
    begin
     owner    :=old;
     Result   :=0;
     is_cur_td:=false;
     goto _signaling;
    end;

    Result:=umtxq_check_susp(td);
    old:=owner;
   until not (Result = 0);

  end;

 end;

 _signaling:

 umtxq_lock(@key);

 if (Result = EFAULT) then
 begin
  //umtxq_signal_queue all
  umtxq_signal(@key,High(Integer));
 end else
 if (is_cur_td) then
 begin
  //umtxq_signal_queue by tid
  umtxq_signal_by_tid(@key,tid,UMTX_SHARED_QUEUE);
 end else
 if is_signal and ((owner and (not UMUTEX_CONTESTED)) = 0) then
 begin
  //umtxq_signal_queue one
  umtxq_signal(@key,1);
 end;

 //unlock and release

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);
end;

//

function umtx_pi_alloc():p_umtx_pi; inline;
var
 pi:p_umtx_pi;
begin
 pi:=AllocMem(sizeof(umtx_pi));
 TAILQ_INIT(@pi^.pi_blocked);
 fetch_add(umtx_pi_allocated, 1);
 Exit(pi);
end;

procedure umtx_pi_free(pi:p_umtx_pi); inline;
begin
 FreeMem(pi);
 fetch_add(umtx_pi_allocated, -1);
end;

function umtx_pi_adjust_thread(pi:p_umtx_pi;td:p_kthread):Integer;
var
 uq,uq1,uq2:p_umtx_q;
 td1:p_kthread;
begin
 mtx_assert(umtx_lock);
 if (pi=nil) then Exit(0);

 uq:=td^.td_umtxq;

 uq1:=TAILQ_PREV(uq, @uq^.uq_lockq);
 uq2:=TAILQ_NEXT(uq, @uq^.uq_lockq);

 if ((uq1<>nil) and (UPRI(td) < UPRI(uq1^.uq_thread))) or
    ((uq2<>nil) and (UPRI(td) > UPRI(uq2^.uq_thread))) then
 begin
  TAILQ_REMOVE(@pi^.pi_blocked, uq, @uq^.uq_lockq);

  uq1:=TAILQ_FIRST(@pi^.pi_blocked);
  while (uq1<>nil) do
  begin
   td1:=uq1^.uq_thread;
   //Assert(td1^.td_proc^.p_magic=P_MAGIC);
   if (UPRI(td1) > UPRI(td)) then Break;
   //
   uq1:=TAILQ_NEXT(uq1,@uq1^.uq_lockq);
  end;

  if (uq1=nil) then
  begin
   TAILQ_INSERT_TAIL(@pi^.pi_blocked, uq, @uq^.uq_lockq);
  end else
  begin
   TAILQ_INSERT_BEFORE(uq1, uq, @uq^.uq_lockq);
  end;
 end;
 Exit(1);
end;

procedure umtx_propagate_priority(td:p_kthread);
var
 uq:p_umtx_q;
 pi:p_umtx_pi;
 pri:Integer;
begin
 mtx_assert(umtx_lock);

 pri:=UPRI(td);
 uq:=td^.td_umtxq;
 pi:=uq^.uq_pi_blocked;
 if (pi=nil) then Exit;

 while (true) do
 begin
  td:=pi^.pi_owner;
  if (td=nil) or (td=curkthread) then Exit;

  //Assert(td^.td_proc<>nil);
  //Assert(td^.td_proc^.p_magic=P_MAGIC);

  thread_lock(td);
  if (td^.td_lend_user_pri > pri) then
  begin
   sched_lend_user_prio(td, pri);
  end else
  begin
   thread_unlock(td);
   Break;
  end;
  thread_unlock(td);

  uq:=td^.td_umtxq;
  pi:=uq^.uq_pi_blocked;
  if (pi=nil) then Break;

  umtx_pi_adjust_thread(pi, td);
 end;
end;

procedure umtx_repropagate_priority(pi:p_umtx_pi);
var
 uq,uq_owner:p_umtx_q;
 pi2:p_umtx_pi;
 pri:Integer;
begin
 mtx_assert(umtx_lock);

 while (pi<>nil) and (pi^.pi_owner<>nil) do
 begin
  pri:=PRI_MAX;
  uq_owner:=pi^.pi_owner^.td_umtxq;

  pi2:=TAILQ_FIRST(@uq_owner^.uq_pi_contested);
  while (pi2<>nil) do
  begin
   uq:=TAILQ_FIRST(@pi2^.pi_blocked);
   if (uq<>nil) then
   begin
    if (pri > UPRI(uq^.uq_thread)) then
    begin
     pri:=UPRI(uq^.uq_thread);
    end;
   end;
   //
   pi2:=TAILQ_NEXT(pi2,@pi2^.pi_link);
  end;

  if (pri > uq_owner^.uq_inherited_pri) then
  begin
   pri:=uq_owner^.uq_inherited_pri;
  end;

  thread_lock(pi^.pi_owner);
  sched_lend_user_prio(pi^.pi_owner, pri);
  thread_unlock(pi^.pi_owner);

  pi:=uq_owner^.uq_pi_blocked;
  if (pi<>nil) then
  begin
   umtx_pi_adjust_thread(pi, uq_owner^.uq_thread);
  end;
 end;
end;

procedure umtx_pi_setowner(pi:p_umtx_pi;owner:p_kthread);
var
 uq_owner:p_umtx_q;
begin
 uq_owner:=owner^.td_umtxq;
 mtx_assert(umtx_lock);
 if (pi^.pi_owner<>nil) then
 begin
  Assert(false,'pi_owner<>nil');
 end;
 thread_inc_ref(owner);
 pi^.pi_owner:=owner;
 TAILQ_INSERT_TAIL(@uq_owner^.uq_pi_contested, pi, @pi^.pi_link);
end;

procedure umtx_pi_clearowner(pi:p_umtx_pi);
var
 owner:p_kthread;
begin
 if (pi^.pi_owner<>nil) then
 begin
  owner:=pi^.pi_owner;
  pi^.pi_owner:=nil;
  thread_dec_ref(owner);
 end;
end;

function umtx_pi_claim(pi:p_umtx_pi;owner:p_kthread):Integer;
var
 uq:p_umtx_q;
 pri:Integer;
begin
 mtx_lock(umtx_lock);

 if (pi^.pi_owner=owner) then
 begin
  mtx_unlock(umtx_lock);
  Exit (0);
 end;

 if (pi^.pi_owner<>nil) then
 begin
  mtx_unlock(umtx_lock);
  Exit(EPERM);
 end;

 umtx_pi_setowner(pi, owner);
 uq:=TAILQ_FIRST(@pi^.pi_blocked);

 if (uq<>nil) then
 begin
  pri:=UPRI(uq^.uq_thread);
  thread_lock(owner);
  if (pri < UPRI(owner)) then
  begin
   sched_lend_user_prio(owner, pri);
  end;
  thread_unlock(owner);
 end;
 mtx_unlock(umtx_lock);
 Exit(0);
end;

procedure umtx_pi_adjust(td:p_kthread;oldpri:Integer); public;
var
 uq:p_umtx_q;
 pi:p_umtx_pi;
begin
 uq:=td^.td_umtxq;
 mtx_lock(umtx_lock);

 pi:=uq^.uq_pi_blocked;
 if (pi<>nil) then
 begin
  umtx_pi_adjust_thread(pi, td);
  umtx_repropagate_priority(pi);
 end;
 mtx_unlock(umtx_lock);
end;

function umtxq_sleep_pi(uq:p_umtx_q;
                        pi:p_umtx_pi;
                        owner:DWORD;
                        wmesg:pchar;
                        timo:Int64;
                        flags:DWORD):Integer;
var
 uc:p_umtxq_chain;
 td,td1:p_kthread;
 uq1:p_umtx_q;
 pri:Integer;
begin
 td:=uq^.uq_thread;
 Assert(td=curkthread,'inconsistent uq_thread');

 uc:=umtxq_getchain(@uq^.uq_key);
 UMTXQ_LOCKED_ASSERT(uc);
 UMTXQ_BUSY_ASSERT(uc);
 umtxq_insert(uq);

 mtx_lock(umtx_lock);
 if (pi^.pi_owner=nil) then
 begin
  mtx_unlock(umtx_lock);

  td1:=tdfind(owner);

  mtx_lock(umtx_lock);
  if (td1<>nil) then
  begin
   if (pi^.pi_owner=nil) then
   begin
    umtx_pi_setowner(pi, td1);
   end;
   //
   thread_dec_ref(td1);
  end;
 end;

 uq1:=TAILQ_FIRST(@pi^.pi_blocked);
 while (uq1<>nil) do
 begin
  pri:=UPRI(uq1^.uq_thread);
  if (pri > UPRI(td)) then Break;
  //
  uq1:=TAILQ_NEXT(uq1,@uq1^.uq_lockq);
 end;

 if (uq1<>nil) then
 begin
  TAILQ_INSERT_BEFORE(uq1, uq, @uq^.uq_lockq);
 end else
 begin
  TAILQ_INSERT_TAIL(@pi^.pi_blocked, uq, @uq^.uq_lockq);
 end;

 uq^.uq_pi_blocked:=pi;

 thread_lock(td);
 td^.td_flags:=td^.td_flags or TDF_UPIBLOCKED;
 thread_unlock(td);

 umtx_propagate_priority(td);
 mtx_unlock(umtx_lock);
 umtxq_unbusy(@uq^.uq_key);

 if ((uq^.uq_flags and UQF_UMTXQ)<>0) then
 begin
  Result:=msleep(uq, @uc^.uc_lock, PCATCH, wmesg, timo);
  if (Result=EWOULDBLOCK) then
  begin
   Result:=ETIMEDOUT;
  end;
  if ((uq^.uq_flags and UQF_UMTXQ)<>0) then
  begin
   umtxq_remove(uq);
  end;
 end;
 mtx_lock(umtx_lock);

 if ((flags and $280) = $280) and
    ((td^.td_flags and TDF_UPIBLOCKED) = 0) then
 begin
  mtx_unlock(umtx_lock);
  umtxq_unlock(@uq^.uq_key);
  Exit;
 end;

 uq^.uq_pi_blocked:=nil;

 thread_lock(td);
 td^.td_flags:=td^.td_flags and (not TDF_UPIBLOCKED);
 thread_unlock(td);

 TAILQ_REMOVE(@pi^.pi_blocked, uq, @uq^.uq_lockq);
 umtx_repropagate_priority(pi);

 mtx_unlock(umtx_lock);
 umtxq_unlock(@uq^.uq_key);
end;

procedure umtx_pi_ref(pi:p_umtx_pi);
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(@pi^.pi_key);
 UMTXQ_LOCKED_ASSERT(uc);
 Inc(pi^.pi_refcount);
end;

procedure umtx_pi_unref(pi:p_umtx_pi);
var
 uq:p_umtx_q;
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(@pi^.pi_key);

 UMTXQ_LOCKED_ASSERT(uc);
 Assert(pi^.pi_refcount > 0,'invalid reference count');

 Dec(pi^.pi_refcount);
 if (pi^.pi_refcount=0) then
 begin
  mtx_lock(umtx_lock);

  if (pi^.pi_owner<>nil) then
  begin
   uq:=pi^.pi_owner^.td_umtxq;
   TAILQ_REMOVE(@uq^.uq_pi_contested, pi, @pi^.pi_link);
   umtx_pi_clearowner(pi);
  end;

  Assert(TAILQ_EMPTY(@pi^.pi_blocked),'blocked queue not empty');
  mtx_unlock(umtx_lock);

  TAILQ_REMOVE(@uc^.uc_pi_list, pi, @pi^.pi_hashlink);

  umtx_pi_free(pi);
 end;
end;

function umtx_pi_lookup(key:p_umtx_key):p_umtx_pi;
var
 uc:p_umtxq_chain;
 pi:p_umtx_pi;
begin
 uc:=umtxq_getchain(key);
 UMTXQ_LOCKED_ASSERT(uc);

 pi:=TAILQ_FIRST(@uc^.uc_pi_list);
 while (pi<>nil) do
 begin
  if (umtx_key_match(@pi^.pi_key, key)<>0) then
  begin
   Exit(pi);
  end;
  //
  pi:=TAILQ_NEXT(pi,@pi^.pi_hashlink);
 end;

 Exit(nil);
end;

procedure umtx_pi_insert(pi:p_umtx_pi);
var
 uc:p_umtxq_chain;
begin
 uc:=umtxq_getchain(@pi^.pi_key);
 UMTXQ_LOCKED_ASSERT(uc);
 TAILQ_INSERT_TAIL(@uc^.uc_pi_list, pi, @pi^.pi_hashlink);
end;

function _do_lock_pi(td:p_kthread;m:p_umutex;flags:Integer;timo:Int64;mode:Integer):Integer;
var
 uq:p_umtx_q;
 pi,new_pi:p_umtx_pi;
 id,owner,old:DWORD;
begin
 Result:=0;

 //Writeln('_do_lock_pi(',HexStr(m),',',flags,',',timo,',',mode,')');

 id:=td^.td_tid;
 uq:=td^.td_umtxq;

 Result:=umtx_key_get(m, TYPE_PI_UMUTEX, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 umtxq_lock(@uq^.uq_key);
 pi:=umtx_pi_lookup(@uq^.uq_key);

 if (pi=nil) then
 begin
  new_pi:=umtx_pi_alloc();

  if (new_pi=nil) then
  begin
   umtxq_unlock(@uq^.uq_key);
   new_pi:=umtx_pi_alloc();
   umtxq_lock(@uq^.uq_key);

   pi:=umtx_pi_lookup(@uq^.uq_key);
   if (pi<>nil) then
   begin
    umtx_pi_free(new_pi);
    new_pi:=nil;
   end;
  end;

  if (new_pi<>nil) then
  begin
   new_pi^.pi_key:=uq^.uq_key;
   umtx_pi_insert(new_pi);
   pi:=new_pi;
  end;
 end;

 umtx_pi_ref(pi);
 umtxq_unlock(@uq^.uq_key);

 while (true) do
 begin
  owner:=casuword32(m^.m_owner, UMUTEX_UNOWNED, id);

  if (owner=UMUTEX_UNOWNED) then
  begin
   Result:=0;
   Break;
  end;

  if (owner=DWORD($40000000)) then
  begin
   Result:=EINVAL;
   Break;
  end;

  if (owner=DWORD(-1)) then
  begin
   Result:=EFAULT;
   Break;
  end;

  if (owner=UMUTEX_CONTESTED) then
  begin
   owner:=casuword32(m^.m_owner, UMUTEX_CONTESTED, id or UMUTEX_CONTESTED);

   if (owner=UMUTEX_CONTESTED) then
   begin
    umtxq_lock(@uq^.uq_key);
    umtxq_busy(@uq^.uq_key);
    Result:=umtx_pi_claim(pi, td);
    umtxq_unbusy(@uq^.uq_key);
    umtxq_unlock(@uq^.uq_key);
    Break;
   end;

   if (owner=DWORD(-1)) then
   begin
    Result:=EFAULT;
    Break;
   end;

   continue;
  end;

  if (owner and (not UMUTEX_CONTESTED) = id) then
  begin
   Result:=0;
   Break;
  end;

  if ((flags and UMUTEX_ERROR_CHECK)<>0) and
     ((owner and (not UMUTEX_CONTESTED))=id) then
  begin
   Result:=EDEADLK;
   Break;
  end;

  if (mode<>0) then
  begin
   Result:=EBUSY;
   Break;
  end;

  if (Result<>0) then Break;

  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  old:=casuword32(m^.m_owner, owner, owner or UMUTEX_CONTESTED);

  if (old=DWORD(-1)) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);
   Result:=EFAULT;
   Break;
  end;

  umtxq_lock(@uq^.uq_key);

  if (old=owner) then
  begin
   Result:=umtxq_sleep_pi(uq, pi, owner and (not UMUTEX_CONTESTED),'umtxpi', timo, flags);
  end else
  begin
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);
  end;

  Result:=umtxq_check_susp(td);
  if (Result<>0) then Break;
 end; //while

 if (Result<>0) then
 begin
  casuword32(m^.m_owner, id or UMUTEX_CONTESTED, UMUTEX_CONTESTED);
 end;

 umtxq_lock   (@uq^.uq_key);
 umtx_pi_unref(pi);
 umtxq_unlock (@uq^.uq_key);

 umtx_key_release(@uq^.uq_key);
end;

function do_unlock_pi(td:p_kthread;m:p_umutex;flags:Integer):Integer;
var
 uq_thread:p_kthread;
 key:umtx_key;
 uq_first,uq_first2,uq_me:p_umtx_q;
 pi,pi2:p_umtx_pi;
 owner,old,id,t:DWORD;
 count,pri:Integer;
begin
 Result:=0;

 //Writeln('do_unlock_pi(',HexStr(m),',',flags,')');

 id:=td^.td_tid;

 owner:=fuword32(m^.m_owner);

 if (owner=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (owner=DWORD($40000000)) then
 begin
  Exit(EINVAL);
 end;

 if ((owner and (not UMUTEX_CONTESTED))<>id) then
 begin
  Exit(EPERM);
 end;

 if ((owner and UMUTEX_CONTESTED)=0) then
 begin
  old:=casuword32(m^.m_owner, owner, UMUTEX_UNOWNED);

  if (old=DWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (old=owner) then
  begin
   Exit(0);
  end;

  owner:=old;
 end;

 Result:=umtx_key_get(m, TYPE_PI_UMUTEX, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);
 count:=umtxq_count_pi(@key, @uq_first);

 if (count<=1) then
 begin
  t:=UMUTEX_UNOWNED;
 end else
 begin
  t:=UMUTEX_CONTESTED;
 end;

 if (uq_first<>nil) then
 begin
  mtx_lock(umtx_lock);

  pi:=uq_first^.uq_pi_blocked;
  Assert(pi<>nil,'pi=nil?');

  if (pi^.pi_owner<>td) then
  begin
   mtx_unlock(umtx_lock);
   umtxq_unbusy(@key);
   umtxq_unlock(@key);
   umtx_key_release(@key);

   Exit(EPERM);
  end;

  uq_me:=td^.td_umtxq;

  umtx_pi_clearowner(pi);

  TAILQ_REMOVE(@uq_me^.uq_pi_contested, pi, @pi^.pi_link);

  uq_first:=TAILQ_FIRST(@pi^.pi_blocked);
  while (uq_first<>nil) and
        ((uq_first^.uq_flags and UQF_UMTXQ)=0) do
  begin
   uq_first:=TAILQ_NEXT(uq_first, @uq_first^.uq_lockq);
  end;

  pri:=1023;

  pi2:=TAILQ_FIRST(@uq_me^.uq_pi_contested);
  while (pi2<>nil) do
  begin
   uq_first2:=TAILQ_FIRST(@pi2^.pi_blocked);
   if (uq_first2<>nil) then
   begin
    if (pri > UPRI(uq_first2^.uq_thread)) then
    begin
     pri:=UPRI(uq_first2^.uq_thread);
    end;
   end;
   //
   pi2:=TAILQ_NEXT(pi2,@pi2^.pi_link);
  end;

  thread_lock(td);
  sched_lend_user_prio(td, pri);
  thread_unlock(td);

  mtx_unlock(umtx_lock);

  if (uq_first=nil) then
  begin
   umtxq_unlock(@key);
  end else
  begin
   //umtxq_signal_thread (extended)

   if ((flags and $80) = 0) then
   begin
    umtxq_signal_thread(uq_first);
    umtxq_unlock(@key);
   end else
   begin
    uq_thread:=uq_first^.uq_thread;
    id:=uq_thread^.td_tid;

    if ((flags and $200) <> 0) then
    begin
     mtx_lock(umtx_lock);

     pi:=uq_first^.uq_pi_blocked;
     Assert(pi<>nil,'pi=nil?');

     uq_first^.uq_pi_blocked:=nil;

     thread_lock(uq_thread);
     uq_thread^.td_flags:=uq_thread^.td_flags and (not TDF_UPIBLOCKED);
     thread_unlock(uq_thread);

     TAILQ_REMOVE(@pi^.pi_blocked, uq_first, @uq_first^.uq_lockq);

     umtx_repropagate_priority(pi);

     mtx_unlock(umtx_lock);

     if (count > 1) then
     begin
      Result:=umtx_pi_claim(pi, uq_thread);

      if (Result <> 0) then
      begin
       umtxq_unbusy(@key);
       umtxq_unlock(@key);

       umtx_key_release(@key);

       Exit(EINVAL);
      end;
     end;

    end;//(flags and $200) <> 0)

    umtxq_signal_thread(uq_first);
    umtxq_unlock(@key);

    if (count < 2) then
    begin
     t:=id;
    end else
    begin
     t:=id or UMUTEX_CONTESTED;
    end;

   end; //((flags and $80) <> 0)

  end; //(uq_first<>nil)

 end;

 old:=casuword32(m^.m_owner, owner, t);

 umtxq_lock  (@key);
 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);

 if (old=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (old<>owner) then
 begin
  Exit(EINVAL);
 end;

 Exit(0);
end;

//

function _do_lock_pp(td:p_kthread;m:p_umutex;flags:Integer;timo:Int64;mode:Integer):Integer;
label
 _out;
var
 uq,uq2:p_umtx_q;
 pi:p_umtx_pi;
 ceiling,owner,id:DWORD;
 pri,old_inherited_pri,su:Integer;
begin
 Result:=0;

 //Writeln('_do_lock_pp(',HexStr(m),',',flags,',',timo,',',mode,')');

 id:=td^.td_tid;
 uq:=td^.td_umtxq;

 Result:=umtx_key_get(m, TYPE_PP_UMUTEX, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 su:=1;
 //su:=(priv_check(td, PRIV_SCHED_RTPRIO)=0);

 while (true) do
 begin
  old_inherited_pri:=uq^.uq_inherited_pri;
  umtxq_lock(@uq^.uq_key);
  umtxq_busy(@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  ceiling:=fuword32(m^.m_ceilings[0]);

  if (ceiling=DWORD(-1)) then
  begin
   Result:=EFAULT;
   goto _out;
  end;

  if ((flags and $400) = 0) then
  begin
   if (ceiling > 767) then
   begin
    Result:=EINVAL;
    goto _out;
   end;
   ceiling:=767 - ceiling;
  end else
  begin
   if ((ceiling - 256) > 639) then
   begin
    Result:=EINVAL;
    goto _out;
   end;
  end;

  mtx_lock(umtx_lock);

  if ((flags and $400) = 0) then
  begin
   ceiling:=ceiling + 256;

   if (UPRI(td) < ceiling) then
   begin
    mtx_unlock(umtx_lock);
    Result:=EINVAL;
    goto _out;
   end;

   if (su<>0) and (ceiling < uq^.uq_inherited_pri) then
   begin
    uq^.uq_inherited_pri:=ceiling;
    //
    thread_lock(td);
    //
    if (uq^.uq_inherited_pri < UPRI(td)) then
    begin
     sched_lend_user_prio(td, uq^.uq_inherited_pri);
    end;
    //
    thread_unlock(td);
   end;

  end else
  begin

   if (UPRI(td) < ceiling) then
   begin
    mtx_unlock(umtx_lock);
    Result:=EINVAL;
    goto _out;
   end;

   if (ceiling < uq^.uq_inherited_pri) then
   begin
    uq^.uq_inherited_pri:=ceiling;
    //
    thread_lock(td);
    //
    if (uq^.uq_inherited_pri < UPRI(td)) then
    begin
     sched_lend_user_prio(td, uq^.uq_inherited_pri);
    end;
    //
    thread_unlock(td);
   end;

  end;

  mtx_unlock(umtx_lock);

  owner:=casuword32(m^.m_owner, UMUTEX_CONTESTED, id or UMUTEX_CONTESTED);

  if (owner=UMUTEX_CONTESTED) then
  begin
   Result:=0;
   Break;
  end;

  if (owner=DWORD($40000000)) then
  begin
   Result:=EINVAL;
   Break;
  end;

  if (owner=DWORD(-1)) then
  begin
   Result:=EFAULT;
   Break;
  end;

  if ((flags and UMUTEX_ERROR_CHECK)<>0) and
     ((owner and (not UMUTEX_CONTESTED))=id) then
  begin
   Result:=EDEADLK;
   Break;
  end;

  if (mode<>0) then
  begin
   Result:=EBUSY;
   Break;
  end;

  if (Result<>0) then Break;

  umtxq_lock  (@uq^.uq_key);
  umtxq_insert(uq);
  umtxq_unbusy(@uq^.uq_key);
  Result:=umtxq_sleep(uq, 'umtxpp', timo);
  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);

  mtx_lock(umtx_lock);
  uq^.uq_inherited_pri:=old_inherited_pri;
  pri:=PRI_MAX;

  pi:=TAILQ_FIRST(@uq^.uq_pi_contested);
  while (pi<>nil) do
  begin
   uq2:=TAILQ_FIRST(@pi^.pi_blocked);
   if (uq2<>nil) then
   begin
    if (pri > UPRI(uq2^.uq_thread)) then
    begin
     pri:=UPRI(uq2^.uq_thread);
    end;
   end;
   //
   pi:=TAILQ_NEXT(pi,@pi^.pi_link);
  end;

  if (pri > uq^.uq_inherited_pri) then
  begin
   pri:=uq^.uq_inherited_pri;
  end;

  thread_lock(td);
  sched_lend_user_prio(td, pri);
  thread_unlock(td);

  mtx_unlock(umtx_lock);
 end;

 if (Result<>0) then
 begin
  mtx_lock(umtx_lock);

  uq^.uq_inherited_pri:=old_inherited_pri;
  pri:=PRI_MAX;

  pi:=TAILQ_FIRST(@uq^.uq_pi_contested);
  while (pi<>nil) do
  begin
   uq2:=TAILQ_FIRST(@pi^.pi_blocked);
   if (uq2<>nil) then
   begin
    if (pri > UPRI(uq2^.uq_thread)) then
    begin
     pri:=UPRI(uq2^.uq_thread);
    end;
   end;
   //
   pi:=TAILQ_NEXT(pi,@pi^.pi_link);
  end;

  if (pri > uq^.uq_inherited_pri) then
  begin
   pri:=uq^.uq_inherited_pri;
  end;

  thread_lock(td);
  sched_lend_user_prio(td, pri);
  thread_unlock(td);

  mtx_unlock(umtx_lock);
 end;

_out:
 umtxq_lock  (@uq^.uq_key);
 umtxq_unbusy(@uq^.uq_key);
 umtxq_unlock(@uq^.uq_key);

 umtx_key_release(@uq^.uq_key);
end;

function do_unlock_pp(td:p_kthread;m:p_umutex;flags:Integer):Integer;
var
 key:umtx_key;
 uq,uq2:p_umtx_q;
 pi:p_umtx_pi;
 owner,id,rceiling:DWORD;
 pri,new_inherited_pri,su:Integer;

 tid:Integer;
begin
 Result:=0;

 //Writeln('do_unlock_pp(',HexStr(m),',',flags,')');

 id:=td^.td_tid;
 uq:=td^.td_umtxq;

 su:=1;
 //su:=(priv_check(td, PRIV_SCHED_RTPRIO)=0);

 owner:=fuword32(m^.m_owner);

 if (owner=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (owner=DWORD($40000000)) then
 begin
  Exit(EINVAL);
 end;

 if ((owner and (not UMUTEX_CONTESTED))<>id) then
 begin
  Exit(EPERM);
 end;

 Result:=copyin(@m^.m_ceilings[1], @rceiling, sizeof(DWORD));
 if (Result<>0) then Exit;

 if (rceiling=DWORD(-1)) then
 begin
  new_inherited_pri:=1023;
 end else
 begin
  if (rceiling > 767) then
  begin
   Exit(EINVAL);
  end;
  rceiling:=767 - rceiling;
  new_inherited_pri:=1023 - rceiling;
 end;

 Result:=umtx_key_get(m, TYPE_PP_UMUTEX, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock  (@key);
 umtxq_busy  (@key);

 if ((flags and $80) <> 0) then
 begin
  tid:=umtxq_first_tid(@key,UMTX_SHARED_QUEUE);
 end;

 umtxq_unlock(@key);

 if ((flags and $80) <> 0) then
 begin

  if (tid <> -1) then
  begin
   id:=tid or UMUTEX_CONTESTED;
  end else
  begin
   id:=UMUTEX_CONTESTED;
  end;

  Result:=suword32(m^.m_owner, id);

  umtxq_lock(@key);

  if (tid <> -1) and (Result = 0) then
  begin
   umtxq_signal_by_tid(@key,tid,UMTX_SHARED_QUEUE);
  end;

 end else
 begin

  Result:=suword32(m^.m_owner, UMUTEX_CONTESTED);

  umtxq_lock(@key);

  if (Result=0) then
  begin
   umtxq_signal(@key, 1);
  end;

 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 if (Result=-1) then
 begin
  Result:=EFAULT;
 end else
 begin
  mtx_lock(umtx_lock);

  if ((flags and $400) <> 0) or (su<>0) then
  begin
   uq^.uq_inherited_pri:=new_inherited_pri;
  end;

  pri:=1023;

  pi:=TAILQ_FIRST(@uq^.uq_pi_contested);
  while (pi<>nil) do
  begin
   uq2:=TAILQ_FIRST(@pi^.pi_blocked);
   if (uq2<>nil) then
   begin
    if (pri > UPRI(uq2^.uq_thread)) then
    begin
     pri:=UPRI(uq2^.uq_thread);
    end;
   end;
   //
   pi:=TAILQ_NEXT(pi,@pi^.pi_link);
  end;

  if (pri > uq^.uq_inherited_pri) then
  begin
   pri:=uq^.uq_inherited_pri;
  end;

  thread_lock(td);
  sched_lend_user_prio(td, pri);
  thread_unlock(td);

  mtx_unlock(umtx_lock);
 end;

 umtx_key_release(@key);
end;

function do_set_ceiling(td:p_kthread;m:p_umutex;ceiling:DWORD;old_ceiling:PDWORD):Integer;
var
 uq:p_umtx_q;
 save_ceiling:DWORD;
 id,owner:DWORD;
 flags:DWORD;
begin
 Result:=0;

 //Writeln('do_set_ceiling(',HexStr(m),',',ceiling,')');

 flags:=fuword32(m^.m_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if ((flags and UMUTEX_PRIO_PROTECT)=0) then Exit(EINVAL);
 if (ceiling>PRI_MAX) then Exit(EINVAL);

 id:=td^.td_tid;
 uq:=td^.td_umtxq;

 Result:=umtx_key_get(m, TYPE_PP_UMUTEX, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 repeat
  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  save_ceiling:=fuword32(m^.m_ceilings[0]);

  owner:=fuword32(m^.m_owner);

  if (owner=DWORD(-1)) then
  begin
   Result:=EFAULT;
   Break;
  end;

  owner:=casuword32(m^.m_owner,UMUTEX_CONTESTED,id or UMUTEX_CONTESTED);

  if (owner=DWORD(-1)) then
  begin
   Exit(EFAULT);
  end;

  if (owner=UMUTEX_CONTESTED) then
  begin
   suword32(m^.m_ceilings[0],ceiling);
   suword32(m^.m_owner,UMUTEX_CONTESTED);
   Result:=0;
   Break;
  end;

  if ((owner and (not UMUTEX_CONTESTED))=id) then
  begin
   suword32(m^.m_ceilings[0],ceiling);
   Result:=0;
   Break;
  end;

  if (Result<>0) then Break;

  umtxq_lock  (@uq^.uq_key);
  umtxq_insert(uq);
  umtxq_unbusy(@uq^.uq_key);

  Result:=umtxq_sleep(uq, 'umtxpp', 0);

  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);
 until false;

 umtxq_lock(@uq^.uq_key);

 if (Result=0) then
 begin
  umtxq_signal(@uq^.uq_key,High(Integer));
 end;

 umtxq_unbusy(@uq^.uq_key);
 umtxq_unlock(@uq^.uq_key);

 umtx_key_release(@uq^.uq_key);

 if (Result=0) and (old_ceiling<>nil) then
 begin
  suword32(old_ceiling^,save_ceiling);
 end;
end;

//////

function _do_lock_umutex(td:p_kthread;m:p_umutex;flags:Integer;timo:Int64;mode:Integer):Integer;
begin
 Case (flags and (UMUTEX_PRIO_INHERIT or UMUTEX_PRIO_PROTECT)) of
                    0:Result:=_do_lock_normal(td,m,flags,timo,mode);
  UMUTEX_PRIO_INHERIT:Result:=_do_lock_pi(td,m,flags,timo,mode);
  UMUTEX_PRIO_PROTECT:Result:=_do_lock_pp(td,m,flags,timo,mode);
  else
   Exit(EINVAL);
 end;
end;

function do_lock_umutex(td:p_kthread;m:p_umutex;timeout:p_timespec;mode:Integer):Integer;
var
 flags:DWORD;
 tv,ts,ts2:Int64;
begin
 Result:=0;

 flags:=fuword32(m^.m_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 if (timeout=nil) then
 begin
  Result:=_do_lock_umutex(td,m,flags,0,mode);
  if (Result=EINTR) and (mode<>_UMUTEX_WAIT) then
  begin
   Result:=ERESTART;
  end;
 end else
 begin
  tv:=TIMESPEC_TO_UNIT(timeout);
  ts:=get_unit_uptime;
  ts:=ts+tv;

  repeat
   Result:=_do_lock_umutex(td,m,flags,tvtohz(tv),mode);
   if (Result<>ETIMEDOUT) then Break;

   ts2:=get_unit_uptime;
   if (ts2>=ts) then
   begin
    Result:=ETIMEDOUT;
    Break;
   end;

   tv:=ts-ts2;
  until false;

  if (Result=ERESTART) then
  begin
   Result:=EINTR;
  end;
 end;
end;

function do_unlock_umutex(td:p_kthread;m:p_umutex):Integer; inline;
var
 flags:DWORD;
begin
 Result:=0;

 flags:=fuword32(m^.m_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 Case (flags and (UMUTEX_PRIO_INHERIT or UMUTEX_PRIO_PROTECT)) of
                    0:Result:=do_unlock_normal(td,m,flags);
  UMUTEX_PRIO_INHERIT:Result:=do_unlock_pi(td,m,flags);
  UMUTEX_PRIO_PROTECT:Result:=do_unlock_pp(td,m,flags);
  else
   Exit(EINVAL);
 end;
end;

////

function do_cv_wait(td:p_kthread;cv:p_ucond;m:p_umutex;timeout:p_timespec;wflags:QWORD):Integer;
var
 uq:p_umtx_q;
 clockid:Integer;
 oldlen:Integer;
 flags:Integer;
 cts,ets,tts:Int64;
begin
 Result:=0;

 //Writeln('do_cv_wait(',HexStr(cv),',',HexStr(m),',',wflags,')');

 uq:=td^.td_umtxq;

 flags:=fuword32(cv^.c_flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(cv, TYPE_CV, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 if ((wflags and CVWAIT_CLOCKID)<>0) then
 begin
  clockid:=fuword32(cv^.c_clockid);
  if (clockid<CLOCK_REALTIME) or
     (clockid>CLOCK_SECOND) then
  begin
   umtx_key_release(@uq^.uq_key);
   Exit(EINVAL);
  end;
 end else
 if ((wflags and CVWAIT_ABSTIME)<>0) then
 begin
  clockid:=CLOCK_REALTIME;
 end else
 begin
  clockid:=CLOCK_PROCTIME;
 end;

 umtxq_lock  (@uq^.uq_key);
 umtxq_busy  (@uq^.uq_key);
 umtxq_insert(uq);
 umtxq_unlock(@uq^.uq_key);

 if (fuword32(cv^.c_has_waiters)=0) then
 begin
  suword32(cv^.c_has_waiters,1);
 end;

 umtxq_lock  (@uq^.uq_key);
 umtxq_unbusy(@uq^.uq_key);
 umtxq_unlock(@uq^.uq_key);

 Result:=do_unlock_umutex(td,m);

 umtxq_lock(@uq^.uq_key);

 if (Result=0) then
 begin
  if (timeout=nil) then
  begin
   Result:=umtxq_sleep(uq, 'ucond', 0);
  end else
  begin
   if ((wflags and CVWAIT_ABSTIME)=0) then
   begin
    ets:=0;
    kern_clock_gettime_unit(clockid,@ets);

    tts:=TIMESPEC_TO_UNIT(timeout);
    ets:=ets+tts;
   end else
   begin
    ets:=TIMESPEC_TO_UNIT(timeout);

    cts:=0;
    kern_clock_gettime_unit(clockid,@cts);

    tts:=ets-cts;
   end;

   repeat
    Result:=umtxq_sleep(uq, 'ucond', tvtohz(tts));
    if (Result<>ETIMEDOUT) then Break;

    cts:=0;
    kern_clock_gettime_unit(clockid,@cts);

    if (cts>=ets) then
    begin
     Result:=ETIMEDOUT;
     Break;
    end;

    tts:=ets-cts;
   until false;
  end;
 end;

 if ((uq^.uq_flags and UQF_UMTXQ)=0) then
 begin
  Result:=0;
 end else
 begin
  umtxq_busy(@uq^.uq_key);

  if ((uq^.uq_flags and UQF_UMTXQ)<>0) then
  begin
   oldlen:=uq^.uq_cur_queue^.length;
   umtxq_remove(uq);

   if (oldlen=1) then
   begin
    umtxq_unlock(@uq^.uq_key);
    suword32(cv^.c_has_waiters,0);
    umtxq_lock(@uq^.uq_key);
   end;
  end;

  umtxq_unbusy(@uq^.uq_key);

  if (Result=ERESTART) then
  begin
   Result:=EINTR;
  end;
 end;

 umtxq_unlock(@uq^.uq_key);

 umtx_key_release(@uq^.uq_key);
end;

function do_cv_signal(td:p_kthread;cv:p_ucond):Integer;
var
 key:umtx_key;
 count,nwake,flags:Integer;
begin
 Result:=0;

 //Writeln('do_cv_signal(',HexStr(cv),')');

 flags:=fuword32(cv^.c_flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(cv, TYPE_CV, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);

 count:=umtxq_count (@key);
 nwake:=umtxq_signal(@key, 1);

 if (count<=nwake) then
 begin
  umtxq_unlock(@key);
  Result:=suword32(cv^.c_has_waiters,0);
  umtxq_lock(@key);
  if (Result<>0) then Result:=EFAULT;
 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);
end;

function do_cv_broadcast(td:p_kthread;cv:p_ucond):Integer;
var
 key:umtx_key;
 flags:Integer;
begin
 Result:=0;

 //Writeln('do_cv_broadcast(',HexStr(cv),')');

 flags:=fuword32(cv^.c_flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(cv, TYPE_CV, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock  (@key);
 umtxq_busy  (@key);
 umtxq_signal(@key, High(Integer));
 umtxq_unlock(@key);

 Result:=suword32(cv^.c_has_waiters,0);
 if (Result<>0) then Result:=EFAULT;

 umtxq_lock  (@key);
 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);
end;

////

function do_rw_rdlock(td:p_kthread;rwlock:p_urwlock;fflag:QWORD;timo:Int64):Integer;
label
 _sleep;
var
 uq:p_umtx_q;
 flags,wrflags:DWORD;
 state,oldstate:Integer;
 blocked_readers:Integer;
begin
 Result:=0;

 uq:=td^.td_umtxq;

 flags:=fuword32(rwlock^.rw_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(rwlock, TYPE_RWLOCK, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 wrflags:=URWLOCK_WRITE_OWNER;

 if ((fflag and URWLOCK_PREFER_READER)=0) and ((flags and URWLOCK_PREFER_READER)=0) then
 begin
  wrflags:=wrflags or URWLOCK_WRITE_WAITERS;
 end;

 repeat
  state:=fuword32(rwlock^.rw_state);

  While ((state and wrflags)=0) do
  begin
   if (URWLOCK_READER_COUNT(state)=URWLOCK_MAX_READERS) then
   begin
    umtx_key_release(@uq^.uq_key);
    Exit(EAGAIN);
   end;

   oldstate:=casuword32(rwlock^.rw_state,state,state+1);

   if (oldstate=-1) then
   begin
    umtx_key_release(@uq^.uq_key);
    Exit(EFAULT);
   end;

   if (oldstate=state) then
   begin
    umtx_key_release(@uq^.uq_key);
    Exit(0);
   end;

   state:=oldstate;
  end;

  if (Result<>0) then Break;

  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  state:=fuword32(rwlock^.rw_state);

  While ((state and wrflags)<>0) and ((state and URWLOCK_READ_WAITERS)=0) do
  begin
   oldstate:=casuword32(rwlock^.rw_state,state,state or URWLOCK_READ_WAITERS);

   if (oldstate=-1) then
   begin
    Result:=EFAULT;
    Break;
   end;

   if (oldstate=state) then
   begin
    goto _sleep;
   end;

   state:=oldstate;
  end;

  if (Result<>0) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);
   Break;
  end;

  if ((state and wrflags)=0) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);

   Continue;
  end;

  _sleep:

  blocked_readers:=fuword32(rwlock^.rw_blocked_readers);
  suword32(rwlock^.rw_blocked_readers,blocked_readers+1);

  While ((state and wrflags)<>0) do
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_insert(uq);
   umtxq_unbusy(@uq^.uq_key);

   Result:= umtxq_sleep(uq, 'urdlck', timo);

   umtxq_busy  (@uq^.uq_key);
   umtxq_remove(uq);
   umtxq_unlock(@uq^.uq_key);

   if (Result<>0) then Break;

   state:=fuword32(rwlock^.rw_state);
  end;

  blocked_readers:=fuword32(rwlock^.rw_blocked_readers);
  suword32(rwlock^.rw_blocked_readers,blocked_readers-1);

  if (blocked_readers=1) then
  begin
   state:=fuword32(rwlock^.rw_state);
   repeat
    oldstate:=casuword32(rwlock^.rw_state,state,state and (not URWLOCK_READ_WAITERS));

    if (oldstate=-1) then
    begin
     Result:=EFAULT;
     Break;
    end;

    if (oldstate=state) then
    begin
     Break;
    end;

    state:=oldstate;
   until false;
  end;

  umtxq_lock  (@uq^.uq_key);
  umtxq_unbusy(@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

 until (Result<>0);

 umtx_key_release(@uq^.uq_key);
end;

function do_rw_rdlock2(td:p_kthread;rwlock:p_urwlock;fflag:QWORD;timeout:p_timespec):Integer;
var
 ts,ts2,tv:Int64;
begin
 Result:=0;

 ts:=get_unit_uptime;
 ts:=ts+TIMESPEC_TO_UNIT(timeout);
 tv:=ts;

 repeat
  Result:=do_rw_rdlock(td,rwlock,fflag,tvtohz(tv));

  if (Result<>ETIMEDOUT) then Break;

  ts2:=get_unit_uptime;

  if (ts2>=ts) then
  begin
   Result:=ETIMEDOUT;
   Break;
  end;

  tv:=ts-ts2;
 until false;

 if (Result=ERESTART) then
 begin
  Result:=EINTR;
 end;
end;

function do_rw_wrlock(td:p_kthread;rwlock:p_urwlock;fflag:QWORD;timo:Int64):Integer;
label
 _sleep;
var
 uq:p_umtx_q;
 flags:Integer;
 state,oldstate:Integer;
 blocked_writers:Integer;
 blocked_readers:Integer;
begin
 Result:=0;

 uq:=td^.td_umtxq;

 flags:=fuword32(rwlock^.rw_flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(rwlock, TYPE_RWLOCK, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 blocked_readers:=0;

 repeat
  state:=fuword32(rwlock^.rw_state);

  while ((state and URWLOCK_WRITE_OWNER)=0) and (URWLOCK_READER_COUNT(state)=0) do
  begin
   oldstate:=casuword32(rwlock^.rw_state,state,state or URWLOCK_WRITE_OWNER);

   if (oldstate=-1) then
   begin
    umtx_key_release(@uq^.uq_key);
    Exit(EFAULT);
   end;

   if (oldstate=state) then
   begin
    umtx_key_release(@uq^.uq_key);
    Exit(0);
   end;

   state:=oldstate;
  end;

  if (Result<>0) then
  begin
   if ((state and (URWLOCK_WRITE_OWNER or URWLOCK_WRITE_WAITERS))=0) and
      (blocked_readers<>0) then
   begin
    umtxq_lock(@uq^.uq_key);
    umtxq_busy(@uq^.uq_key);
    umtxq_signal_queue(@uq^.uq_key, High(Integer), UMTX_SHARED_QUEUE);
    umtxq_unbusy(@uq^.uq_key);
    umtxq_unlock(@uq^.uq_key);
   end;
   Break;
  end;

  umtxq_lock  (@uq^.uq_key);
  umtxq_busy  (@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

  state:=fuword32(rwlock^.rw_state);

  while (((state and URWLOCK_WRITE_OWNER)<>0) or (URWLOCK_READER_COUNT(state)<>0)) and
        ((state and URWLOCK_WRITE_WAITERS)=0) do
  begin
   oldstate:=casuword32(rwlock^.rw_state,state,state or URWLOCK_WRITE_WAITERS);

   if (oldstate=-1) then
   begin
    Result:=EFAULT;
    Break;
   end;

   if (oldstate=state) then
   begin
    goto _sleep;
   end;

   state:=oldstate;
  end;

  if (Result<>0) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);
   Break;
  end;

  if ((state and URWLOCK_WRITE_OWNER)=0) and (URWLOCK_READER_COUNT(state)=0) then
  begin
   umtxq_lock  (@uq^.uq_key);
   umtxq_unbusy(@uq^.uq_key);
   umtxq_unlock(@uq^.uq_key);

   Continue;
  end;

  _sleep:

  blocked_writers:=fuword32(rwlock^.rw_blocked_writers);
  suword32(rwlock^.rw_blocked_writers,blocked_writers+1);

  While ((state and URWLOCK_WRITE_OWNER) or URWLOCK_READER_COUNT(state)<>0) do
  begin
   umtxq_lock(@uq^.uq_key);
   umtxq_insert_queue(uq, UMTX_EXCLUSIVE_QUEUE);
   umtxq_unbusy(@uq^.uq_key);

   Result:=umtxq_sleep(uq, 'uwrlck', timo);

   umtxq_busy(@uq^.uq_key);
   umtxq_remove_queue(uq, UMTX_EXCLUSIVE_QUEUE);
   umtxq_unlock(@uq^.uq_key);

   if (Result<>0) then Break;

   state:=fuword32(rwlock^.rw_state);
  end;

  blocked_writers:=fuword32(rwlock^.rw_blocked_writers);
  suword32(rwlock^.rw_blocked_writers,blocked_writers-1);

  if (blocked_writers=1) then
  begin
   state:=fuword32(rwlock^.rw_state);

   repeat
    oldstate:=casuword32(rwlock^.rw_state,state,state and (not URWLOCK_WRITE_WAITERS));

    if (oldstate=-1) then
    begin
     Result:=EFAULT;
     Break;
    end;

    if (oldstate=state) then
    begin
     Break;
    end;

    state:=oldstate;
   until (Result<>0);

   blocked_readers:=fuword32(rwlock^.rw_blocked_readers);
  end else
  begin
   blocked_readers:=0;
  end;

  umtxq_lock  (@uq^.uq_key);
  umtxq_unbusy(@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);

 until false;

 umtx_key_release(@uq^.uq_key);
end;

function do_rw_wrlock2(td:p_kthread;rwlock:p_urwlock;fflag:QWORD;timeout:p_timespec):Integer;
var
 ts,ts2,tv:Int64;
begin
 Result:=0;

 ts:=get_unit_uptime;
 ts:=ts+TIMESPEC_TO_UNIT(timeout);
 tv:=ts;

 repeat
  Result:=do_rw_wrlock(td,rwlock,fflag,tvtohz(tv));

  if (Result<>ETIMEDOUT) then Break;

  ts2:=get_unit_uptime;

  if (ts2>=ts) then
  begin
   Result:=ETIMEDOUT;
   Break;
  end;

  tv:=ts-ts2;
 until false;

 if (Result=ERESTART) then
 begin
  Result:=EINTR;
 end;
end;

function do_rw_unlock(td:p_kthread;rwlock:p_urwlock):Integer;
label
 _out;
var
 uq:p_umtx_q;
 flags:DWORD;
 state,oldstate:Integer;
 q,count:Integer;
begin
 Result:=0;

 uq:=td^.td_umtxq;

 flags:=fuword32(rwlock^.rw_flags);

 if (flags=DWORD(-1)) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(rwlock, TYPE_RWLOCK, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 state:=fuword32(rwlock^.rw_state);

 if ((state and URWLOCK_WRITE_OWNER)<>0) then
 begin
  repeat
   oldstate:=casuword32(rwlock^.rw_state,state,state and (not URWLOCK_WRITE_OWNER));

   if (oldstate=-1) then
   begin
    Result:=EFAULT;
    goto _out;
   end;

   if (oldstate<>state) then
   begin
    state:=oldstate;
    if ((oldstate and URWLOCK_WRITE_OWNER)=0) then
    begin
     Result:=EPERM;
     goto _out;
    end;
   end else
   begin
    Break;
   end;

  until false;
 end else
 if (URWLOCK_READER_COUNT(state)<>0) then
 begin
  repeat
   oldstate:=casuword32(rwlock^.rw_state,state,state-1);

   if (oldstate=-1) then
   begin
    Result:=EFAULT;
    goto _out;
   end;

   if (oldstate<>state) then
   begin
    state:=oldstate;
    if (URWLOCK_READER_COUNT(oldstate)=0) then
    begin
     Result:=EPERM;
     goto _out;
    end;
   end else
   begin
    Break;
   end;

  until false;
 end else
 begin
  Result:=EPERM;
  goto _out;
 end;

 count:=0;

 if ((flags and URWLOCK_PREFER_READER)=0) then
 begin
  if (state and URWLOCK_WRITE_WAITERS)<>0 then
  begin
   count:=1;
   q:=UMTX_EXCLUSIVE_QUEUE;
  end else
  if (state and URWLOCK_READ_WAITERS)<>0 then
  begin
   count:=High(Integer);
   q:=UMTX_SHARED_QUEUE;
  end;
 end else
 begin
  if ((state and URWLOCK_READ_WAITERS)<>0) then
  begin
   count:=High(Integer);
   q:=UMTX_SHARED_QUEUE;
  end else
  if ((state and URWLOCK_WRITE_WAITERS)<>0) then
  begin
   count:=1;
   q:=UMTX_EXCLUSIVE_QUEUE;
  end;
 end;

 if (count<>0) then
 begin
  umtxq_lock(@uq^.uq_key);
  umtxq_busy(@uq^.uq_key);
  umtxq_signal_queue(@uq^.uq_key, count, q);
  umtxq_unbusy(@uq^.uq_key);
  umtxq_unlock(@uq^.uq_key);
 end;

 _out:
  umtx_key_release(@uq^.uq_key);
end;

////

function do_sem_wait(td:p_kthread;sem:p__usem;timeout:p_timespec):Integer;
label
 _fault;
var
 uq:p_umtx_q;
 flags:Integer;
 has_waiters,count:DWORD;
 cts,ets,tv:Int64;
begin
 Result:=0;

 //Writeln('do_sem_wait(',HexStr(sem),')');

 uq:=td^.td_umtxq;

 flags:=fuword32(sem^._flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(sem, TYPE_SEM, GET_SHARE(flags), @uq^.uq_key);
 if (Result<>0) then Exit;

 umtxq_lock  (@uq^.uq_key);
 umtxq_busy  (@uq^.uq_key);
 umtxq_insert(uq);
 umtxq_unlock(@uq^.uq_key);

 has_waiters:=fuword32(sem^._has_waiters);

 if (has_waiters=DWORD(-1)) then
 begin
  _fault:
  umtxq_lock  (@uq^.uq_key);
  umtxq_unbusy(@uq^.uq_key);
  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);

  umtx_key_release(@uq^.uq_key);

  Exit(EFAULT);
 end;

 has_waiters:=casuword32(sem^._has_waiters,0,1);

 if (has_waiters=DWORD(-1)) then
 begin
  goto _fault;
 end;

 count:=fuword32(sem^._count);

 if (count=DWORD(-1)) then
 begin
  goto _fault;
 end;

 umtxq_lock  (@uq^.uq_key);
 umtxq_unbusy(@uq^.uq_key);

 if (count<>0) then
 begin
  umtxq_remove(uq);
  umtxq_unlock(@uq^.uq_key);

  umtx_key_release(@uq^.uq_key);
  Exit(0);
 end;

 if (timeout=nil) then
 begin
  Result:=umtxq_sleep(uq, 'usem', 0);
 end else
 begin
  ets:=get_unit_uptime;
  tv:=TIMESPEC_TO_UNIT(timeout);
  ets:=ets+tv;

  repeat
   Result:=umtxq_sleep(uq, 'usem', tvtohz(tv));
   if (Result<>ETIMEDOUT) then Break;

   cts:=get_unit_uptime;

   if (cts>=ets) then
   begin
    Result:=ETIMEDOUT;
    Break;
   end;

   tv:=ets-cts;
  until false;
 end;

 if ((uq^.uq_flags and UQF_UMTXQ)=0) then
 begin
  Result:=0;
 end else
 begin
  umtxq_remove(uq);

  if (Result=ERESTART) then
  begin
   Result:=EINTR;
  end;
 end;

 umtxq_unlock(@uq^.uq_key);

 umtx_key_release(@uq^.uq_key);
end;

function do_sem_wake(td:p_kthread;sem:p__usem):Integer;
var
 key:umtx_key;
 flags:Integer;
 count:Integer;
begin
 Result:=0;

 //Writeln('do_sem_wake(',HexStr(sem),')');

 flags:=fuword32(sem^._flags);

 if (flags=-1) then
 begin
  Exit(EFAULT);
 end;

 Result:=umtx_key_get(sem, TYPE_SEM, GET_SHARE(flags), @key);
 if (Result<>0) then Exit;

 umtxq_lock(@key);
 umtxq_busy(@key);
 count:=umtxq_count(@key);

 if (count>0) then
 begin
  if (count=1) then
  begin
   umtxq_unlock(@key);
   Result:=suword32(sem^._has_waiters,0);
   if (Result<>0) then Result:=EFAULT;
   umtxq_lock(@key);
  end;
  umtxq_signal(@key, 1);
 end;

 umtxq_unbusy(@key);
 umtxq_unlock(@key);

 umtx_key_release(@key);
end;

////

function umtx_copyin_timeout(addr:Pointer;tsp:p_timespec):Integer; public;
begin
 Result:=copyin(addr,tsp,SizeOf(timespec));
 if (Result=0) then
 begin
  if (tsp^.tv_sec < 0) or
     (tsp^.tv_nsec>=1000000000) or
     (tsp^.tv_nsec< 0) then
  begin
   Result:=EINVAL;
  end;
 end else
 begin
  Result:=EFAULT;
 end;
end;

function __umtx_op_lock_umtx(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_lock_umtx(td,obj,val,ts);
end;

function __umtx_op_unlock_umtx(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_unlock_umtx(td,obj,val);
end;

function __umtx_op_wait(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_wait(td,obj,val,ts,0,0);
end;

function __umtx_op_wait_uint(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_wait(td,obj,val,ts,1,0);
end;

function __umtx_op_wait_uint_private(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_wait(td,obj,val,ts,1,1);
end;

function __umtx_op_wake(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=kern_umtx_wake(td,obj,val,0);
end;

function __umtx_op_nwake_private(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
const
 BATCH_SIZE=128;
var
 count:Integer;
 tocopy:Integer;
 i,pos:Integer;
 uaddrs:array[0..BATCH_SIZE-1] of Pointer;
 upp:PPByte;
begin
 Result:=0;
 count :=val;
 upp   :=obj;
 i     :=0;
 pos   :=0;

 While (count>0) do
 begin
  tocopy:=count;
  if (tocopy>BATCH_SIZE) then
  begin
   tocopy:=BATCH_SIZE;
  end;

  Result:=copyin(upp+pos,@uaddrs,tocopy);
  if (Result<>0) then Exit;

  For i:=0 to tocopy-1 do
  begin
   kern_umtx_wake(td,uaddrs[i],High(Integer),1);
  end;

  count:=count-tocopy;
  pos  :=pos  +tocopy;
 end;
end;

function __umtx_op_wake_private(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=kern_umtx_wake(td,obj,val,1);
end;

function __umtx_op_lock_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_lock_umutex(td,obj,ts,0);
end;

function __umtx_op_trylock_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_lock_umutex(td,obj,nil,_UMUTEX_TRY);
end;

function __umtx_op_wait_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_lock_umutex(td,obj,ts,_UMUTEX_WAIT);
end;

function __umtx_op_wake_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_wake_umutex(td,obj);
end;

function __umtx_op_unlock_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_unlock_umutex(td,obj);
end;

function __umtx_op_set_ceiling(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_set_ceiling(td,obj,val,uaddr1);
end;

function __umtx_op_cv_wait(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin
 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_cv_wait(td,obj,uaddr1,ts,val);
end;

function __umtx_op_cv_signal(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_cv_signal(td,obj);
end;

function __umtx_op_cv_broadcast(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_cv_broadcast(td,obj);
end;

function __umtx_op_rw_rdlock(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 timeout:timespec;
begin

 if (uaddr2=nil) then
 begin
  Result:=do_rw_rdlock(td,obj,val,0);
 end else
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  Result:=do_rw_rdlock2(td,obj,val,@timeout);
 end;
end;

function __umtx_op_rw_wrlock(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 timeout:timespec;
begin

 if (uaddr2=nil) then
 begin
  Result:=do_rw_wrlock(td,obj,val,0);
 end else
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  Result:=do_rw_wrlock2(td,obj,val,@timeout);
 end;
end;

function __umtx_op_rw_unlock(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_rw_unlock(td,obj);
end;

function sys__umtx_lock(mtx:Pointer):Integer;
var
 td:p_kthread;
begin
 if (mtx=nil) then Exit(EINVAL);
 td:=curkthread;
 if (td=nil) then Exit(EFAULT);
 Result:=_do_lock_umtx(td,mtx,td^.td_tid,0);
end;

function sys__umtx_unlock(mtx:Pointer):Integer;
var
 td:p_kthread;
begin
 if (mtx=nil) then Exit(EINVAL);
 td:=curkthread;
 if (td=nil) then Exit(EFAULT);
 Result:=do_unlock_umtx(td,mtx,td^.td_tid);
end;

function __umtx_op_sem_wait(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 ts:p_timespec;
 timeout:timespec;
begin

 ts:=nil;
 if (uaddr2<>nil) then
 begin
  Result:=umtx_copyin_timeout(uaddr2,@timeout);
  if (Result<>0) then Exit;
  ts:=@timeout;
 end;
 Result:=do_sem_wait(td,obj,ts);
end;

function __umtx_op_sem_wake(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_sem_wake(td,obj)
end;

function __umtx_op_sem_signalto(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_cv_signalto(td,obj,val);
end;

function __umtx_op_wake3_umutex(td:p_kthread;obj:Pointer;val:QWORD;uaddr1,uaddr2:Pointer):Integer; inline;
begin
 Result:=do_wake3_umutex(td,obj,val);
end;

function sys__umtx_op(obj:Pointer;op:Integer;val:QWORD;uaddr1,uaddr2:Pointer):Integer;
var
 td:p_kthread;
begin
 if (obj=nil) then Exit(EINVAL);
 td:=curkthread;
 if (td=nil) then Exit(EFAULT);

 Case op of
  UMTX_OP_LOCK             :Result:=__umtx_op_lock_umtx        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_UNLOCK           :Result:=__umtx_op_unlock_umtx      (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_WAIT             :Result:=__umtx_op_wait             (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_WAKE             :Result:=__umtx_op_wake             (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_MUTEX_TRYLOCK    :Result:=__umtx_op_trylock_umutex   (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_MUTEX_LOCK       :Result:=__umtx_op_lock_umutex      (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_MUTEX_UNLOCK     :Result:=__umtx_op_unlock_umutex    (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_SET_CEILING      :Result:=__umtx_op_set_ceiling      (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_CV_WAIT          :Result:=__umtx_op_cv_wait          (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_CV_SIGNAL        :Result:=__umtx_op_cv_signal        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_CV_BROADCAST     :Result:=__umtx_op_cv_broadcast     (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_WAIT_UINT        :Result:=__umtx_op_wait_uint        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_RW_RDLOCK        :Result:=__umtx_op_rw_rdlock        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_RW_WRLOCK        :Result:=__umtx_op_rw_wrlock        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_RW_UNLOCK        :Result:=__umtx_op_rw_unlock        (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_WAIT_UINT_PRIVATE:Result:=__umtx_op_wait_uint_private(td,obj,val,uaddr1,uaddr2);
  UMTX_OP_WAKE_PRIVATE     :Result:=__umtx_op_wake_private     (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_MUTEX_WAIT       :Result:=__umtx_op_wait_umutex      (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_MUTEX_WAKE       :Result:=__umtx_op_wake_umutex      (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_SEM_WAIT         :Result:=__umtx_op_sem_wait         (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_SEM_WAKE         :Result:=__umtx_op_sem_wake         (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_NWAKE_PRIVATE    :Result:=__umtx_op_nwake_private    (td,obj,val,uaddr1,uaddr2);
  UMTX_OP_SEM_SIGNALTO     :Result:=__umtx_op_sem_signalto     (td,obj,val,uaddr1,uaddr2); //Sony extension
  UMTX_OP_MUTEX_WAKE3      :Result:=__umtx_op_wake3_umutex     (td,obj,val,uaddr1,uaddr2); //Sony extension
  else
   Exit(EINVAL);
 end;

 //Writeln('umtx_op(',HexStr(obj),',',op,',',val,',',HexStr(uaddr1),',',HexStr(uaddr2),')');
end;

procedure _umutex_init(mtx:p_umutex); inline;
begin
 mtx^:=Default(umutex);
end;


end.

