DeviceMemoryManager.cs
1 using Ryujinx.Common.Memory; 2 using Ryujinx.Memory; 3 using System; 4 using System.Runtime.CompilerServices; 5 using System.Runtime.InteropServices; 6 7 namespace Ryujinx.Graphics.Device 8 { 9 /// <summary> 10 /// Device memory manager. 11 /// </summary> 12 public class DeviceMemoryManager : IWritableBlock 13 { 14 private const int PtLvl0Bits = 10; 15 private const int PtLvl1Bits = 10; 16 public const int PtPageBits = 12; 17 18 private const ulong PtLvl0Size = 1UL << PtLvl0Bits; 19 private const ulong PtLvl1Size = 1UL << PtLvl1Bits; 20 public const ulong PageSize = 1UL << PtPageBits; 21 22 private const ulong PtLvl0Mask = PtLvl0Size - 1; 23 private const ulong PtLvl1Mask = PtLvl1Size - 1; 24 public const ulong PageMask = PageSize - 1; 25 26 private const int PtLvl0Bit = PtPageBits + PtLvl1Bits; 27 private const int PtLvl1Bit = PtPageBits; 28 private const int AddressSpaceBits = PtPageBits + PtLvl1Bits + PtLvl0Bits; 29 30 public const ulong PteUnmapped = ulong.MaxValue; 31 32 private readonly ulong[][] _pageTable; 33 34 private readonly IVirtualMemoryManager _physical; 35 36 /// <summary> 37 /// Creates a new instance of the GPU memory manager. 38 /// </summary> 39 /// <param name="physicalMemory">Physical memory that this memory manager will map into</param> 40 public DeviceMemoryManager(IVirtualMemoryManager physicalMemory) 41 { 42 _physical = physicalMemory; 43 _pageTable = new ulong[PtLvl0Size][]; 44 } 45 46 /// <summary> 47 /// Reads data from GPU mapped memory. 48 /// </summary> 49 /// <typeparam name="T">Type of the data</typeparam> 50 /// <param name="va">GPU virtual address where the data is located</param> 51 /// <returns>The data at the specified memory location</returns> 52 public T Read<T>(ulong va) where T : unmanaged 53 { 54 int size = Unsafe.SizeOf<T>(); 55 56 if (IsContiguous(va, size)) 57 { 58 return _physical.Read<T>(Translate(va)); 59 } 60 else 61 { 62 Span<byte> data = new byte[size]; 63 64 ReadImpl(va, data); 65 66 return MemoryMarshal.Cast<byte, T>(data)[0]; 67 } 68 } 69 70 /// <summary> 71 /// Gets a read-only span of data from GPU mapped memory. 72 /// </summary> 73 /// <param name="va">GPU virtual address where the data is located</param> 74 /// <param name="size">Size of the data</param> 75 /// <returns>The span of the data at the specified memory location</returns> 76 public ReadOnlySpan<byte> GetSpan(ulong va, int size) 77 { 78 if (IsContiguous(va, size)) 79 { 80 return _physical.GetSpan(Translate(va), size); 81 } 82 else 83 { 84 Span<byte> data = new byte[size]; 85 86 ReadImpl(va, data); 87 88 return data; 89 } 90 } 91 92 /// <summary> 93 /// Reads data from a possibly non-contiguous region of GPU mapped memory. 94 /// </summary> 95 /// <param name="va">GPU virtual address of the data</param> 96 /// <param name="data">Span to write the read data into</param> 97 private void ReadImpl(ulong va, Span<byte> data) 98 { 99 if (data.Length == 0) 100 { 101 return; 102 } 103 104 int offset = 0, size; 105 106 if ((va & PageMask) != 0) 107 { 108 ulong pa = Translate(va); 109 110 size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); 111 112 if (pa != PteUnmapped && _physical.IsMapped(pa)) 113 { 114 _physical.GetSpan(pa, size).CopyTo(data[..size]); 115 } 116 117 offset += size; 118 } 119 120 for (; offset < data.Length; offset += size) 121 { 122 ulong pa = Translate(va + (ulong)offset); 123 124 size = Math.Min(data.Length - offset, (int)PageSize); 125 126 if (pa != PteUnmapped && _physical.IsMapped(pa)) 127 { 128 _physical.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); 129 } 130 } 131 } 132 133 /// <summary> 134 /// Gets a writable region from GPU mapped memory. 135 /// </summary> 136 /// <param name="va">Start address of the range</param> 137 /// <param name="size">Size in bytes to be range</param> 138 /// <returns>A writable region with the data at the specified memory location</returns> 139 public WritableRegion GetWritableRegion(ulong va, int size) 140 { 141 if (IsContiguous(va, size)) 142 { 143 return _physical.GetWritableRegion(Translate(va), size, tracked: true); 144 } 145 else 146 { 147 MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size); 148 149 ReadImpl(va, memoryOwner.Span); 150 151 return new WritableRegion(this, va, memoryOwner, tracked: true); 152 } 153 } 154 155 /// <summary> 156 /// Writes data to GPU mapped memory. 157 /// </summary> 158 /// <typeparam name="T">Type of the data</typeparam> 159 /// <param name="va">GPU virtual address to write the value into</param> 160 /// <param name="value">The value to be written</param> 161 public void Write<T>(ulong va, T value) where T : unmanaged 162 { 163 Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1))); 164 } 165 166 /// <summary> 167 /// Writes data to GPU mapped memory. 168 /// </summary> 169 /// <param name="va">GPU virtual address to write the data into</param> 170 /// <param name="data">The data to be written</param> 171 public void Write(ulong va, ReadOnlySpan<byte> data) 172 { 173 if (IsContiguous(va, data.Length)) 174 { 175 _physical.Write(Translate(va), data); 176 } 177 else 178 { 179 int offset = 0, size; 180 181 if ((va & PageMask) != 0) 182 { 183 ulong pa = Translate(va); 184 185 size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask)); 186 187 if (pa != PteUnmapped && _physical.IsMapped(pa)) 188 { 189 _physical.Write(pa, data[..size]); 190 } 191 192 offset += size; 193 } 194 195 for (; offset < data.Length; offset += size) 196 { 197 ulong pa = Translate(va + (ulong)offset); 198 199 size = Math.Min(data.Length - offset, (int)PageSize); 200 201 if (pa != PteUnmapped && _physical.IsMapped(pa)) 202 { 203 _physical.Write(pa, data.Slice(offset, size)); 204 } 205 } 206 } 207 } 208 209 /// <summary> 210 /// Writes data to GPU mapped memory without write tracking. 211 /// </summary> 212 /// <param name="va">GPU virtual address to write the data into</param> 213 /// <param name="data">The data to be written</param> 214 public void WriteUntracked(ulong va, ReadOnlySpan<byte> data) 215 { 216 throw new NotSupportedException(); 217 } 218 219 /// <summary> 220 /// Maps a given range of pages to the specified CPU virtual address. 221 /// </summary> 222 /// <remarks> 223 /// All addresses and sizes must be page aligned. 224 /// </remarks> 225 /// <param name="pa">CPU virtual address to map into</param> 226 /// <param name="va">GPU virtual address to be mapped</param> 227 /// <param name="kind">Kind of the resource located at the mapping</param> 228 public void Map(ulong pa, ulong va, ulong size) 229 { 230 lock (_pageTable) 231 { 232 for (ulong offset = 0; offset < size; offset += PageSize) 233 { 234 SetPte(va + offset, PackPte(pa + offset)); 235 } 236 } 237 } 238 239 /// <summary> 240 /// Unmaps a given range of pages at the specified GPU virtual memory region. 241 /// </summary> 242 /// <param name="va">GPU virtual address to unmap</param> 243 /// <param name="size">Size in bytes of the region being unmapped</param> 244 public void Unmap(ulong va, ulong size) 245 { 246 lock (_pageTable) 247 { 248 for (ulong offset = 0; offset < size; offset += PageSize) 249 { 250 SetPte(va + offset, PteUnmapped); 251 } 252 } 253 } 254 255 /// <summary> 256 /// Checks if a region of GPU mapped memory is contiguous. 257 /// </summary> 258 /// <param name="va">GPU virtual address of the region</param> 259 /// <param name="size">Size of the region</param> 260 /// <returns>True if the region is contiguous, false otherwise</returns> 261 [MethodImpl(MethodImplOptions.AggressiveInlining)] 262 private bool IsContiguous(ulong va, int size) 263 { 264 if (!ValidateAddress(va) || GetPte(va) == PteUnmapped) 265 { 266 return false; 267 } 268 269 ulong endVa = (va + (ulong)size + PageMask) & ~PageMask; 270 271 va &= ~PageMask; 272 273 int pages = (int)((endVa - va) / PageSize); 274 275 for (int page = 0; page < pages - 1; page++) 276 { 277 if (!ValidateAddress(va + PageSize) || GetPte(va + PageSize) == PteUnmapped) 278 { 279 return false; 280 } 281 282 if (Translate(va) + PageSize != Translate(va + PageSize)) 283 { 284 return false; 285 } 286 287 va += PageSize; 288 } 289 290 return true; 291 } 292 293 /// <summary> 294 /// Validates a GPU virtual address. 295 /// </summary> 296 /// <param name="va">Address to validate</param> 297 /// <returns>True if the address is valid, false otherwise</returns> 298 private static bool ValidateAddress(ulong va) 299 { 300 return va < (1UL << AddressSpaceBits); 301 } 302 303 /// <summary> 304 /// Checks if a given page is mapped. 305 /// </summary> 306 /// <param name="va">GPU virtual address of the page to check</param> 307 /// <returns>True if the page is mapped, false otherwise</returns> 308 public bool IsMapped(ulong va) 309 { 310 return Translate(va) != PteUnmapped; 311 } 312 313 /// <summary> 314 /// Translates a GPU virtual address to a CPU virtual address. 315 /// </summary> 316 /// <param name="va">GPU virtual address to be translated</param> 317 /// <returns>CPU virtual address, or <see cref="PteUnmapped"/> if unmapped</returns> 318 public ulong Translate(ulong va) 319 { 320 if (!ValidateAddress(va)) 321 { 322 return PteUnmapped; 323 } 324 325 ulong pte = GetPte(va); 326 327 if (pte == PteUnmapped) 328 { 329 return PteUnmapped; 330 } 331 332 return UnpackPaFromPte(pte) + (va & PageMask); 333 } 334 335 /// <summary> 336 /// Gets the Page Table entry for a given GPU virtual address. 337 /// </summary> 338 /// <param name="va">GPU virtual address</param> 339 /// <returns>Page table entry (CPU virtual address)</returns> 340 private ulong GetPte(ulong va) 341 { 342 ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; 343 ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; 344 345 if (_pageTable[l0] == null) 346 { 347 return PteUnmapped; 348 } 349 350 return _pageTable[l0][l1]; 351 } 352 353 /// <summary> 354 /// Sets a Page Table entry at a given GPU virtual address. 355 /// </summary> 356 /// <param name="va">GPU virtual address</param> 357 /// <param name="pte">Page table entry (CPU virtual address)</param> 358 private void SetPte(ulong va, ulong pte) 359 { 360 ulong l0 = (va >> PtLvl0Bit) & PtLvl0Mask; 361 ulong l1 = (va >> PtLvl1Bit) & PtLvl1Mask; 362 363 if (_pageTable[l0] == null) 364 { 365 _pageTable[l0] = new ulong[PtLvl1Size]; 366 367 for (ulong index = 0; index < PtLvl1Size; index++) 368 { 369 _pageTable[l0][index] = PteUnmapped; 370 } 371 } 372 373 _pageTable[l0][l1] = pte; 374 } 375 376 /// <summary> 377 /// Creates a page table entry from a physical address and kind. 378 /// </summary> 379 /// <param name="pa">Physical address</param> 380 /// <returns>Page table entry</returns> 381 private static ulong PackPte(ulong pa) 382 { 383 return pa; 384 } 385 386 /// <summary> 387 /// Unpacks physical address from a page table entry. 388 /// </summary> 389 /// <param name="pte">Page table entry</param> 390 /// <returns>Physical address</returns> 391 private static ulong UnpackPaFromPte(ulong pte) 392 { 393 return pte; 394 } 395 } 396 }