FPPS4/sys/kern/kern_stub.pas

488 lines
9.8 KiB
Plaintext

unit kern_stub;
{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}
interface
uses
mqueue;
{.$DEFINE chunk_alloc}
const
m_header=WORD($C3C3);
m_free__chunk=1;
m_first_chunk=2;
m_last__chunk=4;
type
p_stub_chunk=^stub_chunk;
stub_chunk=packed record
head :WORD;
flags :WORD;
prev_size:Integer;
curr_size:Integer;
refs :Integer;
link :TAILQ_ENTRY;
body :record end;
end;
function is_near_valid(vaddr,body:Pointer):Boolean;
function is_mask_valid(vaddr,body:Pointer;mask:DWORD):Boolean;
function p_alloc (vaddr:Pointer;size:Integer;guest:Boolean):p_stub_chunk;
function p_alloc_m(vaddr:Pointer;size:Integer;mask:DWORD;guest:Boolean):p_stub_chunk;
procedure p_free (chunk:p_stub_chunk);
procedure p_inc_ref(chunk:p_stub_chunk);
procedure p_dec_ref(chunk:p_stub_chunk);
implementation
uses
{$IFDEF chunk_alloc}
hamt,
{$ENDIF}
kern_rwlock,
vm,
vmparam,
vm_map,
vm_mmap,
md_map,
sys_vm_object,
kern_proc;
var
{$IFDEF chunk_alloc}
chunk_alloc:array[Boolean] of TSTUB_HAMT64;
{$ENDIF}
chunk_free:array[Boolean] of TAILQ_HEAD=
(
(tqh_first:nil;tqh_last:@chunk_free[False].tqh_first),
(tqh_first:nil;tqh_last:@chunk_free[True ].tqh_first)
);
chunk_lock:array[Boolean] of Pointer=(nil,nil);
function AlignUp(addr:PtrUInt;alignment:PtrUInt):PtrUInt; inline;
var
tmp:PtrUInt;
begin
if (alignment=0) then Exit(addr);
tmp:=addr+PtrUInt(alignment-1);
Result:=tmp-(tmp mod alignment)
end;
function is_near_valid(vaddr,body:Pointer):Boolean;
var
delta:Int64;
begin
delta:=abs(Int64(body)-Int64(vaddr));
Result:=(delta<High(Integer));
end;
const
XMASK:array[0..1] of DWORD=(
$FF000000, //0xNN XX XX XX 4-byte instruction overlap
$FFFF0000 //0xNN NN XX XX 3-byte instruction overlap
);
function is_mask_valid(vaddr,body:Pointer;mask:DWORD):Boolean;
var
delta:Int64;
x:DWORD;
begin
delta:=Int64(body)-Int64(vaddr);
if not (abs(delta)<High(Integer)) then Exit(False);
X:=XMASK[mask and 1];
Result:=(DWORD(delta) and X)=(DWORD(mask) and X);
end;
function alloc_segment(start,size:QWORD;guest:Boolean):p_stub_chunk;
var
map:vm_map_t;
err:Integer;
begin
Result:=nil;
if guest then
begin
size:=AlignUp(size+SizeOf(stub_chunk),PAGE_SIZE);
map:=p_proc.p_vmspace;
if (start=0) then
begin
start:=SCE_REPLAY_EXEC_START;
end;
err:=vm_mmap2(map,
@start,
size,
VM_PROT_RWX,
VM_PROT_RWX,
MAP_ANON or MAP_PRIVATE,
OBJT_DEFAULT,
nil,
0,
nil);
if (err<>0) then Exit;
vm_map_lock(map);
vm_map_set_info_locked(map,start,start+size,'#patch',VM_INHERIT_PATCH);
vm_map_unlock(map);
end else
begin
size:=AlignUp(size+SizeOf(stub_chunk),MD_PAGE_SIZE);
if (start=0) then
begin
start:=KERNEL_LOWER;
end;
err:=md_mmap(Pointer(start),size,VM_RWX);
if (err<>0) then Exit;
end;
Result:=Pointer(start);
Result^.head :=m_header;
Result^.flags :=m_free__chunk or m_first_chunk or m_last__chunk;
Result^.prev_size :=0;
Result^.curr_size :=size;
Result^.refs :=0;
Result^.link.tqe_next:=nil;
Result^.link.tqe_prev:=nil;
end;
procedure free_segment(chunk:p_stub_chunk;guest:Boolean);
var
map:vm_map_t;
begin
if guest then
begin
map:=p_proc.p_vmspace;
vm_map_remove(map, qword(chunk), qword(chunk) + chunk^.curr_size);
end else
begin
md_unmap(chunk,chunk^.curr_size);
end;
end;
procedure fix_next_size(chunk:p_stub_chunk);
var
next:p_stub_chunk;
begin
if ((chunk^.flags and m_last__chunk)=0) then
begin
next:=Pointer(chunk)+chunk^.curr_size;
next^.prev_size:=chunk^.curr_size;
end;
end;
procedure split_chunk(chunk:p_stub_chunk;used_size:Integer;guest:Boolean);
var
chunk_size:Integer;
next:p_stub_chunk;
begin
chunk_size:=chunk^.curr_size;
if (AlignUp(used_size+SizeOf(stub_chunk)*2,SizeOf(Pointer))>chunk_size) then Exit;
used_size:=AlignUp(used_size+SizeOf(stub_chunk),SizeOf(Pointer));
next:=Pointer(chunk)+used_size;
chunk^.curr_size:=used_size;
next^.head :=m_header;
next^.flags :=m_free__chunk;
next^.prev_size :=used_size;
next^.curr_size :=chunk_size-used_size;
next^.refs :=0;
next^.link.tqe_next:=nil;
next^.link.tqe_prev:=nil;
if ((chunk^.flags and m_last__chunk)<>0) then
begin
chunk^.flags:=chunk^.flags and (not m_last__chunk);
next ^.flags:=next^.flags or m_last__chunk;
end;
TAILQ_INSERT_TAIL(@chunk_free[guest],next,@next^.link);
fix_next_size(next);
end;
procedure merge_chunk(var chunk:p_stub_chunk;guest:Boolean);
var
prev,next:p_stub_chunk;
begin
if (chunk^.prev_size<>0) and
((chunk^.flags and m_first_chunk)=0) then
begin
prev:=Pointer(chunk)-chunk^.prev_size;
if ((prev^.flags and m_free__chunk)<>0) then
begin
Assert(prev^.curr_size=chunk^.prev_size,'invalid prev chunk curr_size');
Assert(prev^.refs=0 ,'invalid prev chunk refs');
TAILQ_REMOVE(@chunk_free[guest],prev,@prev^.link);
prev^.link:=Default(TAILQ_ENTRY);
prev^.curr_size:=prev^.curr_size+chunk^.curr_size;
if ((chunk^.flags and m_last__chunk)<>0) then
begin
prev^.flags:=prev^.flags or m_last__chunk;
end;
chunk^:=Default(stub_chunk);
chunk:=prev;
fix_next_size(chunk);
end;
end;
if ((chunk^.flags and m_last__chunk)=0) then
begin
next:=Pointer(chunk)+chunk^.curr_size;
if ((next^.flags and m_free__chunk)<>0) then
begin
Assert(next^.prev_size=chunk^.curr_size,'invalid next chunk prev_size');
Assert(next^.refs=0 ,'invalid next chunk refs');
TAILQ_REMOVE(@chunk_free[guest],next,@next^.link);
next^.link:=Default(TAILQ_ENTRY);
chunk^.curr_size:=chunk^.curr_size+next^.curr_size;
if ((next^.flags and m_last__chunk)<>0) then
begin
chunk^.flags:=chunk^.flags or m_last__chunk;
end;
next^:=Default(stub_chunk);
fix_next_size(chunk);
end;
end;
end;
function find_free_chunk(vaddr:Pointer;size:Integer;guest:Boolean):p_stub_chunk;
var
entry,next:p_stub_chunk;
begin
Result:=nil;
size:=size+SizeOf(stub_chunk);
entry:=TAILQ_FIRST(@chunk_free[guest]);
while (entry<>nil) do
begin
next:=TAILQ_NEXT(entry,@entry^.link);
//
if (entry^.curr_size>=size) then
begin
if (vaddr=nil) or is_near_valid(vaddr,@entry^.body) then
begin
TAILQ_REMOVE(@chunk_free[guest],entry,@entry^.link);
entry^.link:=Default(TAILQ_ENTRY);
Exit(entry);
end;
end;
//
entry:=next;
end;
end;
function find_free_chunk_m(vaddr:Pointer;size:Integer;mask:DWORD;guest:Boolean):p_stub_chunk;
var
entry,next:p_stub_chunk;
begin
Result:=nil;
size:=size+SizeOf(stub_chunk);
entry:=TAILQ_FIRST(@chunk_free[guest]);
while (entry<>nil) do
begin
next:=TAILQ_NEXT(entry,@entry^.link);
//
if (entry^.curr_size>=size) then
begin
if is_mask_valid(vaddr,@entry^.body,mask) then
begin
TAILQ_REMOVE(@chunk_free[guest],entry,@entry^.link);
entry^.link:=Default(TAILQ_ENTRY);
Exit(entry);
end;
end;
//
entry:=next;
end;
end;
function p_alloc(vaddr:Pointer;size:Integer;guest:Boolean):p_stub_chunk;
var
chunk:p_stub_chunk;
begin
rw_wlock(chunk_lock[guest]);
chunk:=find_free_chunk(vaddr,size,guest);
if (chunk=nil) then
begin
chunk:=alloc_segment(QWORD(vaddr),size,guest);
Assert(chunk<>nil,'p_alloc NOMEM');
if (vaddr<>nil) then
if (not is_near_valid(vaddr,@chunk^.body)) then
if (QWORD(vaddr)>High(Integer)) then
begin
free_segment(chunk,guest);
if guest then
begin
chunk:=alloc_segment(AlignUp(QWORD(vaddr)-High(Integer),PAGE_SIZE),size,guest);
end else
begin
chunk:=alloc_segment(AlignUp(QWORD(vaddr)-High(Integer),MD_PAGE_SIZE),size,guest);
end;
Assert(chunk<>nil,'p_alloc NOMEM');
end;
if (vaddr<>nil) then
begin
Assert(is_near_valid(vaddr,@chunk^.body),'p_alloc is_near_valid');
end;
end;
split_chunk(chunk,size,guest);
chunk^.flags:=chunk^.flags and (not m_free__chunk);
{$IFDEF chunk_alloc}
HAMT_insert64(@chunk_alloc[guest],QWORD(chunk),chunk);
{$ENDIF}
rw_wunlock(chunk_lock[guest]);
Result:=chunk;
end;
//
function p_alloc_m(vaddr:Pointer;size:Integer;mask:DWORD;guest:Boolean):p_stub_chunk;
var
chunk:p_stub_chunk;
x:Integer;
begin
rw_wlock(chunk_lock[guest]);
chunk:=find_free_chunk_m(vaddr,size,mask,guest);
if (chunk=nil) then
begin
x:=Integer(mask and XMASK[mask and 1]);
if (x<0) and (QWORD(vaddr)<abs(x)) then
begin
Result:=nil;
rw_wunlock(chunk_lock[guest]);
end;
chunk:=alloc_segment(QWORD(Int64(vaddr)+x),size,guest);
if (chunk=nil) then
begin
Result:=nil;
rw_wunlock(chunk_lock[guest]);
end;
if not is_mask_valid(vaddr,@chunk^.body,mask) then
begin
free_segment(chunk,guest);
Result:=nil;
rw_wunlock(chunk_lock[guest]);
end;
end;
split_chunk(chunk,size,guest);
chunk^.flags:=chunk^.flags and (not m_free__chunk);
{$IFDEF chunk_alloc}
HAMT_insert64(@chunk_alloc[guest],QWORD(chunk),chunk);
{$ENDIF}
rw_wunlock(chunk_lock[guest]);
Result:=chunk;
end;
//
procedure p_free(chunk:p_stub_chunk);
var
guest:Boolean;
begin
if (chunk=nil) then Exit;
guest:=is_guest_addr(QWORD(chunk));
rw_wlock(chunk_lock[guest]);
{$IFDEF chunk_alloc}
if (HAMT_search64(@chunk_alloc[guest],QWORD(chunk))=nil) then
begin
rw_wunlock(chunk_lock[guest]);
Exit;
end;
HAMT_delete64(@chunk_alloc[guest],QWORD(chunk),nil);
{$ENDIF}
chunk^.flags:=chunk^.flags or m_free__chunk;
merge_chunk(chunk,guest);
if ((chunk^.flags and (m_first_chunk or m_last__chunk))=(m_first_chunk or m_last__chunk)) then
begin
free_segment(chunk,guest);
end else
begin
TAILQ_INSERT_TAIL(@chunk_free[guest],chunk,@chunk^.link);
end;
rw_wunlock(chunk_lock[guest]);
end;
procedure p_inc_ref(chunk:p_stub_chunk);
begin
if (chunk=nil) then Exit;
System.InterlockedIncrement(chunk^.refs);
end;
procedure p_dec_ref(chunk:p_stub_chunk);
begin
if (chunk=nil) then Exit;
if (System.InterlockedDecrement(chunk^.refs)=0) then
begin
p_free(chunk);
end;
end;
end.