diff options
Diffstat (limited to 'Ryujinx.Cpu/AppleHv/HvExecutionContext.cs')
-rw-r--r-- | Ryujinx.Cpu/AppleHv/HvExecutionContext.cs | 284 |
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 |