aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Cpu/AppleHv/HvExecutionContext.cs')
-rw-r--r--Ryujinx.Cpu/AppleHv/HvExecutionContext.cs284
1 files changed, 284 insertions, 0 deletions
diff --git a/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs b/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
new file mode 100644
index 00000000..dc1f6f6d
--- /dev/null
+++ b/Ryujinx.Cpu/AppleHv/HvExecutionContext.cs
@@ -0,0 +1,284 @@
+using ARMeilleure.State;
+using Ryujinx.Cpu.AppleHv.Arm;
+using Ryujinx.Memory.Tracking;
+using System;
+
+namespace Ryujinx.Cpu.AppleHv
+{
+ class HvExecutionContext : IExecutionContext
+ {
+ /// <inheritdoc/>
+ public ulong Pc => _impl.ElrEl1;
+
+ /// <inheritdoc/>
+ public long TpidrEl0
+ {
+ get => _impl.TpidrEl0;
+ set => _impl.TpidrEl0 = value;
+ }
+
+ /// <inheritdoc/>
+ public long TpidrroEl0
+ {
+ get => _impl.TpidrroEl0;
+ set => _impl.TpidrroEl0 = value;
+ }
+
+ /// <inheritdoc/>
+ public uint Pstate
+ {
+ get => _impl.Pstate;
+ set => _impl.Pstate = value;
+ }
+
+ /// <inheritdoc/>
+ public uint Fpcr
+ {
+ get => _impl.Fpcr;
+ set => _impl.Fpcr = value;
+ }
+
+ /// <inheritdoc/>
+ public uint Fpsr
+ {
+ get => _impl.Fpsr;
+ set => _impl.Fpsr = value;
+ }
+
+ /// <inheritdoc/>
+ public bool IsAarch32
+ {
+ get => false;
+ set
+ {
+ if (value)
+ {
+ throw new NotSupportedException();
+ }
+ }
+ }
+
+ /// <inheritdoc/>
+ public bool Running { get; private set; }
+
+ private readonly ICounter _counter;
+ private readonly IHvExecutionContext _shadowContext;
+ private IHvExecutionContext _impl;
+
+ private readonly ExceptionCallbacks _exceptionCallbacks;
+
+ public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
+ {
+ _counter = counter;
+ _shadowContext = new HvExecutionContextShadow();
+ _impl = _shadowContext;
+ _exceptionCallbacks = exceptionCallbacks;
+ Running = true;
+ }
+
+ /// <inheritdoc/>
+ public ulong GetX(int index) => _impl.GetX(index);
+
+ /// <inheritdoc/>
+ public void SetX(int index, ulong value) => _impl.SetX(index, value);
+
+ /// <inheritdoc/>
+ public V128 GetV(int index) => _impl.GetV(index);
+
+ /// <inheritdoc/>
+ public void SetV(int index, V128 value) => _impl.SetV(index, value);
+
+ private void InterruptHandler()
+ {
+ _exceptionCallbacks.InterruptCallback?.Invoke(this);
+ }
+
+ private void BreakHandler(ulong address, int imm)
+ {
+ _exceptionCallbacks.BreakCallback?.Invoke(this, address, imm);
+ }
+
+ private void SupervisorCallHandler(ulong address, int imm)
+ {
+ _exceptionCallbacks.SupervisorCallback?.Invoke(this, address, imm);
+ }
+
+ private void UndefinedHandler(ulong address, int opCode)
+ {
+ _exceptionCallbacks.UndefinedCallback?.Invoke(this, address, opCode);
+ }
+
+ /// <inheritdoc/>
+ public void RequestInterrupt()
+ {
+ _impl.RequestInterrupt();
+ }
+
+ /// <inheritdoc/>
+ public void StopRunning()
+ {
+ Running = false;
+ RequestInterrupt();
+ }
+
+ public unsafe void Execute(HvMemoryManager memoryManager, ulong address)
+ {
+ HvVcpu vcpu = HvVcpuPool.Instance.Create(memoryManager.AddressSpace, _shadowContext, SwapContext);
+
+ HvApi.hv_vcpu_set_reg(vcpu.Handle, hv_reg_t.HV_REG_PC, address).ThrowOnError();
+
+ while (Running)
+ {
+ HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
+
+ uint reason = vcpu.ExitInfo->reason;
+
+ if (reason == 1)
+ {
+ uint hvEsr = (uint)vcpu.ExitInfo->exception.syndrome;
+ ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
+
+ if (hvEc != ExceptionClass.HvcAarch64)
+ {
+ throw new Exception($"Unhandled exception from guest kernel with ESR 0x{hvEsr:X} ({hvEc}).");
+ }
+
+ address = SynchronousException(memoryManager, ref vcpu);
+ HvApi.hv_vcpu_set_reg(vcpu.Handle, hv_reg_t.HV_REG_PC, address).ThrowOnError();
+ }
+ else if (reason == 0)
+ {
+ if (_impl.GetAndClearInterruptRequested())
+ {
+ ReturnToPool(vcpu);
+ InterruptHandler();
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ }
+ }
+ else
+ {
+ throw new Exception($"Unhandled exit reason {reason}.");
+ }
+ }
+
+ HvVcpuPool.Instance.Destroy(vcpu, SwapContext);
+ }
+
+ private ulong SynchronousException(HvMemoryManager memoryManager, ref HvVcpu vcpu)
+ {
+ ulong vcpuHandle = vcpu.Handle;
+
+ HvApi.hv_vcpu_get_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, out ulong elr).ThrowOnError();
+ HvApi.hv_vcpu_get_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ESR_EL1, out ulong esr).ThrowOnError();
+
+ ExceptionClass ec = (ExceptionClass)((uint)esr >> 26);
+
+ switch (ec)
+ {
+ case ExceptionClass.DataAbortLowerEl:
+ DataAbort(memoryManager.Tracking, vcpuHandle, (uint)esr);
+ break;
+ case ExceptionClass.TrappedMsrMrsSystem:
+ InstructionTrap((uint)esr);
+ HvApi.hv_vcpu_set_sys_reg(vcpuHandle, hv_sys_reg_t.HV_SYS_REG_ELR_EL1, elr + 4UL).ThrowOnError();
+ break;
+ case ExceptionClass.SvcAarch64:
+ ReturnToPool(vcpu);
+ ushort id = (ushort)esr;
+ SupervisorCallHandler(elr - 4UL, id);
+ vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
+ break;
+ default:
+ throw new Exception($"Unhandled guest exception {ec}.");
+ }
+
+ // Make sure we will continue running at EL0.
+ if (memoryManager.AddressSpace.GetAndClearUserTlbInvalidationPending())
+ {
+ // TODO: Invalidate only the range that was modified?
+ return HvAddressSpace.KernelRegionTlbiEretAddress;
+ }
+ else
+ {
+ return HvAddressSpace.KernelRegionEretAddress;
+ }
+ }
+
+ private void DataAbort(MemoryTracking tracking, ulong vcpu, uint esr)
+ {
+ bool write = (esr & (1u << 6)) != 0;
+ bool farValid = (esr & (1u << 10)) == 0;
+ int accessSizeLog2 = (int)((esr >> 22) & 3);
+
+ if (farValid)
+ {
+ HvApi.hv_vcpu_get_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_FAR_EL1, out ulong far).ThrowOnError();
+
+ ulong size = 1UL << accessSizeLog2;
+
+ if (!tracking.VirtualMemoryEvent(far, size, write))
+ {
+ string rw = write ? "write" : "read";
+ throw new Exception($"Unhandled invalid memory access at VA 0x{far:X} with size 0x{size:X} ({rw}).");
+ }
+ }
+ else
+ {
+ throw new Exception($"Unhandled invalid memory access at unknown VA with ESR 0x{esr:X}.");
+ }
+ }
+
+ private void InstructionTrap(uint esr)
+ {
+ bool read = (esr & 1) != 0;
+ uint rt = (esr >> 5) & 0x1f;
+
+ if (read)
+ {
+ // Op0 Op2 Op1 CRn 00000 CRm
+ switch ((esr >> 1) & 0x1ffe0f)
+ {
+ case 0b11_000_011_1110_00000_0000: // CNTFRQ_EL0
+ WriteRt(rt, _counter.Frequency);
+ break;
+ case 0b11_001_011_1110_00000_0000: // CNTPCT_EL0
+ WriteRt(rt, _counter.Counter);
+ break;
+ default:
+ throw new Exception($"Unhandled system register read with ESR 0x{esr:X}");
+ }
+ }
+ else
+ {
+ throw new Exception($"Unhandled system register write with ESR 0x{esr:X}");
+ }
+ }
+
+ private void WriteRt(uint rt, ulong value)
+ {
+ if (rt < 31)
+ {
+ SetX((int)rt, value);
+ }
+ }
+
+ private void ReturnToPool(HvVcpu vcpu)
+ {
+ HvVcpuPool.Instance.Return(vcpu, SwapContext);
+ }
+
+ private HvVcpu RentFromPool(HvAddressSpace addressSpace, HvVcpu vcpu)
+ {
+ return HvVcpuPool.Instance.Rent(addressSpace, _shadowContext, vcpu, SwapContext);
+ }
+
+ private void SwapContext(IHvExecutionContext newContext)
+ {
+ _impl = newContext;
+ }
+
+ public void Dispose()
+ {
+ }
+ }
+} \ No newline at end of file