From ddc9ae2a8380668273c21a75b11b833be76eebed Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Mon, 25 Sep 2023 20:18:32 -0300
Subject: 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 ++
 src/Ryujinx.Cpu/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 -------
 src/Ryujinx.Cpu/AppleHv/HvExecutionContextVcpu.cs  | 16 +++--------
 src/Ryujinx.Cpu/AppleHv/HvMemoryBlockAllocation.cs |  2 ++
 src/Ryujinx.Cpu/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 ++
 src/Ryujinx.Cpu/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

(limited to 'src')

diff --git a/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs b/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs
index 4785a3f0..eb7c0ef0 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 87633430..7754431f 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 4f748498..e6e08111 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 a58f8359..2c4ff2b6 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 2967857f..c3c1a448 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 2c9afdc4..fc2b76d1 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
     {
         /// <inheritdoc/>
@@ -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
         /// <inheritdoc/>
         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;
         }
 
         /// <inheritdoc/>
@@ -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 78ffcbe4..6ce8e180 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 d9ad637f..bb232940 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 3c3f087a..855d313c 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 ac184cb9..4e3723d5 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<HvMemoryBlockAllocator.Block>
     {
         public class Block : PrivateMemoryAllocator.Block
diff --git a/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/src/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
index 01c685d4..d5ce817a 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
     /// <summary>
     /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
     /// </summary>
+    [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 9c2cc0ff..ee91c478 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 fe01dce3..2edcd7e4 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 1f15022f..c4f10753 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 7990ab72..54b73acc 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 00000000..85bc7717
--- /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);
+    }
+}
-- 
cgit v1.2.3-70-g09d2