RegionHandle.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Threading; 5 6 namespace Ryujinx.Memory.Tracking 7 { 8 /// <summary> 9 /// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made, 10 /// and an action can be performed when the region is read to or written from. 11 /// </summary> 12 public class RegionHandle : IRegionHandle 13 { 14 /// <summary> 15 /// If more than this number of checks have been performed on a dirty flag since its last reprotect, 16 /// then it is dirtied infrequently. 17 /// </summary> 18 private const int CheckCountForInfrequent = 3; 19 20 /// <summary> 21 /// Number of frequent dirty/consume in a row to make this handle volatile. 22 /// </summary> 23 private const int VolatileThreshold = 5; 24 25 public bool Dirty 26 { 27 get 28 { 29 return Bitmap.IsSet(DirtyBit); 30 } 31 protected set 32 { 33 Bitmap.Set(DirtyBit, value); 34 } 35 } 36 37 internal int SequenceNumber { get; set; } 38 internal int Id { get; } 39 40 public bool Unmapped { get; private set; } 41 42 public ulong Address { get; } 43 public ulong Size { get; } 44 public ulong EndAddress { get; } 45 46 public ulong RealAddress { get; } 47 public ulong RealSize { get; } 48 public ulong RealEndAddress { get; } 49 50 internal IMultiRegionHandle Parent { get; set; } 51 52 private event Action OnDirty; 53 54 private readonly object _preActionLock = new(); 55 private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access. 56 private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write. 57 private readonly List<VirtualRegion> _regions; 58 private readonly List<VirtualRegion> _guestRegions; 59 private readonly List<VirtualRegion> _allRegions; 60 private readonly MemoryTracking _tracking; 61 private bool _disposed; 62 63 private int _checkCount = 0; 64 private int _volatileCount = 0; 65 private bool _volatile; 66 67 internal MemoryPermission RequiredPermission 68 { 69 get 70 { 71 // If this is unmapped, allow reprotecting as RW as it can't be dirtied. 72 // This is required for the partial unmap cases where part of the data are still being accessed. 73 if (Unmapped) 74 { 75 return MemoryPermission.ReadAndWrite; 76 } 77 78 if (_preAction != null) 79 { 80 return MemoryPermission.None; 81 } 82 83 return Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read; 84 } 85 } 86 87 internal RegionSignal PreAction => _preAction; 88 89 internal ConcurrentBitmap Bitmap; 90 internal int DirtyBit; 91 92 /// <summary> 93 /// Create a new bitmap backed region handle. The handle is registered with the given tracking object, 94 /// and will be notified of any changes to the specified region. 95 /// </summary> 96 /// <param name="tracking">Tracking object for the target memory block</param> 97 /// <param name="address">Virtual address of the region to track</param> 98 /// <param name="size">Size of the region to track</param> 99 /// <param name="realAddress">The real, unaligned address of the handle</param> 100 /// <param name="realSize">The real, unaligned size of the handle</param> 101 /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param> 102 /// <param name="bit">The bit index representing the dirty flag for this handle</param> 103 /// <param name="id">Handle ID</param> 104 /// <param name="flags">Region flags</param> 105 /// <param name="mapped">True if the region handle starts mapped</param> 106 internal RegionHandle( 107 MemoryTracking tracking, 108 ulong address, 109 ulong size, 110 ulong realAddress, 111 ulong realSize, 112 ConcurrentBitmap bitmap, 113 int bit, 114 int id, 115 RegionFlags flags, 116 bool mapped = true) 117 { 118 Bitmap = bitmap; 119 DirtyBit = bit; 120 121 Dirty = mapped; 122 123 Id = id; 124 125 Unmapped = !mapped; 126 Address = address; 127 Size = size; 128 EndAddress = address + size; 129 130 RealAddress = realAddress; 131 RealSize = realSize; 132 RealEndAddress = realAddress + realSize; 133 134 _tracking = tracking; 135 136 _regions = tracking.GetVirtualRegionsForHandle(address, size, false); 137 _guestRegions = GetGuestRegions(tracking, address, size, flags); 138 _allRegions = new List<VirtualRegion>(_regions.Count + _guestRegions.Count); 139 140 InitializeRegions(); 141 } 142 143 /// <summary> 144 /// Create a new region handle. The handle is registered with the given tracking object, 145 /// and will be notified of any changes to the specified region. 146 /// </summary> 147 /// <param name="tracking">Tracking object for the target memory block</param> 148 /// <param name="address">Virtual address of the region to track</param> 149 /// <param name="size">Size of the region to track</param> 150 /// <param name="realAddress">The real, unaligned address of the handle</param> 151 /// <param name="realSize">The real, unaligned size of the handle</param> 152 /// <param name="id">Handle ID</param> 153 /// <param name="flags">Region flags</param> 154 /// <param name="mapped">True if the region handle starts mapped</param> 155 internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, RegionFlags flags, bool mapped = true) 156 { 157 Bitmap = new ConcurrentBitmap(1, mapped); 158 159 Id = id; 160 161 Unmapped = !mapped; 162 163 Address = address; 164 Size = size; 165 EndAddress = address + size; 166 167 RealAddress = realAddress; 168 RealSize = realSize; 169 RealEndAddress = realAddress + realSize; 170 171 _tracking = tracking; 172 173 _regions = tracking.GetVirtualRegionsForHandle(address, size, false); 174 _guestRegions = GetGuestRegions(tracking, address, size, flags); 175 _allRegions = new List<VirtualRegion>(_regions.Count + _guestRegions.Count); 176 177 InitializeRegions(); 178 } 179 180 private List<VirtualRegion> GetGuestRegions(MemoryTracking tracking, ulong address, ulong size, RegionFlags flags) 181 { 182 ulong guestAddress; 183 ulong guestSize; 184 185 if (flags.HasFlag(RegionFlags.UnalignedAccess)) 186 { 187 (guestAddress, guestSize) = tracking.GetUnalignedSafeRegion(address, size); 188 } 189 else 190 { 191 (guestAddress, guestSize) = (address, size); 192 } 193 194 return tracking.GetVirtualRegionsForHandle(guestAddress, guestSize, true); 195 } 196 197 private void InitializeRegions() 198 { 199 _allRegions.AddRange(_regions); 200 _allRegions.AddRange(_guestRegions); 201 202 foreach (var region in _allRegions) 203 { 204 region.Handles.Add(this); 205 } 206 } 207 208 /// <summary> 209 /// Replace the bitmap and bit index used to track dirty state. 210 /// </summary> 211 /// <remarks> 212 /// The tracking lock should be held when this is called, to ensure neither bitmap is modified. 213 /// </remarks> 214 /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param> 215 /// <param name="bit">The bit index representing the dirty flag for this handle</param> 216 internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit) 217 { 218 // Assumes the tracking lock is held, so nothing else can signal right now. 219 220 var oldBitmap = Bitmap; 221 var oldBit = DirtyBit; 222 223 bitmap.Set(bit, Dirty); 224 225 Bitmap = bitmap; 226 DirtyBit = bit; 227 228 Dirty |= oldBitmap.IsSet(oldBit); 229 } 230 231 /// <summary> 232 /// Clear the volatile state of this handle. 233 /// </summary> 234 private void ClearVolatile() 235 { 236 _volatileCount = 0; 237 _volatile = false; 238 } 239 240 /// <summary> 241 /// Check if this handle is dirty, or if it is volatile. (changes very often) 242 /// </summary> 243 /// <returns>True if the handle is dirty or volatile, false otherwise</returns> 244 public bool DirtyOrVolatile() 245 { 246 _checkCount++; 247 return _volatile || Dirty; 248 } 249 250 /// <summary> 251 /// Signal that a memory action occurred within this handle's virtual regions. 252 /// </summary> 253 /// <param name="address">Address accessed</param> 254 /// <param name="size">Size of the region affected in bytes</param> 255 /// <param name="write">Whether the region was written to or read</param> 256 /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param> 257 internal void Signal(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable) 258 { 259 // If this handle was already unmapped (even if just partially), 260 // then we have nothing to do until it is mapped again. 261 // The pre-action should be still consumed to avoid flushing on remap. 262 if (Unmapped) 263 { 264 Interlocked.Exchange(ref _preAction, null); 265 return; 266 } 267 268 if (_preAction != null) 269 { 270 // Limit the range to within this handle. 271 ulong maxAddress = Math.Max(address, RealAddress); 272 ulong minEndAddress = Math.Min(address + size, RealAddress + RealSize); 273 274 // Copy the handles list in case it changes when we're out of the lock. 275 if (handleIterable is List<RegionHandle>) 276 { 277 handleIterable = handleIterable.ToArray(); 278 } 279 280 // Temporarily release the tracking lock while we're running the action. 281 Monitor.Exit(_tracking.TrackingLock); 282 283 try 284 { 285 lock (_preActionLock) 286 { 287 _preAction?.Invoke(maxAddress, minEndAddress - maxAddress); 288 289 // The action is removed after it returns, to ensure that the null check above succeeds when 290 // it's still in progress rather than continuing and possibly missing a required data flush. 291 Interlocked.Exchange(ref _preAction, null); 292 } 293 } 294 finally 295 { 296 Monitor.Enter(_tracking.TrackingLock); 297 } 298 } 299 300 if (write) 301 { 302 bool oldDirty = Dirty; 303 Dirty = true; 304 if (!oldDirty) 305 { 306 OnDirty?.Invoke(); 307 } 308 Parent?.SignalWrite(); 309 } 310 } 311 312 /// <summary> 313 /// Signal that a precise memory action occurred within this handle's virtual regions. 314 /// If there is no precise action, or the action returns false, the normal signal handler will be called. 315 /// </summary> 316 /// <param name="address">Address accessed</param> 317 /// <param name="size">Size of the region affected in bytes</param> 318 /// <param name="write">Whether the region was written to or read</param> 319 /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param> 320 /// <returns>True if a precise action was performed and returned true, false otherwise</returns> 321 internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable) 322 { 323 if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write)) 324 { 325 return true; 326 } 327 328 Signal(address, size, write, ref handleIterable); 329 330 return false; 331 } 332 333 /// <summary> 334 /// Force this handle to be dirty, without reprotecting. 335 /// </summary> 336 public void ForceDirty() 337 { 338 Dirty = true; 339 } 340 341 /// <summary> 342 /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. 343 /// </summary> 344 /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param> 345 /// <param name="consecutiveCheck">True if this reprotect is the result of consecutive dirty checks</param> 346 public void Reprotect(bool asDirty, bool consecutiveCheck = false) 347 { 348 if (_volatile) 349 { 350 return; 351 } 352 353 Dirty = asDirty; 354 355 bool protectionChanged = false; 356 357 lock (_tracking.TrackingLock) 358 { 359 foreach (VirtualRegion region in _allRegions) 360 { 361 protectionChanged |= region.UpdateProtection(); 362 } 363 } 364 365 if (!protectionChanged) 366 { 367 // Counteract the check count being incremented when this handle was forced dirty. 368 // It doesn't count for protected write tracking. 369 370 _checkCount--; 371 } 372 else if (!asDirty) 373 { 374 if (consecutiveCheck || (_checkCount > 0 && _checkCount < CheckCountForInfrequent)) 375 { 376 if (++_volatileCount >= VolatileThreshold && _preAction == null) 377 { 378 _volatile = true; 379 return; 380 } 381 } 382 else 383 { 384 _volatileCount = 0; 385 } 386 387 _checkCount = 0; 388 } 389 } 390 391 /// <summary> 392 /// Consume the dirty flag for this handle, and reprotect so it can be set on the next write. 393 /// </summary> 394 /// <param name="asDirty">True if the handle should be reprotected as dirty, rather than have it cleared</param> 395 public void Reprotect(bool asDirty = false) 396 { 397 Reprotect(asDirty, false); 398 } 399 400 /// <summary> 401 /// Register an action to perform when the tracked region is read or written. 402 /// The action is automatically removed after it runs. 403 /// </summary> 404 /// <param name="action">Action to call on read or write</param> 405 public void RegisterAction(RegionSignal action) 406 { 407 ClearVolatile(); 408 409 lock (_preActionLock) 410 { 411 RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action); 412 413 if (lastAction == null && action != lastAction) 414 { 415 lock (_tracking.TrackingLock) 416 { 417 foreach (VirtualRegion region in _allRegions) 418 { 419 region.UpdateProtection(); 420 } 421 } 422 } 423 } 424 } 425 426 /// <summary> 427 /// Register an action to perform when a precise access occurs (one with exact address and size). 428 /// If the action returns true, read/write tracking are skipped. 429 /// </summary> 430 /// <param name="action">Action to call on read or write</param> 431 public void RegisterPreciseAction(PreciseRegionSignal action) 432 { 433 _preciseAction = action; 434 } 435 436 /// <summary> 437 /// Register an action to perform when the region is written to. 438 /// This action will not be removed when it is called - it is called each time the dirty flag is set. 439 /// </summary> 440 /// <param name="action">Action to call on dirty</param> 441 public void RegisterDirtyEvent(Action action) 442 { 443 OnDirty += action; 444 } 445 446 /// <summary> 447 /// Add a child virtual region to this handle. 448 /// </summary> 449 /// <param name="region">Virtual region to add as a child</param> 450 internal void AddChild(VirtualRegion region) 451 { 452 if (region.Guest) 453 { 454 _guestRegions.Add(region); 455 } 456 else 457 { 458 _regions.Add(region); 459 } 460 461 _allRegions.Add(region); 462 } 463 464 /// <summary> 465 /// Signal that this handle has been mapped or unmapped. 466 /// </summary> 467 /// <param name="mapped">True if the handle has been mapped, false if unmapped</param> 468 internal void SignalMappingChanged(bool mapped) 469 { 470 if (Unmapped == mapped) 471 { 472 Unmapped = !mapped; 473 474 if (Unmapped) 475 { 476 ClearVolatile(); 477 Dirty = false; 478 } 479 } 480 } 481 482 /// <summary> 483 /// Check if this region overlaps with another. 484 /// </summary> 485 /// <param name="address">Base address</param> 486 /// <param name="size">Size of the region</param> 487 /// <returns>True if overlapping, false otherwise</returns> 488 public bool OverlapsWith(ulong address, ulong size) 489 { 490 return Address < address + size && address < EndAddress; 491 } 492 493 /// <summary> 494 /// Determines if this handle's memory range matches another exactly. 495 /// </summary> 496 /// <param name="other">The other handle</param> 497 /// <returns>True on a match, false otherwise</returns> 498 public bool RangeEquals(RegionHandle other) 499 { 500 return RealAddress == other.RealAddress && RealSize == other.RealSize; 501 } 502 503 /// <summary> 504 /// Dispose the handle. Within the tracking lock, this removes references from virtual regions. 505 /// </summary> 506 public void Dispose() 507 { 508 ObjectDisposedException.ThrowIf(_disposed, this); 509 510 GC.SuppressFinalize(this); 511 512 _disposed = true; 513 514 lock (_tracking.TrackingLock) 515 { 516 foreach (VirtualRegion region in _allRegions) 517 { 518 region.RemoveHandle(this); 519 } 520 } 521 } 522 } 523 }