using ARMeilleure.State; using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Memory.Tracking; using System; namespace Ryujinx.Cpu.AppleHv { class HvExecutionContext : IExecutionContext { /// public ulong Pc => _impl.ElrEl1; /// public long TpidrEl0 { get => _impl.TpidrEl0; set => _impl.TpidrEl0 = value; } /// public long TpidrroEl0 { get => _impl.TpidrroEl0; set => _impl.TpidrroEl0 = value; } /// public uint Pstate { get => _impl.Pstate; set => _impl.Pstate = value; } /// public uint Fpcr { get => _impl.Fpcr; set => _impl.Fpcr = value; } /// public uint Fpsr { get => _impl.Fpsr; set => _impl.Fpsr = value; } /// public bool IsAarch32 { get => false; set { if (value) { throw new NotSupportedException(); } } } /// 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; } /// public ulong GetX(int index) => _impl.GetX(index); /// public void SetX(int index, ulong value) => _impl.SetX(index, value); /// public V128 GetV(int index) => _impl.GetV(index); /// 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); } /// public void RequestInterrupt() { _impl.RequestInterrupt(); } /// 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() { } } }