From ddc9ae2a8380668273c21a75b11b833be76eebed Mon Sep 17 00:00:00 2001 From: gdkchan Date: Mon, 25 Sep 2023 20:18:32 -0300 Subject: [PATCH] Add VTimer as alternative interrupt method on Apple Hypervisor (#5663) * Add VTimer as alternative interrupt method on Apple Hypervisor * Fix naming violations on TimeApi * Fix timer interval (was 16us rather than 16ms) * Fix delta ticks calculation * Missing ThrowOnError call * Add SupportedOSPlatform attribute on AppleHv classes --- src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs | 2 ++ .../AppleHv/HvAddressSpaceRange.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvApi.cs | 12 ++++++- src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvEngine.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 31 ++++++++++++++++--- .../AppleHv/HvExecutionContextShadow.cs | 9 ------ .../AppleHv/HvExecutionContextVcpu.cs | 16 +++------- .../AppleHv/HvMemoryBlockAllocation.cs | 2 ++ .../AppleHv/HvMemoryBlockAllocator.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs | 2 ++ src/Ryujinx.Cpu/AppleHv/HvVcpu.cs | 31 +++++++++++++++++++ src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs | 4 +++ src/Ryujinx.Cpu/AppleHv/HvVm.cs | 2 ++ .../AppleHv/IHvExecutionContext.cs | 5 +-- src/Ryujinx.Cpu/AppleHv/TimeApi.cs | 21 +++++++++++++ 16 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 src/Ryujinx.Cpu/AppleHv/TimeApi.cs diff --git a/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs b/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs index 4785a3f05e..eb7c0ef086 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs @@ -1,9 +1,11 @@ using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Memory; using System; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvAddressSpace : IDisposable { private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39)); diff --git a/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs b/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs index 876334307c..7754431faa 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvAddressSpaceRange.cs @@ -2,10 +2,12 @@ using Ryujinx.Cpu.AppleHv.Arm; using System; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvAddressSpaceRange : IDisposable { private const ulong AllocationGranule = 1UL << 14; diff --git a/src/Ryujinx.Cpu/AppleHv/HvApi.cs b/src/Ryujinx.Cpu/AppleHv/HvApi.cs index 4f74849816..e6e08111f3 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvApi.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvApi.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { @@ -12,10 +13,18 @@ namespace Ryujinx.Cpu.AppleHv #pragma warning restore CS0649 } + enum HvExitReason : uint + { + Canceled, + Exception, + VTimerActivated, + Unknown, + } + struct HvVcpuExit { #pragma warning disable CS0649 // Field is never assigned to - public uint Reason; + public HvExitReason Reason; public HvVcpuExitException Exception; #pragma warning restore CS0649 } @@ -255,6 +264,7 @@ namespace Ryujinx.Cpu.AppleHv } } + [SupportedOSPlatform("macos")] static partial class HvApi { public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor"; diff --git a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs index a58f835913..2c4ff2b64e 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvCpuContext.cs @@ -1,7 +1,9 @@ using ARMeilleure.Memory; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvCpuContext : ICpuContext { private readonly ITickSource _tickSource; diff --git a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs index 2967857f95..c3c1a4484f 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvEngine.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvEngine.cs @@ -1,7 +1,9 @@ using ARMeilleure.Memory; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] public class HvEngine : ICpuEngine { private readonly ITickSource _tickSource; diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs index 2c9afdc4ed..fc2b76d151 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs @@ -2,9 +2,12 @@ using ARMeilleure.State; using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Memory.Tracking; using System; +using System.Runtime.Versioning; +using System.Threading; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvExecutionContext : IExecutionContext { /// @@ -67,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv private readonly ExceptionCallbacks _exceptionCallbacks; + private int _interruptRequested; + public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks) { _counter = counter; @@ -111,7 +116,15 @@ namespace Ryujinx.Cpu.AppleHv /// public void RequestInterrupt() { - _impl.RequestInterrupt(); + if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl) + { + impl.RequestInterrupt(); + } + } + + private bool GetAndClearInterruptRequested() + { + return Interlocked.Exchange(ref _interruptRequested, 0) != 0; } /// @@ -131,9 +144,9 @@ namespace Ryujinx.Cpu.AppleHv { HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); - uint reason = vcpu.ExitInfo->Reason; + HvExitReason reason = vcpu.ExitInfo->Reason; - if (reason == 1) + if (reason == HvExitReason.Exception) { uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome; ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26); @@ -146,14 +159,22 @@ namespace Ryujinx.Cpu.AppleHv address = SynchronousException(memoryManager, ref vcpu); HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); } - else if (reason == 0) + else if (reason == HvExitReason.Canceled || reason == HvExitReason.VTimerActivated) { - if (_impl.GetAndClearInterruptRequested()) + if (GetAndClearInterruptRequested()) { ReturnToPool(vcpu); InterruptHandler(); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); } + + if (reason == HvExitReason.VTimerActivated) + { + vcpu.EnableAndUpdateVTimer(); + + // Unmask VTimer interrupts. + HvApi.hv_vcpu_set_vtimer_mask(vcpu.Handle, false).ThrowOnError(); + } } else { diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs index 78ffcbe4b3..6ce8e18000 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextShadow.cs @@ -46,14 +46,5 @@ namespace Ryujinx.Cpu.AppleHv { _v[index] = value; } - - public void RequestInterrupt() - { - } - - public bool GetAndClearInterruptRequested() - { - return false; - } } } diff --git a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs index d9ad637f8c..bb232940de 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs @@ -2,10 +2,11 @@ using ARMeilleure.State; using Ryujinx.Memory; using System; using System.Runtime.InteropServices; -using System.Threading; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvExecutionContextVcpu : IHvExecutionContext { private static readonly MemoryBlock _setSimdFpRegFuncMem; @@ -135,7 +136,6 @@ namespace Ryujinx.Cpu.AppleHv } private readonly ulong _vcpu; - private int _interruptRequested; public HvExecutionContextVcpu(ulong vcpu) { @@ -181,16 +181,8 @@ namespace Ryujinx.Cpu.AppleHv public void RequestInterrupt() { - if (Interlocked.Exchange(ref _interruptRequested, 1) == 0) - { - ulong vcpu = _vcpu; - HvApi.hv_vcpus_exit(ref vcpu, 1); - } - } - - public bool GetAndClearInterruptRequested() - { - return Interlocked.Exchange(ref _interruptRequested, 0) != 0; + ulong vcpu = _vcpu; + HvApi.hv_vcpus_exit(ref vcpu, 1); } } } diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs index 3c3f087ab7..855d313c57 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs @@ -1,8 +1,10 @@ using Ryujinx.Memory; using System; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] readonly struct HvMemoryBlockAllocation : IDisposable { private readonly HvMemoryBlockAllocator _owner; diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs index ac184cb9a0..4e3723d554 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocator.cs @@ -1,7 +1,9 @@ using Ryujinx.Memory; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl { public class Block : PrivateMemoryAllocator.Block diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs index 01c685d4bd..d5ce817a41 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using System.Threading; namespace Ryujinx.Cpu.AppleHv @@ -14,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv /// /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// + [SupportedOSPlatform("macos")] public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock { public const int PageBits = 12; diff --git a/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs b/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs index 9c2cc0ff3a..ee91c478b0 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvVcpu.cs @@ -1,7 +1,15 @@ +using System.Diagnostics; +using System.Runtime.Versioning; + namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] unsafe class HvVcpu { + private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms + + private static ulong _interruptTimeDeltaTicks = 0; + public readonly ulong Handle; public readonly HvVcpuExit* ExitInfo; public readonly IHvExecutionContext ShadowContext; @@ -21,5 +29,28 @@ namespace Ryujinx.Cpu.AppleHv NativeContext = nativeContext; IsEphemeral = isEphemeral; } + + public void EnableAndUpdateVTimer() + { + // We need to ensure interrupts will be serviced, + // and for that we set up the VTime to trigger an interrupt at fixed intervals. + + ulong deltaTicks = _interruptTimeDeltaTicks; + + if (deltaTicks == 0) + { + // Calculate our time delta in ticks based on the current clock frequency. + + int result = TimeApi.mach_timebase_info(out var timeBaseInfo); + + Debug.Assert(result == 0); + + deltaTicks = ((InterruptIntervalNs * timeBaseInfo.Denom) + (timeBaseInfo.Numer - 1)) / timeBaseInfo.Numer; + _interruptTimeDeltaTicks = deltaTicks; + } + + HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CTL_EL0, 1).ThrowOnError(); + HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CVAL_EL0, TimeApi.mach_absolute_time() + deltaTicks).ThrowOnError(); + } } } diff --git a/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs b/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs index fe01dce3c3..2edcd7e4ef 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvVcpuPool.cs @@ -1,8 +1,10 @@ using System; +using System.Runtime.Versioning; using System.Threading; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] class HvVcpuPool { // Since there's a limit on the number of VCPUs we can create, @@ -81,6 +83,8 @@ namespace Ryujinx.Cpu.AppleHv HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral); + vcpu.EnableAndUpdateVTimer(); + return vcpu; } diff --git a/src/Ryujinx.Cpu/AppleHv/HvVm.cs b/src/Ryujinx.Cpu/AppleHv/HvVm.cs index 1f15022fd8..c4f107532e 100644 --- a/src/Ryujinx.Cpu/AppleHv/HvVm.cs +++ b/src/Ryujinx.Cpu/AppleHv/HvVm.cs @@ -1,8 +1,10 @@ using Ryujinx.Memory; using System; +using System.Runtime.Versioning; namespace Ryujinx.Cpu.AppleHv { + [SupportedOSPlatform("macos")] static class HvVm { // This alignment allows us to use larger blocks on the page table. diff --git a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs index 7990ab72aa..54b73acc63 100644 --- a/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs +++ b/src/Ryujinx.Cpu/AppleHv/IHvExecutionContext.cs @@ -2,7 +2,7 @@ using ARMeilleure.State; namespace Ryujinx.Cpu.AppleHv { - public interface IHvExecutionContext + interface IHvExecutionContext { ulong Pc { get; set; } ulong ElrEl1 { get; set; } @@ -39,8 +39,5 @@ namespace Ryujinx.Cpu.AppleHv SetV(i, context.GetV(i)); } } - - void RequestInterrupt(); - bool GetAndClearInterruptRequested(); } } diff --git a/src/Ryujinx.Cpu/AppleHv/TimeApi.cs b/src/Ryujinx.Cpu/AppleHv/TimeApi.cs new file mode 100644 index 0000000000..85bc771788 --- /dev/null +++ b/src/Ryujinx.Cpu/AppleHv/TimeApi.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Cpu.AppleHv +{ + struct MachTimebaseInfo + { + public uint Numer; + public uint Denom; + } + + [SupportedOSPlatform("macos")] + static partial class TimeApi + { + [LibraryImport("libc", SetLastError = true)] + public static partial ulong mach_absolute_time(); + + [LibraryImport("libc", SetLastError = true)] + public static partial int mach_timebase_info(out MachTimebaseInfo info); + } +}