aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Cpu/AppleHv/HvAddressSpace.cs
blob: eb7c0ef086fa0fb6f05a0b2afd51d23a368b8465 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory;
using System;
using System.Runtime.Versioning;

namespace Ryujinx.Cpu.AppleHv
{
    [SupportedOSPlatform("macos")]
    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 _backingSize;

        private readonly HvAddressSpaceRange _userRange;
        private readonly HvAddressSpaceRange _kernelRange;

        private readonly MemoryBlock _kernelCodeBlock;

        public HvAddressSpace(MemoryBlock backingMemory, ulong asSize)
        {
            (_asBase, var ipaAllocator) = HvVm.CreateAddressSpace(backingMemory);
            _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, HvMemoryFlags.Read | HvMemoryFlags.Exec).ThrowOnError();

            _kernelRange.Map(KernelRegionCodeOffset, kernelCodePa, KernelRegionCodeSize, ApFlags.UserNoneKernelReadExecute);
        }

        public void InitializeMmu(ulong vcpu)
        {
            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.VBAR_EL1, KernelRegionBase + KernelRegionCodeOffset);

            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TTBR0_EL1, _userRange.GetIpaBase());
            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TTBR1_EL1, _kernelRange.GetIpaBase());
            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.MAIR_EL1, 0xffUL);
            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.TCR_EL1, 0x00000011B5193519UL);
            HvApi.hv_vcpu_set_sys_reg(vcpu, HvSysReg.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);
        }
    }
}