VirtualMemoryManagerBase.cs
1 using Ryujinx.Common.Memory; 2 using System; 3 using System.Buffers; 4 using System.Runtime.CompilerServices; 5 using System.Runtime.InteropServices; 6 7 namespace Ryujinx.Memory 8 { 9 public abstract class VirtualMemoryManagerBase : IWritableBlock 10 { 11 public const int PageBits = 12; 12 public const int PageSize = 1 << PageBits; 13 public const int PageMask = PageSize - 1; 14 15 protected abstract ulong AddressSpaceSize { get; } 16 17 public virtual ReadOnlySequence<byte> GetReadOnlySequence(ulong va, int size, bool tracked = false) 18 { 19 if (size == 0) 20 { 21 return ReadOnlySequence<byte>.Empty; 22 } 23 24 if (tracked) 25 { 26 SignalMemoryTracking(va, (ulong)size, false); 27 } 28 29 if (IsContiguousAndMapped(va, size)) 30 { 31 nuint pa = TranslateVirtualAddressUnchecked(va); 32 33 return new ReadOnlySequence<byte>(GetPhysicalAddressMemory(pa, size)); 34 } 35 else 36 { 37 AssertValidAddressAndSize(va, size); 38 39 int offset = 0, segmentSize; 40 41 BytesReadOnlySequenceSegment first = null, last = null; 42 43 if ((va & PageMask) != 0) 44 { 45 nuint pa = TranslateVirtualAddressChecked(va); 46 47 segmentSize = Math.Min(size, PageSize - (int)(va & PageMask)); 48 49 Memory<byte> memory = GetPhysicalAddressMemory(pa, segmentSize); 50 51 first = last = new BytesReadOnlySequenceSegment(memory); 52 53 offset += segmentSize; 54 } 55 56 for (; offset < size; offset += segmentSize) 57 { 58 nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); 59 60 segmentSize = Math.Min(size - offset, PageSize); 61 62 Memory<byte> memory = GetPhysicalAddressMemory(pa, segmentSize); 63 64 if (first is null) 65 { 66 first = last = new BytesReadOnlySequenceSegment(memory); 67 } 68 else 69 { 70 if (last.IsContiguousWith(memory, out nuint contiguousStart, out int contiguousSize)) 71 { 72 last.Replace(GetPhysicalAddressMemory(contiguousStart, contiguousSize)); 73 } 74 else 75 { 76 last = last.Append(memory); 77 } 78 } 79 } 80 81 return new ReadOnlySequence<byte>(first, 0, last, (int)(size - last.RunningIndex)); 82 } 83 } 84 85 public virtual ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false) 86 { 87 if (size == 0) 88 { 89 return ReadOnlySpan<byte>.Empty; 90 } 91 92 if (tracked) 93 { 94 SignalMemoryTracking(va, (ulong)size, false); 95 } 96 97 if (IsContiguousAndMapped(va, size)) 98 { 99 nuint pa = TranslateVirtualAddressUnchecked(va); 100 101 return GetPhysicalAddressSpan(pa, size); 102 } 103 else 104 { 105 Span<byte> data = new byte[size]; 106 107 Read(va, data); 108 109 return data; 110 } 111 } 112 113 public virtual WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false) 114 { 115 if (size == 0) 116 { 117 return new WritableRegion(null, va, Memory<byte>.Empty); 118 } 119 120 if (tracked) 121 { 122 SignalMemoryTracking(va, (ulong)size, true); 123 } 124 125 if (IsContiguousAndMapped(va, size)) 126 { 127 nuint pa = TranslateVirtualAddressUnchecked(va); 128 129 return new WritableRegion(null, va, GetPhysicalAddressMemory(pa, size)); 130 } 131 else 132 { 133 MemoryOwner<byte> memoryOwner = MemoryOwner<byte>.Rent(size); 134 135 Read(va, memoryOwner.Span); 136 137 return new WritableRegion(this, va, memoryOwner); 138 } 139 } 140 141 public abstract bool IsMapped(ulong va); 142 143 public virtual void MapForeign(ulong va, nuint hostPointer, ulong size) 144 { 145 throw new NotSupportedException(); 146 } 147 148 public virtual T Read<T>(ulong va) where T : unmanaged 149 { 150 return MemoryMarshal.Cast<byte, T>(GetSpan(va, Unsafe.SizeOf<T>()))[0]; 151 } 152 153 public virtual void Read(ulong va, Span<byte> data) 154 { 155 if (data.Length == 0) 156 { 157 return; 158 } 159 160 AssertValidAddressAndSize(va, data.Length); 161 162 int offset = 0, size; 163 164 if ((va & PageMask) != 0) 165 { 166 nuint pa = TranslateVirtualAddressChecked(va); 167 168 size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); 169 170 GetPhysicalAddressSpan(pa, size).CopyTo(data[..size]); 171 172 offset += size; 173 } 174 175 for (; offset < data.Length; offset += size) 176 { 177 nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); 178 179 size = Math.Min(data.Length - offset, PageSize); 180 181 GetPhysicalAddressSpan(pa, size).CopyTo(data.Slice(offset, size)); 182 } 183 } 184 185 public virtual T ReadTracked<T>(ulong va) where T : unmanaged 186 { 187 SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false); 188 189 return Read<T>(va); 190 } 191 192 public virtual void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null) 193 { 194 // No default implementation 195 } 196 197 public virtual void Write(ulong va, ReadOnlySpan<byte> data) 198 { 199 if (data.Length == 0) 200 { 201 return; 202 } 203 204 SignalMemoryTracking(va, (ulong)data.Length, true); 205 206 WriteImpl(va, data); 207 } 208 209 public virtual void Write<T>(ulong va, T value) where T : unmanaged 210 { 211 Write(va, MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateSpan(ref value, 1))); 212 } 213 214 public virtual void WriteUntracked(ulong va, ReadOnlySpan<byte> data) 215 { 216 if (data.Length == 0) 217 { 218 return; 219 } 220 221 WriteImpl(va, data); 222 } 223 224 public virtual bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data) 225 { 226 if (data.Length == 0) 227 { 228 return false; 229 } 230 231 if (IsContiguousAndMapped(va, data.Length)) 232 { 233 SignalMemoryTracking(va, (ulong)data.Length, false); 234 235 nuint pa = TranslateVirtualAddressChecked(va); 236 237 var target = GetPhysicalAddressSpan(pa, data.Length); 238 239 bool changed = !data.SequenceEqual(target); 240 241 if (changed) 242 { 243 data.CopyTo(target); 244 } 245 246 return changed; 247 } 248 else 249 { 250 Write(va, data); 251 252 return true; 253 } 254 } 255 256 /// <summary> 257 /// Ensures the combination of virtual address and size is part of the addressable space. 258 /// </summary> 259 /// <param name="va">Virtual address of the range</param> 260 /// <param name="size">Size of the range in bytes</param> 261 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception> 262 protected void AssertValidAddressAndSize(ulong va, ulong size) 263 { 264 if (!ValidateAddressAndSize(va, size)) 265 { 266 throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}"); 267 } 268 } 269 270 /// <summary> 271 /// Ensures the combination of virtual address and size is part of the addressable space. 272 /// </summary> 273 /// <param name="va">Virtual address of the range</param> 274 /// <param name="size">Size of the range in bytes</param> 275 /// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception> 276 [MethodImpl(MethodImplOptions.AggressiveInlining)] 277 protected void AssertValidAddressAndSize(ulong va, int size) 278 => AssertValidAddressAndSize(va, (ulong)size); 279 280 /// <summary> 281 /// Computes the number of pages in a virtual address range. 282 /// </summary> 283 /// <param name="va">Virtual address of the range</param> 284 /// <param name="size">Size of the range</param> 285 /// <param name="startVa">The virtual address of the beginning of the first page</param> 286 /// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks> 287 [MethodImpl(MethodImplOptions.AggressiveInlining)] 288 protected static int GetPagesCount(ulong va, ulong size, out ulong startVa) 289 { 290 // WARNING: Always check if ulong does not overflow during the operations. 291 startVa = va & ~(ulong)PageMask; 292 ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask; 293 294 return (int)(vaSpan / PageSize); 295 } 296 297 protected abstract Memory<byte> GetPhysicalAddressMemory(nuint pa, int size); 298 299 protected abstract Span<byte> GetPhysicalAddressSpan(nuint pa, int size); 300 301 [MethodImpl(MethodImplOptions.AggressiveInlining)] 302 protected bool IsContiguous(ulong va, int size) => IsContiguous(va, (ulong)size); 303 304 protected virtual bool IsContiguous(ulong va, ulong size) 305 { 306 if (!ValidateAddress(va) || !ValidateAddressAndSize(va, size)) 307 { 308 return false; 309 } 310 311 int pages = GetPagesCount(va, size, out va); 312 313 for (int page = 0; page < pages - 1; page++) 314 { 315 if (!ValidateAddress(va + PageSize)) 316 { 317 return false; 318 } 319 320 if (TranslateVirtualAddressUnchecked(va) + PageSize != TranslateVirtualAddressUnchecked(va + PageSize)) 321 { 322 return false; 323 } 324 325 va += PageSize; 326 } 327 328 return true; 329 } 330 331 [MethodImpl(MethodImplOptions.AggressiveInlining)] 332 protected bool IsContiguousAndMapped(ulong va, int size) 333 => IsContiguous(va, size) && IsMapped(va); 334 335 protected abstract nuint TranslateVirtualAddressChecked(ulong va); 336 337 protected abstract nuint TranslateVirtualAddressUnchecked(ulong va); 338 339 /// <summary> 340 /// Checks if the virtual address is part of the addressable space. 341 /// </summary> 342 /// <param name="va">Virtual address</param> 343 /// <returns>True if the virtual address is part of the addressable space</returns> 344 [MethodImpl(MethodImplOptions.AggressiveInlining)] 345 protected bool ValidateAddress(ulong va) 346 { 347 return va < AddressSpaceSize; 348 } 349 350 /// <summary> 351 /// Checks if the combination of virtual address and size is part of the addressable space. 352 /// </summary> 353 /// <param name="va">Virtual address of the range</param> 354 /// <param name="size">Size of the range in bytes</param> 355 /// <returns>True if the combination of virtual address and size is part of the addressable space</returns> 356 protected bool ValidateAddressAndSize(ulong va, ulong size) 357 { 358 ulong endVa = va + size; 359 return endVa >= va && endVa >= size && endVa <= AddressSpaceSize; 360 } 361 362 protected static void ThrowInvalidMemoryRegionException(string message) 363 => throw new InvalidMemoryRegionException(message); 364 365 protected static void ThrowMemoryNotContiguous() 366 => throw new MemoryNotContiguousException(); 367 368 protected virtual void WriteImpl(ulong va, ReadOnlySpan<byte> data) 369 { 370 AssertValidAddressAndSize(va, data.Length); 371 372 if (IsContiguousAndMapped(va, data.Length)) 373 { 374 nuint pa = TranslateVirtualAddressUnchecked(va); 375 376 data.CopyTo(GetPhysicalAddressSpan(pa, data.Length)); 377 } 378 else 379 { 380 int offset = 0, size; 381 382 if ((va & PageMask) != 0) 383 { 384 nuint pa = TranslateVirtualAddressChecked(va); 385 386 size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); 387 388 data[..size].CopyTo(GetPhysicalAddressSpan(pa, size)); 389 390 offset += size; 391 } 392 393 for (; offset < data.Length; offset += size) 394 { 395 nuint pa = TranslateVirtualAddressChecked(va + (ulong)offset); 396 397 size = Math.Min(data.Length - offset, PageSize); 398 399 data.Slice(offset, size).CopyTo(GetPhysicalAddressSpan(pa, size)); 400 } 401 } 402 } 403 404 } 405 }