/ src / Ryujinx.Cpu / AppleHv / HvAddressSpaceRange.cs
HvAddressSpaceRange.cs
  1  using Ryujinx.Cpu.AppleHv.Arm;
  2  using System;
  3  using System.Diagnostics;
  4  using System.Runtime.InteropServices;
  5  using System.Runtime.Versioning;
  6  using System.Threading;
  7  
  8  namespace Ryujinx.Cpu.AppleHv
  9  {
 10      [SupportedOSPlatform("macos")]
 11      class HvAddressSpaceRange : IDisposable
 12      {
 13          private const ulong AllocationGranule = 1UL << 14;
 14  
 15          private const ulong AttributesMask = (0x3ffUL << 2) | (0x3fffUL << 50);
 16  
 17          private const ulong BaseAttributes = (1UL << 10) | (3UL << 8); // Access flag set, inner shareable.
 18  
 19          private const int LevelBits = 9;
 20          private const int LevelCount = 1 << LevelBits;
 21          private const int LevelMask = LevelCount - 1;
 22          private const int PageBits = 12;
 23          private const int PageSize = 1 << PageBits;
 24          private const int PageMask = PageSize - 1;
 25          private const int AllLevelsMask = PageMask | (LevelMask << PageBits) | (LevelMask << (PageBits + LevelBits));
 26  
 27          private class PtLevel
 28          {
 29              public ulong Address => Allocation.Ipa + Allocation.Offset;
 30              public int EntriesCount;
 31              public readonly HvMemoryBlockAllocation Allocation;
 32              public readonly PtLevel[] Next;
 33  
 34              public PtLevel(HvMemoryBlockAllocator blockAllocator, int count, bool hasNext)
 35              {
 36                  ulong size = (ulong)count * sizeof(ulong);
 37                  Allocation = blockAllocator.Allocate(size, PageSize);
 38  
 39                  AsSpan().Clear();
 40  
 41                  if (hasNext)
 42                  {
 43                      Next = new PtLevel[count];
 44                  }
 45              }
 46  
 47              public Span<ulong> AsSpan()
 48              {
 49                  return MemoryMarshal.Cast<byte, ulong>(Allocation.Memory.GetSpan(Allocation.Offset, (int)Allocation.Size));
 50              }
 51          }
 52  
 53          private PtLevel _level0;
 54  
 55          private int _tlbInvalidationPending;
 56  
 57          private readonly HvMemoryBlockAllocator _blockAllocator;
 58  
 59          public HvAddressSpaceRange(HvIpaAllocator ipaAllocator)
 60          {
 61              _blockAllocator = new HvMemoryBlockAllocator(ipaAllocator, (int)AllocationGranule);
 62          }
 63  
 64          public ulong GetIpaBase()
 65          {
 66              return EnsureLevel0().Address;
 67          }
 68  
 69          public bool GetAndClearTlbInvalidationPending()
 70          {
 71              return Interlocked.Exchange(ref _tlbInvalidationPending, 0) != 0;
 72          }
 73  
 74          public void Map(ulong va, ulong pa, ulong size, ApFlags accessPermission)
 75          {
 76              MapImpl(va, pa, size, (ulong)accessPermission | BaseAttributes);
 77          }
 78  
 79          public void Unmap(ulong va, ulong size)
 80          {
 81              UnmapImpl(EnsureLevel0(), 0, va, size);
 82              Interlocked.Exchange(ref _tlbInvalidationPending, 1);
 83          }
 84  
 85          public void Reprotect(ulong va, ulong size, ApFlags accessPermission)
 86          {
 87              UpdateAttributes(va, size, (ulong)accessPermission | BaseAttributes);
 88          }
 89  
 90          private void MapImpl(ulong va, ulong pa, ulong size, ulong attr)
 91          {
 92              PtLevel level0 = EnsureLevel0();
 93  
 94              ulong endVa = va + size;
 95  
 96              while (va < endVa)
 97              {
 98                  (ulong mapSize, int depth) = GetMapSizeAndDepth(va, pa, endVa);
 99  
100                  PtLevel currentLevel = level0;
101  
102                  for (int i = 0; i < depth; i++)
103                  {
104                      int l = (int)(va >> (PageBits + (2 - i) * LevelBits)) & LevelMask;
105                      EnsureTable(currentLevel, l, i == 0);
106                      currentLevel = currentLevel.Next[l];
107                  }
108  
109                  (ulong blockSize, int blockShift) = GetBlockSizeAndShift(depth);
110  
111                  for (ulong i = 0; i < mapSize; i += blockSize)
112                  {
113                      if ((va >> blockShift) << blockShift != va ||
114                          (pa >> blockShift) << blockShift != pa)
115                      {
116                          Debug.Fail($"Block size 0x{blockSize:X} (log2: {blockShift}) is invalid for VA 0x{va:X} or PA 0x{pa:X}.");
117                      }
118  
119                      WriteBlock(currentLevel, (int)(va >> blockShift) & LevelMask, depth, pa, attr);
120  
121                      va += blockSize;
122                      pa += blockSize;
123                  }
124              }
125          }
126  
127          private void UnmapImpl(PtLevel level, int depth, ulong va, ulong size)
128          {
129              ulong endVa = (va + size + PageMask) & ~((ulong)PageMask);
130              va &= ~((ulong)PageMask);
131  
132              (ulong blockSize, _) = GetBlockSizeAndShift(depth);
133  
134              while (va < endVa)
135              {
136                  ulong nextEntryVa = GetNextAddress(va, blockSize);
137                  ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va);
138  
139                  int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask;
140  
141                  PtLevel nextTable = level.Next?[l];
142  
143                  if (nextTable != null)
144                  {
145                      // Entry is a table, visit it and update attributes as required.
146                      UnmapImpl(nextTable, depth + 1, va, chunckSize);
147                  }
148                  else if (chunckSize != blockSize)
149                  {
150                      // Entry is a block but is not aligned, we need to turn it into a table.
151                      ref ulong pte = ref level.AsSpan()[l];
152                      nextTable = CreateTable(pte, depth + 1);
153                      level.Next[l] = nextTable;
154  
155                      // Now that we have a table, we can handle it like the first case.
156                      UnmapImpl(nextTable, depth + 1, va, chunckSize);
157  
158                      // Update PTE to point to the new table.
159                      pte = (nextTable.Address & ~(ulong)PageMask) | 3UL;
160                  }
161  
162                  // If entry is a block, or if entry is a table but it is empty, we can remove it.
163                  if (nextTable == null || nextTable.EntriesCount == 0)
164                  {
165                      // Entry is a block and is fully aligned, so we can just set it to 0.
166                      if (nextTable != null)
167                      {
168                          nextTable.Allocation.Dispose();
169                          level.Next[l] = null;
170                      }
171  
172                      level.AsSpan()[l] = 0UL;
173                      level.EntriesCount--;
174                      ValidateEntriesCount(level.EntriesCount);
175                  }
176  
177                  va += chunckSize;
178              }
179          }
180  
181          private void UpdateAttributes(ulong va, ulong size, ulong newAttr)
182          {
183              UpdateAttributes(EnsureLevel0(), 0, va, size, newAttr);
184  
185              Interlocked.Exchange(ref _tlbInvalidationPending, 1);
186          }
187  
188          private void UpdateAttributes(PtLevel level, int depth, ulong va, ulong size, ulong newAttr)
189          {
190              ulong endVa = (va + size + PageSize - 1) & ~((ulong)PageSize - 1);
191              va &= ~((ulong)PageSize - 1);
192  
193              (ulong blockSize, _) = GetBlockSizeAndShift(depth);
194  
195              while (va < endVa)
196              {
197                  ulong nextEntryVa = GetNextAddress(va, blockSize);
198                  ulong chunckSize = Math.Min(endVa - va, nextEntryVa - va);
199  
200                  int l = (int)(va >> (PageBits + (2 - depth) * LevelBits)) & LevelMask;
201  
202                  ref ulong pte = ref level.AsSpan()[l];
203  
204                  // First check if the region is mapped.
205                  if ((pte & 3) != 0)
206                  {
207                      PtLevel nextTable = level.Next?[l];
208  
209                      if (nextTable != null)
210                      {
211                          // Entry is a table, visit it and update attributes as required.
212                          UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr);
213                      }
214                      else if (chunckSize != blockSize)
215                      {
216                          // Entry is a block but is not aligned, we need to turn it into a table.
217                          nextTable = CreateTable(pte, depth + 1);
218                          level.Next[l] = nextTable;
219  
220                          // Now that we have a table, we can handle it like the first case.
221                          UpdateAttributes(nextTable, depth + 1, va, chunckSize, newAttr);
222  
223                          // Update PTE to point to the new table.
224                          pte = (nextTable.Address & ~(ulong)PageMask) | 3UL;
225                      }
226                      else
227                      {
228                          // Entry is a block and is fully aligned, so we can just update the attributes.
229                          // Update PTE with the new attributes.
230                          pte = (pte & ~AttributesMask) | newAttr;
231                      }
232                  }
233  
234                  va += chunckSize;
235              }
236          }
237  
238          private PtLevel CreateTable(ulong pte, int depth)
239          {
240              pte &= ~3UL;
241              pte |= (depth == 2 ? 3UL : 1UL);
242  
243              PtLevel level = new(_blockAllocator, LevelCount, depth < 2);
244              Span<ulong> currentLevel = level.AsSpan();
245  
246              (_, int blockShift) = GetBlockSizeAndShift(depth);
247  
248              // Fill in the blocks.
249              for (int i = 0; i < LevelCount; i++)
250              {
251                  ulong offset = (ulong)i << blockShift;
252                  currentLevel[i] = pte + offset;
253              }
254  
255              level.EntriesCount = LevelCount;
256  
257              return level;
258          }
259  
260          private static (ulong, int) GetBlockSizeAndShift(int depth)
261          {
262              int blockShift = PageBits + (2 - depth) * LevelBits;
263              ulong blockSize = 1UL << blockShift;
264  
265              return (blockSize, blockShift);
266          }
267  
268          private static (ulong, int) GetMapSizeAndDepth(ulong va, ulong pa, ulong endVa)
269          {
270              // Both virtual and physical addresses must be aligned to the block size.
271              ulong combinedAddress = va | pa;
272  
273              ulong l0Alignment = 1UL << (PageBits + LevelBits * 2);
274              ulong l1Alignment = 1UL << (PageBits + LevelBits);
275  
276              if ((combinedAddress & (l0Alignment - 1)) == 0 && AlignDown(endVa, l0Alignment) > va)
277              {
278                  return (AlignDown(endVa, l0Alignment) - va, 0);
279              }
280              else if ((combinedAddress & (l1Alignment - 1)) == 0 && AlignDown(endVa, l1Alignment) > va)
281              {
282                  ulong nextOrderVa = GetNextAddress(va, l0Alignment);
283  
284                  if (nextOrderVa <= endVa)
285                  {
286                      return (nextOrderVa - va, 1);
287                  }
288                  else
289                  {
290                      return (AlignDown(endVa, l1Alignment) - va, 1);
291                  }
292              }
293              else
294              {
295                  ulong nextOrderVa = GetNextAddress(va, l1Alignment);
296  
297                  if (nextOrderVa <= endVa)
298                  {
299                      return (nextOrderVa - va, 2);
300                  }
301                  else
302                  {
303                      return (endVa - va, 2);
304                  }
305              }
306          }
307  
308          private static ulong AlignDown(ulong va, ulong alignment)
309          {
310              return va & ~(alignment - 1);
311          }
312  
313          private static ulong GetNextAddress(ulong va, ulong alignment)
314          {
315              return (va + alignment) & ~(alignment - 1);
316          }
317  
318          private PtLevel EnsureLevel0()
319          {
320              PtLevel level0 = _level0;
321  
322              if (level0 == null)
323              {
324                  level0 = new PtLevel(_blockAllocator, LevelCount, true);
325                  _level0 = level0;
326              }
327  
328              return level0;
329          }
330  
331          private void EnsureTable(PtLevel level, int index, bool hasNext)
332          {
333              Span<ulong> currentTable = level.AsSpan();
334  
335              if ((currentTable[index] & 1) == 0)
336              {
337                  PtLevel nextLevel = new(_blockAllocator, LevelCount, hasNext);
338  
339                  currentTable[index] = (nextLevel.Address & ~(ulong)PageMask) | 3UL;
340                  level.Next[index] = nextLevel;
341                  level.EntriesCount++;
342                  ValidateEntriesCount(level.EntriesCount);
343              }
344              else if (level.Next[index] == null)
345              {
346                  Debug.Fail($"Index {index} is block, expected a table.");
347              }
348          }
349  
350          private static void WriteBlock(PtLevel level, int index, int depth, ulong pa, ulong attr)
351          {
352              Span<ulong> currentTable = level.AsSpan();
353  
354              currentTable[index] = (pa & ~((ulong)AllLevelsMask >> (depth * LevelBits))) | (depth == 2 ? 3UL : 1UL) | attr;
355  
356              level.EntriesCount++;
357              ValidateEntriesCount(level.EntriesCount);
358          }
359  
360          private static void ValidateEntriesCount(int count)
361          {
362              Debug.Assert(count >= 0 && count <= LevelCount, $"Entries count {count} is invalid.");
363          }
364  
365          public void Dispose()
366          {
367              _blockAllocator.Dispose();
368          }
369      }
370  }