Просмотр исходного кода

Implement SvcWaitForAddress 0x34 (#289)

* Implement SvcWaitForAddress 0x34

Currently needed by Sonic Mania Plus

* Fix mistake

* read-decrement-write locked
Ac_K 7 лет назад
Родитель
Сommit
8b685b12f0

+ 4 - 2
Ryujinx.HLE/OsHle/Handles/KThread.cs

@@ -9,10 +9,12 @@ namespace Ryujinx.HLE.OsHle.Handles
 
 
         public int CoreMask { get; set; }
         public int CoreMask { get; set; }
 
 
-        public long MutexAddress   { get; set; }
-        public long CondVarAddress { get; set; }
+        public long MutexAddress       { get; set; }
+        public long CondVarAddress     { get; set; }
+        public long ArbiterWaitAddress { get; set; }
 
 
         public bool CondVarSignaled { get; set; }
         public bool CondVarSignaled { get; set; }
+        public bool ArbiterSignaled { get; set; }
 
 
         private Process Process;
         private Process Process;
 
 

+ 112 - 0
Ryujinx.HLE/OsHle/Kernel/AddressArbiter.cs

@@ -0,0 +1,112 @@
+using ChocolArm64.Memory;
+using ChocolArm64.State;
+using Ryujinx.HLE.OsHle.Handles;
+
+using static Ryujinx.HLE.OsHle.ErrorCode;
+
+namespace Ryujinx.HLE.OsHle.Kernel
+{
+    static class AddressArbiter
+    {
+        static ulong WaitForAddress(Process Process, AThreadState ThreadState, long Address, ulong Timeout)
+        {
+            KThread CurrentThread = Process.GetThread(ThreadState.Tpidr);
+
+            Process.Scheduler.SetReschedule(CurrentThread.ProcessorId);
+
+            CurrentThread.ArbiterWaitAddress = Address;
+            CurrentThread.ArbiterSignaled    = false;
+
+            Process.Scheduler.EnterWait(CurrentThread, NsTimeConverter.GetTimeMs(Timeout));
+
+            if (!CurrentThread.ArbiterSignaled)
+            {
+                return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+            }
+
+            return 0;
+        }
+
+        public static ulong WaitForAddressIfLessThan(Process      Process, 
+                                                     AThreadState ThreadState, 
+                                                     AMemory      Memory, 
+                                                     long         Address, 
+                                                     int          Value, 
+                                                     ulong        Timeout, 
+                                                     bool         ShouldDecrement)
+        {
+            Memory.SetExclusive(ThreadState, Address);
+
+            int CurrentValue = Memory.ReadInt32(Address);
+
+            while (true)
+            {
+                if (Memory.TestExclusive(ThreadState, Address))
+                {
+                    if (CurrentValue < Value)
+                    {
+                        if (ShouldDecrement)
+                        {
+                            Memory.WriteInt32(Address, CurrentValue - 1);
+                        }
+
+                        Memory.ClearExclusiveForStore(ThreadState);
+                    }
+                    else
+                    {
+                        Memory.ClearExclusiveForStore(ThreadState);
+
+                        return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+                    }
+
+                    break;
+                }
+
+                Memory.SetExclusive(ThreadState, Address);
+
+                CurrentValue = Memory.ReadInt32(Address);
+            }
+
+            if (Timeout == 0)
+            {
+                return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+            }
+
+            return WaitForAddress(Process, ThreadState, Address, Timeout);
+        }
+
+        public static ulong WaitForAddressIfEqual(Process      Process, 
+                                                  AThreadState ThreadState, 
+                                                  AMemory      Memory, 
+                                                  long         Address, 
+                                                  int          Value, 
+                                                  ulong        Timeout)
+        {
+            if (Memory.ReadInt32(Address) != Value)
+            {
+                return MakeError(ErrorModule.Kernel, KernelErr.InvalidState);
+            }
+
+            if (Timeout == 0)
+            {
+                return MakeError(ErrorModule.Kernel, KernelErr.Timeout);
+            }
+
+            return WaitForAddress(Process, ThreadState, Address, Timeout);
+        }
+    }
+
+    enum ArbitrationType : int
+    {
+        WaitIfLessThan,
+        DecrementAndWaitIfLessThan,
+        WaitIfEqual
+    }
+
+    enum SignalType : int
+    {
+        Signal,
+        IncrementAndSignalIfEqual,
+        ModifyByWaitingCountAndSignalIfEqual
+    }
+}

+ 1 - 1
Ryujinx.HLE/OsHle/Kernel/KernelErr.cs

@@ -12,7 +12,7 @@ namespace Ryujinx.HLE.OsHle.Kernel
         public const int Timeout          = 117;
         public const int Timeout          = 117;
         public const int Canceled         = 118;
         public const int Canceled         = 118;
         public const int CountOutOfRange  = 119;
         public const int CountOutOfRange  = 119;
-        public const int InvalidInfo      = 120;
+        public const int InvalidEnumValue = 120;
         public const int InvalidThread    = 122;
         public const int InvalidThread    = 122;
         public const int InvalidState     = 125;
         public const int InvalidState     = 125;
     }
     }

+ 2 - 1
Ryujinx.HLE/OsHle/Kernel/SvcHandler.cs

@@ -73,7 +73,8 @@ namespace Ryujinx.HLE.OsHle.Kernel
                 { 0x2c, SvcMapPhysicalMemory             },
                 { 0x2c, SvcMapPhysicalMemory             },
                 { 0x2d, SvcUnmapPhysicalMemory           },
                 { 0x2d, SvcUnmapPhysicalMemory           },
                 { 0x32, SvcSetThreadActivity             },
                 { 0x32, SvcSetThreadActivity             },
-                { 0x33, SvcGetThreadContext3             }
+                { 0x33, SvcGetThreadContext3             },
+                { 0x34, SvcWaitForAddress                }
             };
             };
 
 
             this.Ns      = Ns;
             this.Ns      = Ns;

+ 1 - 1
Ryujinx.HLE/OsHle/Kernel/SvcSystem.cs

@@ -294,7 +294,7 @@ namespace Ryujinx.HLE.OsHle.Kernel
                 InfoType == 19 ||
                 InfoType == 19 ||
                 InfoType == 20)
                 InfoType == 20)
             {
             {
-                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidInfo);
+                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
 
 
                 return;
                 return;
             }
             }

+ 51 - 0
Ryujinx.HLE/OsHle/Kernel/SvcThreadSync.cs

@@ -197,6 +197,57 @@ namespace Ryujinx.HLE.OsHle.Kernel
             Process.Scheduler.EnterWait(CurrThread);
             Process.Scheduler.EnterWait(CurrThread);
         }
         }
 
 
+        private void SvcWaitForAddress(AThreadState ThreadState)
+        {
+            long            Address = (long)ThreadState.X0;
+            ArbitrationType Type    = (ArbitrationType)ThreadState.X1;
+            int             Value   = (int)ThreadState.X2;
+            ulong           Timeout = ThreadState.X3;
+
+            Ns.Log.PrintDebug(LogClass.KernelSvc,
+                "Address = "         + Address.ToString("x16") + ", " +
+                "ArbitrationType = " + Type   .ToString()      + ", " +
+                "Value = "           + Value  .ToString("x8")  + ", " +
+                "Timeout = "         + Timeout.ToString("x16"));
+
+            if (IsPointingInsideKernel(Address))
+            {
+                Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid address 0x{Address:x16}!");
+
+                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAddress);
+
+                return;
+            }
+
+            if (IsWordAddressUnaligned(Address))
+            {
+                Ns.Log.PrintWarning(LogClass.KernelSvc, $"Unaligned address 0x{Address:x16}!");
+
+                ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidAlignment);
+
+                return;
+            }
+
+            switch (Type)
+            {
+                case ArbitrationType.WaitIfLessThan:
+                    ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, false);
+                    break;
+
+                case ArbitrationType.DecrementAndWaitIfLessThan:
+                    ThreadState.X0 = AddressArbiter.WaitForAddressIfLessThan(Process, ThreadState, Memory, Address, Value, Timeout, true);
+                    break;
+
+                case ArbitrationType.WaitIfEqual:
+                    ThreadState.X0 = AddressArbiter.WaitForAddressIfEqual(Process, ThreadState, Memory, Address, Value, Timeout);
+                    break;
+
+                default:
+                    ThreadState.X0 = MakeError(ErrorModule.Kernel, KernelErr.InvalidEnumValue);
+                    break;
+            }
+        }
+
         private void MutexUnlock(KThread CurrThread, long MutexAddress)
         private void MutexUnlock(KThread CurrThread, long MutexAddress)
         {
         {
             lock (Process.ThreadSyncLock)
             lock (Process.ThreadSyncLock)