unit md_time; {$mode ObjFPC}{$H+} {$CALLING SysV_ABI_CDecl} interface uses windows, ntapi, time; var md_tsc_freq :QWORD=0; md_unit_freq:QWORD=UNIT_PER_SEC; Procedure md_timeinit; function md_rdtsc:QWORD; assembler; function md_rdtsc_unit:QWORD; assembler; function md_rdtsc_freq:QWORD; assembler; function md_get_rdtsc_freq:QWORD; function get_proc_time:Int64; function get_proc_time_freq:Int64; function get_unit_uptime:Int64; procedure unittime(time:PInt64); procedure calcru_proc(user,syst:PInt64); procedure get_process_cputime(time:PInt64); function GetProcessTime:QWORD; //microsecond procedure calcru_thread(user,syst:PInt64); procedure get_thread_cputime(time:PInt64); procedure gettimezone(z:p_timezone); procedure getadjtime(tv:p_timeval); function kern_clock_gettime_unit(clock_id:Integer;time:PInt64):Integer; function kern_clock_gettime(clock_id:Integer;tp:p_timespec):Integer; function kern_clock_getres(clock_id:Integer;tp:p_timespec):Integer; implementation uses errno; Procedure md_timeinit; var min,max,cur:ULONG; begin NtQueryTimerResolution(@min,@max,@cur); NtSetTimerResolution(max,True,@cur); // md_tsc_freq:=md_get_rdtsc_freq; end; function md_rdtsc:QWORD; assembler; nostackframe; asm lfence rdtsc lfence // shl $32,%rdx or %rdx,%rax end; function md_rdtsc_unit:QWORD; assembler; nostackframe; asm lfence rdtsc lfence // shl $32,%rdx or %rdx,%rax // mulq md_unit_freq(%rip) divq md_tsc_freq (%rip) end; function md_rdtsc_freq:QWORD; assembler; nostackframe; asm lfence rdtsc lfence // shl $32,%rdx or %rdx,%rax // mulq tsc_freq(%rip) divq md_tsc_freq(%rip) end; function _get_rdtsc_freq:QWORD; var shared_page:PQWORD; size:DWORD; R:DWORD; begin Result:=0; shared_page:=nil; size:=0; R:=NtQuerySystemInformation(SystemHypervisorSharedPageInformation, @shared_page,SizeOf(Pointer),@size); if (R<>0) then Exit; if (size<>SizeOf(Pointer)) then Exit; Result:=(UNIT_PER_SEC shl 32) div (shared_page[1] shr 32); end; function tsc_calibrate:QWORD; const samples=80; var i:Integer; tsc_freq :QWORD; qpc_begin:QWORD; tsc_begin:QWORD; qpc_end :QWORD; tsc_end :QWORD; qpc_freq :QWORD; begin tsc_freq:=0; For i:=0 to samples-1 do begin qpc_freq :=get_proc_time_freq; qpc_begin:=get_proc_time; tsc_begin:=md_rdtsc; Sleep(2); qpc_end:=get_proc_time; tsc_end:=md_rdtsc; if (qpc_end<>qpc_begin) then begin tsc_freq:=tsc_freq + (tsc_end - tsc_begin) * qpc_freq div (qpc_end - qpc_begin); end; end; tsc_freq:=tsc_freq div samples; Result:=tsc_freq; end; function md_get_rdtsc_freq:QWORD; begin Result:=_get_rdtsc_freq; if (Result=0) then begin Result:=tsc_calibrate; end; end; function get_proc_time:Int64; var pc:QWORD; pf:QWORD; begin pc:=0; pf:=1; NtQueryPerformanceCounter(@pc,@pf); Result:=pc; end; function get_proc_time_freq:Int64; var pc:QWORD; pf:QWORD; begin pc:=0; pf:=1; NtQueryPerformanceCounter(@pc,@pf); Result:=pf; end; function mul_div_u64(m,d,v:QWORD):QWORD; sysv_abi_default; assembler; nostackframe; asm movq v,%rax mulq m divq d end; function get_unit_uptime:Int64; var pc:QWORD; pf:QWORD; begin pc:=0; pf:=1; NtQueryPerformanceCounter(@pc,@pf); if (pf=UNIT_PER_SEC) then begin Result:=pc; end else begin Result:=mul_div_u64(UNIT_PER_SEC,pf,pc); end; end; type tunittime=procedure(time:PInt64); stdcall; var _unittime:tunittime; procedure unittime(time:PInt64); var h:HMODULE; begin if (_unittime=nil) then begin h:=GetModuleHandle('kernel32.dll'); Pointer(_unittime):=GetProcAddress(h,'GetSystemTimePreciseAsFileTime'); if (_unittime=nil) then begin Pointer(_unittime):=GetProcAddress(h,'GetSystemTimeAsFileTime'); end; end; _unittime(time); end; procedure calcru_proc(user,syst:PInt64); var k:KERNEL_USER_TIMES; R:DWORD; begin k:=Default(KERNEL_USER_TIMES); R:=NtQueryInformationProcess(NtCurrentProcess, ProcessTimes, @k, SizeOf(KERNEL_USER_TIMES), nil); Assert(R=0,'calcru_proc'); user^:=k.UserTime.QuadPart; syst^:=k.KernelTime.QuadPart; end; procedure get_process_cputime(time:PInt64); var k:KERNEL_USER_TIMES; R:DWORD; begin k:=Default(KERNEL_USER_TIMES); R:=NtQueryInformationProcess(NtCurrentProcess, ProcessTimes, @k, SizeOf(KERNEL_USER_TIMES), nil); Assert(R=0,'get_process_cputime'); unittime(@k.ExitTime.QuadPart); time^:=k.ExitTime.QuadPart-k.CreateTime.QuadPart; end; function GetProcessTime:QWORD; //microsecond begin Result:=0; get_process_cputime(@Result); Result:=Result div UNIT_PER_USEC; end; procedure calcru_thread(user,syst:PInt64); var k:KERNEL_USER_TIMES; R:DWORD; begin k:=Default(KERNEL_USER_TIMES); R:=NtQueryInformationThread(NtCurrentThread, ThreadTimes, @k, SizeOf(KERNEL_USER_TIMES), nil); Assert(R=0,'calcru_thread'); user^:=k.UserTime.QuadPart; syst^:=k.KernelTime.QuadPart; end; procedure get_thread_cputime(time:PInt64); var k:KERNEL_USER_TIMES; R:DWORD; begin k:=Default(KERNEL_USER_TIMES); R:=NtQueryInformationThread(NtCurrentThread, ThreadTimes, @k, SizeOf(KERNEL_USER_TIMES), nil); Assert(R=0,'get_thread_cputime'); unittime(@k.ExitTime.QuadPart); time^:=k.ExitTime.QuadPart-k.CreateTime.QuadPart; end; procedure gettimezone(z:p_timezone); var TZInfo:TTimeZoneInformation; tzi:DWORD; begin if (z<>nil) then begin tzi:=GetTimeZoneInformation(@TZInfo); if (tzi<>TIME_ZONE_ID_INVALID) then begin z^.tz_minuteswest:=TZInfo.Bias; z^.tz_dsttime :=ord(tzi=TIME_ZONE_ID_DAYLIGHT); end else begin z^.tz_minuteswest:=0; z^.tz_dsttime :=0; end; end; end; procedure getadjtime(tv:p_timeval); var STA:SYSTEM_QUERY_TIME_ADJUST_INFORMATION; R:DWORD; begin tv^:=Default(timeval); STA:=Default(SYSTEM_QUERY_TIME_ADJUST_INFORMATION); R:=NtQuerySystemInformation(SystemTimeAdjustmentInformation,@STA,SizeOf(STA),nil); if (R<>0) then Exit; if not Boolean(STA.Enable) then begin tv^.tv_sec :=(STA.TimeAdjustment div UNIT_PER_SEC); tv^.tv_usec:=(STA.TimeAdjustment mod UNIT_PER_SEC) div UNIT_PER_USEC; end; end; function kern_clock_gettime_unit(clock_id:Integer;time:PInt64):Integer; var user,syst:Int64; begin Result:=0; case clock_id of CLOCK_REALTIME, CLOCK_REALTIME_PRECISE, CLOCK_REALTIME_FAST: //getnanotime begin //nanotime unittime(@user); user:=user-DELTA_EPOCH_IN_UNIT; time^:=user; end; CLOCK_VIRTUAL: begin calcru_proc(@user,@syst); time^:=user; end; CLOCK_PROF: begin calcru_proc(@user,@syst); time^:=user+syst; end; CLOCK_MONOTONIC, CLOCK_MONOTONIC_PRECISE, CLOCK_MONOTONIC_FAST, CLOCK_UPTIME, CLOCK_UPTIME_PRECISE, CLOCK_UPTIME_FAST: begin //nanouptime + acpi_time_sleep time^:=md_rdtsc_unit; end; CLOCK_EXT_NETWORK, CLOCK_EXT_RAW_NETWORK: begin //nanouptime + SCE_REGMGR_ENT_KEY_DATE_rtc_net/SCE_REGMGR_ENT_KEY_DATE_rtc_net_dbg time^:=md_rdtsc_unit; end; CLOCK_SECOND: begin unittime(@user); user:=user-DELTA_EPOCH_IN_UNIT; user:=user-(user mod UNIT_PER_SEC); time^:=user; end; CLOCK_PROCTIME: begin //nanouptime - resume_time get_process_cputime(time); end; CLOCK_THREAD_CPUTIME_ID: begin get_thread_cputime(time); end; CLOCK_EXT_DEBUG_NETWORK: begin //sceSblACMgrIsSystemUcred | is_SecureWebProcess //nanouptime + SCE_REGMGR_ENT_KEY_DATE_rtc_net_dbg Exit(EINVAL); end; CLOCK_EXT_AD_NETWORK: begin //(sceSblACMgrIsSystemUcred | is_SecureWebProcess) & sceSblRcMgrIsAllowAdClock //nanouptime + SCE_REGMGR_ENT_KEY_DATE_rtc_net_dbg Exit(EINVAL); end; CLOCK_EXT_BOOT_TIME: begin //sceSblACMgrIsSystemUcred //get_emctimer_dev Exit(EINVAL); end; else Result:=EINVAL; end; end; function kern_clock_gettime(clock_id:Integer;tp:p_timespec):Integer; var time:Int64; begin time:=0; Result:=kern_clock_gettime_unit(clock_id,@time); if (Result=0) then begin tp^.tv_sec :=(time div UNIT_PER_SEC); tp^.tv_nsec:=(time mod UNIT_PER_SEC)*NSEC_PER_UNIT; end; end; function kern_clock_getres(clock_id:Integer;tp:p_timespec):Integer; begin Result:=0; case clock_id of CLOCK_REALTIME, CLOCK_VIRTUAL, CLOCK_PROF, CLOCK_MONOTONIC, CLOCK_UPTIME, CLOCK_UPTIME_PRECISE, CLOCK_UPTIME_FAST, CLOCK_REALTIME_PRECISE, CLOCK_REALTIME_FAST, CLOCK_MONOTONIC_PRECISE, CLOCK_MONOTONIC_FAST, CLOCK_THREAD_CPUTIME_ID, CLOCK_PROCTIME, CLOCK_EXT_NETWORK, CLOCK_EXT_DEBUG_NETWORK, CLOCK_EXT_AD_NETWORK, CLOCK_EXT_RAW_NETWORK: begin tp^.tv_sec :=0; tp^.tv_nsec:=80; end; CLOCK_SECOND: begin tp^.tv_sec :=1; tp^.tv_nsec:=0; end; else Result:=EINVAL; end; end; end.