aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Memory/Tracking
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Memory/Tracking')
-rw-r--r--Ryujinx.Memory/Tracking/IMultiRegionHandle.cs7
-rw-r--r--Ryujinx.Memory/Tracking/IRegionHandle.cs1
-rw-r--r--Ryujinx.Memory/Tracking/MemoryTracking.cs20
-rw-r--r--Ryujinx.Memory/Tracking/MultiRegionHandle.cs16
-rw-r--r--Ryujinx.Memory/Tracking/RegionHandle.cs77
-rw-r--r--Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs11
-rw-r--r--Ryujinx.Memory/Tracking/VirtualRegion.cs22
7 files changed, 141 insertions, 13 deletions
diff --git a/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs
index 357b8c5c..71bd602f 100644
--- a/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/IMultiRegionHandle.cs
@@ -10,6 +10,13 @@ namespace Ryujinx.Memory.Tracking
bool Dirty { get; }
/// <summary>
+ /// Force the range of handles to be dirty, without reprotecting.
+ /// </summary>
+ /// <param name="address">Start address of the range</param>
+ /// <param name="size">Size of the range</param>
+ public void ForceDirty(ulong address, ulong size);
+
+ /// <summary>
/// Check if any part of the region has been modified, and perform an action for each.
/// Contiguous modified regions are combined.
/// </summary>
diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs
index cd33e5c8..ec802cb3 100644
--- a/Ryujinx.Memory/Tracking/IRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/IRegionHandle.cs
@@ -10,6 +10,7 @@ namespace Ryujinx.Memory.Tracking
ulong Size { get; }
ulong EndAddress { get; }
+ void ForceDirty();
void Reprotect(bool asDirty = false);
void RegisterAction(RegionSignal action);
}
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
index 425552f8..70951e8c 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -9,7 +9,7 @@ namespace Ryujinx.Memory.Tracking
public class MemoryTracking
{
private readonly IVirtualMemoryManager _memoryManager;
- private readonly MemoryBlock _block;
+ private readonly InvalidAccessHandler _invalidAccessHandler;
// Only use these from within the lock.
private readonly NonOverlappingRangeList<VirtualRegion> _virtualRegions;
@@ -25,8 +25,6 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
internal object TrackingLock = new object();
- public bool EnablePhysicalProtection { get; set; }
-
/// <summary>
/// Create a new tracking structure for the given "physical" memory block,
/// with a given "virtual" memory manager that will provide mappings and virtual memory protection.
@@ -34,11 +32,11 @@ namespace Ryujinx.Memory.Tracking
/// <param name="memoryManager">Virtual memory manager</param>
/// <param name="block">Physical memory block</param>
/// <param name="pageSize">Page size of the virtual memory space</param>
- public MemoryTracking(IVirtualMemoryManager memoryManager, MemoryBlock block, int pageSize)
+ public MemoryTracking(IVirtualMemoryManager memoryManager, int pageSize, InvalidAccessHandler invalidAccessHandler = null)
{
_memoryManager = memoryManager;
- _block = block;
_pageSize = pageSize;
+ _invalidAccessHandler = invalidAccessHandler;
_virtualRegions = new NonOverlappingRangeList<VirtualRegion>();
}
@@ -56,9 +54,8 @@ namespace Ryujinx.Memory.Tracking
/// Should be called after the mapping is complete.
/// </summary>
/// <param name="va">Virtual memory address</param>
- /// <param name="pa">Physical memory address</param>
/// <param name="size">Size to be mapped</param>
- public void Map(ulong va, ulong pa, ulong size)
+ public void Map(ulong va, ulong size)
{
// A mapping may mean we need to re-evaluate each VirtualRegion's affected area.
// Find all handles that overlap with the range, we need to recalculate their physical regions
@@ -208,6 +205,15 @@ namespace Ryujinx.Memory.Tracking
if (count == 0)
{
+ if (!_memoryManager.IsMapped(address))
+ {
+ _invalidAccessHandler?.Invoke(address);
+
+ // We can't continue - it's impossible to remove protection from the page.
+ // Even if the access handler wants us to continue, we wouldn't be able to.
+ throw new InvalidMemoryRegionException();
+ }
+
_memoryManager.TrackingReprotect(address & ~(ulong)(_pageSize - 1), (ulong)_pageSize, MemoryPermission.ReadAndWrite);
return false; // We can't handle this - it's probably a real invalid access.
}
diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
index df154bc2..1f09807a 100644
--- a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
@@ -34,6 +34,20 @@ namespace Ryujinx.Memory.Tracking
Size = size;
}
+ public void ForceDirty(ulong address, ulong size)
+ {
+ Dirty = true;
+
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ for (int i = startHandle; i <= lastHandle; i++)
+ {
+ _handles[i].SequenceNumber--;
+ _handles[i].ForceDirty();
+ }
+ }
+
public void SignalWrite()
{
Dirty = true;
@@ -98,7 +112,7 @@ namespace Ryujinx.Memory.Tracking
{
RegionHandle handle = _handles[i];
- if (handle.Dirty && sequenceNumber != handle.SequenceNumber)
+ if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile())
{
rgSize += handle.Size;
handle.Reprotect();
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 5c32fba4..69d77977 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -11,6 +11,17 @@ namespace Ryujinx.Memory.Tracking
/// </summary>
public class RegionHandle : IRegionHandle, IRange
{
+ /// <summary>
+ /// If more than this number of checks have been performed on a dirty flag since its last reprotect,
+ /// then it is dirtied infrequently.
+ /// </summary>
+ private static int CheckCountForInfrequent = 3;
+
+ /// <summary>
+ /// Number of frequent dirty/consume in a row to make this handle volatile.
+ /// </summary>
+ private static int VolatileThreshold = 5;
+
public bool Dirty { get; private set; }
public bool Unmapped { get; private set; }
@@ -28,6 +39,10 @@ namespace Ryujinx.Memory.Tracking
private readonly MemoryTracking _tracking;
private bool _disposed;
+ private int _checkCount = 0;
+ private int _volatileCount = 0;
+ private bool _volatile;
+
internal MemoryPermission RequiredPermission => _preAction != null ? MemoryPermission.None : (Dirty ? MemoryPermission.ReadAndWrite : MemoryPermission.Read);
internal RegionSignal PreAction => _preAction;
@@ -56,6 +71,25 @@ namespace Ryujinx.Memory.Tracking
}
/// <summary>
+ /// Clear the volatile state of this handle.
+ /// </summary>
+ private void ClearVolatile()
+ {
+ _volatileCount = 0;
+ _volatile = false;
+ }
+
+ /// <summary>
+ /// Check if this handle is dirty, or if it is volatile. (changes very often)
+ /// </summary>
+ /// <returns>True if the handle is dirty or volatile, false otherwise</returns>
+ public bool DirtyOrVolatile()
+ {
+ _checkCount++;
+ return Dirty || _volatile;
+ }
+
+ /// <summary>
/// Signal that a memory action occurred within this handle's virtual regions.
/// </summary>
/// <param name="write">Whether the region was written to or read</param>
@@ -77,18 +111,56 @@ namespace Ryujinx.Memory.Tracking
}
/// <summary>
+ /// Force this handle to be dirty, without reprotecting.
+ /// </summary>
+ public void ForceDirty()
+ {
+ Dirty = true;
+ }
+
+ /// <summary>
/// Consume the dirty flag for this handle, and reprotect so it can be set on the next write.
/// </summary>
public void Reprotect(bool asDirty = false)
{
+ if (_volatile) return;
+
Dirty = asDirty;
+
+ bool protectionChanged = false;
+
lock (_tracking.TrackingLock)
{
foreach (VirtualRegion region in _regions)
{
- region.UpdateProtection();
+ protectionChanged |= region.UpdateProtection();
}
}
+
+ if (!protectionChanged)
+ {
+ // Counteract the check count being incremented when this handle was forced dirty.
+ // It doesn't count for protected write tracking.
+
+ _checkCount--;
+ }
+ else if (!asDirty)
+ {
+ if (_checkCount > 0 && _checkCount < CheckCountForInfrequent)
+ {
+ if (++_volatileCount >= VolatileThreshold && _preAction == null)
+ {
+ _volatile = true;
+ return;
+ }
+ }
+ else
+ {
+ _volatileCount = 0;
+ }
+
+ _checkCount = 0;
+ }
}
/// <summary>
@@ -98,6 +170,8 @@ namespace Ryujinx.Memory.Tracking
/// <param name="action">Action to call on read or write</param>
public void RegisterAction(RegionSignal action)
{
+ ClearVolatile();
+
RegionSignal lastAction = Interlocked.Exchange(ref _preAction, action);
if (lastAction == null && action != lastAction)
{
@@ -142,6 +216,7 @@ namespace Ryujinx.Memory.Tracking
if (Unmapped)
{
+ ClearVolatile();
Dirty = false;
}
}
diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
index 8bc10c41..eabbd723 100644
--- a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
@@ -41,6 +41,17 @@ namespace Ryujinx.Memory.Tracking
Dirty = true;
}
+ public void ForceDirty(ulong address, ulong size)
+ {
+ foreach (var handle in _handles)
+ {
+ if (handle != null && handle.OverlapsWith(address, size))
+ {
+ handle.ForceDirty();
+ }
+ }
+ }
+
public void RegisterAction(RegionSignal action)
{
foreach (var handle in _handles)
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
index 696d3560..e758f38e 100644
--- a/Ryujinx.Memory/Tracking/VirtualRegion.cs
+++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -11,9 +11,11 @@ namespace Ryujinx.Memory.Tracking
public List<RegionHandle> Handles = new List<RegionHandle>();
private readonly MemoryTracking _tracking;
+ private MemoryPermission _lastPermission;
- public VirtualRegion(MemoryTracking tracking, ulong address, ulong size) : base(address, size)
+ public VirtualRegion(MemoryTracking tracking, ulong address, ulong size, MemoryPermission lastPermission = MemoryPermission.Invalid) : base(address, size)
{
+ _lastPermission = lastPermission;
_tracking = tracking;
}
@@ -33,6 +35,8 @@ namespace Ryujinx.Memory.Tracking
/// <param name="mapped">True if the region has been mapped, false if unmapped</param>
public void SignalMappingChanged(bool mapped)
{
+ _lastPermission = MemoryPermission.Invalid;
+
foreach (RegionHandle handle in Handles)
{
handle.SignalMappingChanged(mapped);
@@ -61,9 +65,19 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// Updates the protection for this virtual region.
/// </summary>
- public void UpdateProtection()
+ public bool UpdateProtection()
{
- _tracking.ProtectVirtualRegion(this, GetRequiredPermission());
+ MemoryPermission permission = GetRequiredPermission();
+
+ if (_lastPermission != permission)
+ {
+ _tracking.ProtectVirtualRegion(this, permission);
+ _lastPermission = permission;
+
+ return true;
+ }
+
+ return false;
}
/// <summary>
@@ -85,7 +99,7 @@ namespace Ryujinx.Memory.Tracking
public override INonOverlappingRange Split(ulong splitAddress)
{
- VirtualRegion newRegion = new VirtualRegion(_tracking, splitAddress, EndAddress - splitAddress);
+ VirtualRegion newRegion = new VirtualRegion(_tracking, splitAddress, EndAddress - splitAddress, _lastPermission);
Size = splitAddress - Address;
// The new region inherits all of our parents.