Ryujinx/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs
gdkchan a53cfdab78
Initial Apple Hypervisor based CPU emulation (#4332)
* Initial Apple Hypervisor based CPU emulation implementation

* Add UseHypervisor Setting

* Add basic MacOS support to Avalonia

* Fix initialization

* Fix GTK build

* Fix/silence warnings

* Change exceptions to asserts on HvAddressSpaceRange

* Replace DllImport with LibraryImport

* Fix LibraryImport

* Remove unneeded usings

* Revert outdated change

* Set DiskCacheLoadState when using hypervisor too

* Fix HvExecutionContext PC value

* Address PR feedback

* Use existing entitlements.xml file on distribution folder

---------

Co-authored-by: riperiperi <rhy3756547@hotmail.com>
2023-01-29 08:37:52 -03:00

129 lines
5 KiB
C#

using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory;
using System;
namespace Ryujinx.Cpu.AppleHv
{
class HvAddressSpace : IDisposable
{
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));
private const ulong KernelRegionCodeOffset = 0UL;
private const ulong KernelRegionCodeSize = 0x2000UL;
private const ulong KernelRegionTlbiEretOffset = KernelRegionCodeOffset + 0x1000UL;
private const ulong KernelRegionEretOffset = KernelRegionTlbiEretOffset + 4UL;
public const ulong KernelRegionEretAddress = KernelRegionBase + KernelRegionEretOffset;
public const ulong KernelRegionTlbiEretAddress = KernelRegionBase + KernelRegionTlbiEretOffset;
private const ulong AllocationGranule = 1UL << 14;
private readonly ulong _asBase;
private readonly ulong _asSize;
private readonly ulong _backingSize;
private readonly HvAddressSpaceRange _userRange;
private readonly HvAddressSpaceRange _kernelRange;
private MemoryBlock _kernelCodeBlock;
public HvAddressSpace(MemoryBlock backingMemory, ulong asSize)
{
(_asBase, var ipaAllocator) = HvVm.CreateAddressSpace(backingMemory);
_asSize = asSize;
_backingSize = backingMemory.Size;
_userRange = new HvAddressSpaceRange(ipaAllocator);
_kernelRange = new HvAddressSpaceRange(ipaAllocator);
_kernelCodeBlock = new MemoryBlock(AllocationGranule);
InitializeKernelCode(ipaAllocator);
}
private void InitializeKernelCode(HvIpaAllocator ipaAllocator)
{
// Write exception handlers.
for (ulong offset = 0; offset < 0x800; offset += 0x80)
{
// Offsets:
// 0x0: Synchronous
// 0x80: IRQ
// 0x100: FIQ
// 0x180: SError
_kernelCodeBlock.Write(KernelRegionCodeOffset + offset, 0xD41FFFE2u); // HVC #0xFFFF
_kernelCodeBlock.Write(KernelRegionCodeOffset + offset + 4, 0xD69F03E0u); // ERET
}
_kernelCodeBlock.Write(KernelRegionTlbiEretOffset, 0xD508831Fu); // TLBI VMALLE1IS
_kernelCodeBlock.Write(KernelRegionEretOffset, 0xD69F03E0u); // ERET
ulong kernelCodePa = ipaAllocator.Allocate(AllocationGranule);
HvApi.hv_vm_map((ulong)_kernelCodeBlock.Pointer, kernelCodePa, AllocationGranule, hv_memory_flags_t.HV_MEMORY_READ | hv_memory_flags_t.HV_MEMORY_EXEC).ThrowOnError();
_kernelRange.Map(KernelRegionCodeOffset, kernelCodePa, KernelRegionCodeSize, ApFlags.UserNoneKernelReadExecute);
}
public void InitializeMmu(ulong vcpu)
{
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_VBAR_EL1, KernelRegionBase + KernelRegionCodeOffset);
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TTBR0_EL1, _userRange.GetIpaBase());
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TTBR1_EL1, _kernelRange.GetIpaBase());
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_MAIR_EL1, 0xffUL);
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_TCR_EL1, 0x00000011B5193519UL);
HvApi.hv_vcpu_set_sys_reg(vcpu, hv_sys_reg_t.HV_SYS_REG_SCTLR_EL1, 0x0000000034D5D925UL);
}
public bool GetAndClearUserTlbInvalidationPending()
{
return _userRange.GetAndClearTlbInvalidationPending();
}
public void MapUser(ulong va, ulong pa, ulong size, MemoryPermission permission)
{
pa += _asBase;
lock (_userRange)
{
_userRange.Map(va, pa, size, GetApFlags(permission));
}
}
public void UnmapUser(ulong va, ulong size)
{
lock (_userRange)
{
_userRange.Unmap(va, size);
}
}
public void ReprotectUser(ulong va, ulong size, MemoryPermission permission)
{
lock (_userRange)
{
_userRange.Reprotect(va, size, GetApFlags(permission));
}
}
private static ApFlags GetApFlags(MemoryPermission permission)
{
return permission switch
{
MemoryPermission.None => ApFlags.UserNoneKernelRead,
MemoryPermission.Execute => ApFlags.UserExecuteKernelRead,
MemoryPermission.Read => ApFlags.UserReadKernelRead,
MemoryPermission.ReadAndWrite => ApFlags.UserReadWriteKernelReadWrite,
MemoryPermission.ReadAndExecute => ApFlags.UserReadExecuteKernelRead,
MemoryPermission.ReadWriteExecute => ApFlags.UserReadWriteExecuteKernelReadWrite,
_ => throw new ArgumentException($"Permission \"{permission}\" is invalid.")
};
}
public void Dispose()
{
_userRange.Dispose();
_kernelRange.Dispose();
HvVm.DestroyAddressSpace(_asBase, _backingSize);
}
}
}