Add NCE code

This commit is contained in:
gdk 2023-07-03 19:28:05 -03:00 committed by Emmanuel Hansen
parent a1e34041fa
commit 0970972f0d
40 changed files with 2702 additions and 40 deletions

View File

@ -115,7 +115,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public bool IsOpenGLAvailable => !OperatingSystem.IsMacOS();
public bool IsHypervisorAvailable => OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
public bool IsHypervisorAvailable => RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
public bool DirectoryChanged
{

View File

@ -32,6 +32,11 @@ namespace Ryujinx.Cpu.AppleHv
{
}
/// <inheritdoc/>
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
{
}
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
{
return new DummyDiskCacheLoadState();

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.AppleHv
}
/// <inheritdoc/>
public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
{
return new HvCpuContext(_tickSource, memoryManager, for64Bit);
}

View File

@ -16,7 +16,7 @@ namespace Ryujinx.Cpu.AppleHv
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary>
[SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public class HvMemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@ -182,6 +182,11 @@ namespace Ryujinx.Cpu.AppleHv
}
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
{
}
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{

View File

@ -0,0 +1,15 @@
using System;
namespace Ryujinx.Cpu.Jit
{
class DiskCacheLoadState : IDiskCacheLoadState
{
/// <inheritdoc/>
public event Action<LoadState, int, int> StateChanged;
/// <inheritdoc/>
public void Cancel()
{
}
}
}

View File

@ -38,6 +38,8 @@ namespace Ryujinx.Cpu
/// <param name="size">Size of the region to be invalidated</param>
void InvalidateCacheRegion(ulong address, ulong size);
void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize);
/// <summary>
/// Loads cached code from disk for a given application.
/// </summary>

View File

@ -1,5 +1,3 @@
using ARMeilleure.Memory;
namespace Ryujinx.Cpu
{
/// <summary>
@ -13,6 +11,6 @@ namespace Ryujinx.Cpu
/// <param name="memoryManager">Memory manager for the address space of the context</param>
/// <param name="for64Bit">Indicates if the context will be used to run 64-bit or 32-bit Arm code</param>
/// <returns>CPU context</returns>
ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit);
ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit);
}
}

View File

@ -0,0 +1,19 @@
using ARMeilleure.Memory;
using Ryujinx.Memory;
namespace Ryujinx.Cpu
{
/// <summary>
/// CPU memory manager interface.
/// </summary>
public interface ICpuMemoryManager : IMemoryManager
{
/// <summary>
/// Reprotects a previously mapped range of virtual memory.
/// </summary>
/// <param name="va">Virtual address of the range to be reprotected</param>
/// <param name="size">Size of the range to be reprotected</param>
/// <param name="permission">New protection of the memory range</param>
void Reprotect(ulong va, ulong size, MemoryPermission permission);
}
}

View File

@ -46,6 +46,11 @@ namespace Ryujinx.Cpu.Jit
_translator.InvalidateJitCacheRegion(address, size);
}
/// <inheritdoc/>
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
{
}
/// <inheritdoc/>
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
{

View File

@ -12,7 +12,7 @@ namespace Ryujinx.Cpu.Jit
}
/// <inheritdoc/>
public ICpuContext CreateCpuContext(IMemoryManager memoryManager, bool for64Bit)
public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
{
return new JitCpuContext(_tickSource, memoryManager, for64Bit);
}

View File

@ -14,7 +14,7 @@ namespace Ryujinx.Cpu.Jit
/// <summary>
/// Represents a CPU memory manager.
/// </summary>
public sealed class MemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public sealed class MemoryManager : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@ -126,6 +126,11 @@ namespace Ryujinx.Cpu.Jit
}
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
{
}
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{

View File

@ -13,7 +13,7 @@ namespace Ryujinx.Cpu.Jit
/// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary>
public sealed class MemoryManagerHostMapped : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
public sealed class MemoryManagerHostMapped : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
@ -194,6 +194,11 @@ namespace Ryujinx.Cpu.Jit
}
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
{
}
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{

View File

@ -46,6 +46,20 @@ namespace Ryujinx.Cpu
}
}
public MemoryEhMeilleure(ulong asSize, MemoryTracking tracking)
{
_baseAddress = 0UL;
ulong endAddress = asSize;
_trackingEvent = new TrackingEventDelegate(tracking.VirtualMemoryEvent);
bool added = NativeSignalHandler.AddTrackedRegion((nuint)_baseAddress, (nuint)endAddress, Marshal.GetFunctionPointerForDelegate(_trackingEvent));
if (!added)
{
throw new InvalidOperationException("Number of allowed tracked regions exceeded.");
}
}
public void Dispose()
{
GC.SuppressFinalize(this);

View File

@ -0,0 +1,848 @@
using ARMeilleure.Memory;
using Ryujinx.Memory;
using Ryujinx.Memory.Range;
using Ryujinx.Memory.Tracking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Ryujinx.Cpu.Nce
{
/// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto a host virtual region.
/// </summary>
public sealed class MemoryManagerNative : MemoryManagerBase, ICpuMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{
public const int PageBits = 12;
public const int PageSize = 1 << PageBits;
public const int PageMask = PageSize - 1;
public const int PageToPteShift = 5; // 32 pages (2 bits each) in one ulong page table entry.
public const ulong BlockMappedMask = 0x5555555555555555; // First bit of each table entry set.
private enum HostMappedPtBits : ulong
{
Unmapped = 0,
Mapped,
WriteTracked,
ReadWriteTracked,
MappedReplicated = 0x5555555555555555,
WriteTrackedReplicated = 0xaaaaaaaaaaaaaaaa,
ReadWriteTrackedReplicated = ulong.MaxValue
}
private readonly InvalidAccessHandler _invalidAccessHandler;
private readonly MemoryBlock _addressSpace;
private readonly MemoryBlock _addressSpaceMirror;
private readonly ulong _addressSpaceSize;
private readonly MemoryBlock _backingMemory;
private readonly PageTable<ulong> _pageTable;
private readonly MemoryEhMeilleure _memoryEh;
private readonly ulong[] _pageBitmap;
/// <inheritdoc/>
public bool Supports4KBPages => MemoryBlock.GetPageSize() == PageSize;
public int AddressSpaceBits { get; }
public IntPtr PageTablePointer => IntPtr.Zero;
public ulong ReservedSize => (ulong)_addressSpace.Pointer.ToInt64();
public MemoryManagerType Type => MemoryManagerType.HostMappedUnsafe;
public MemoryTracking Tracking { get; }
public event Action<ulong, ulong> UnmapEvent;
/// <summary>
/// Creates a new instance of the host mapped memory manager.
/// </summary>
/// <param name="addressSpace">Address space instance to use</param>
/// <param name="backingMemory">Physical backing memory where virtual memory will be mapped to</param>
/// <param name="addressSpaceSize">Size of the address space</param>
/// <param name="invalidAccessHandler">Optional function to handle invalid memory accesses</param>
public MemoryManagerNative(
AddressSpace addressSpace,
MemoryBlock backingMemory,
ulong addressSpaceSize,
InvalidAccessHandler invalidAccessHandler = null)
{
_backingMemory = backingMemory;
_pageTable = new PageTable<ulong>();
_invalidAccessHandler = invalidAccessHandler;
_addressSpaceSize = addressSpaceSize;
ulong asSize = PageSize;
int asBits = PageBits;
while (asSize < addressSpaceSize)
{
asSize <<= 1;
asBits++;
}
AddressSpaceBits = asBits;
_pageBitmap = new ulong[1 << (AddressSpaceBits - (PageBits + PageToPteShift))];
_addressSpace = addressSpace.Base;
_addressSpaceMirror = addressSpace.Mirror;
Tracking = new MemoryTracking(this, PageSize, invalidAccessHandler);
_memoryEh = new MemoryEhMeilleure(asSize, Tracking);
}
/// <summary>
/// Checks if the virtual address is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address</param>
/// <returns>True if the virtual address is part of the addressable space</returns>
private bool ValidateAddress(ulong va)
{
return va < _addressSpaceSize;
}
/// <summary>
/// Checks if the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <returns>True if the combination of virtual address and size is part of the addressable space</returns>
private bool ValidateAddressAndSize(ulong va, ulong size)
{
ulong endVa = va + size;
return endVa >= va && endVa >= size && endVa <= _addressSpaceSize;
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
/// <exception cref="InvalidMemoryRegionException">Throw when the memory region specified outside the addressable space</exception>
private void AssertValidAddressAndSize(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size))
{
throw new InvalidMemoryRegionException($"va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <summary>
/// Ensures the combination of virtual address and size is part of the addressable space and fully mapped.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range in bytes</param>
private void AssertMapped(ulong va, ulong size)
{
if (!ValidateAddressAndSize(va, size) || !IsRangeMappedImpl(va, size))
{
throw new InvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
/// <inheritdoc/>
public void Map(ulong va, ulong pa, ulong size, MemoryMapFlags flags)
{
AssertValidAddressAndSize(va, size);
_addressSpace.MapView(_backingMemory, pa, AddressToOffset(va), size);
_addressSpaceMirror.MapView(_backingMemory, pa, AddressToOffset(va), size);
AddMapping(va, size);
PtMap(va, pa, size);
Tracking.Map(va, size);
}
/// <inheritdoc/>
public void MapForeign(ulong va, nuint hostPointer, ulong size)
{
throw new NotSupportedException();
}
/// <inheritdoc/>
public void Unmap(ulong va, ulong size)
{
AssertValidAddressAndSize(va, size);
UnmapEvent?.Invoke(va, size);
Tracking.Unmap(va, size);
RemoveMapping(va, size);
PtUnmap(va, size);
_addressSpace.UnmapView(_backingMemory, AddressToOffset(va), size);
_addressSpaceMirror.UnmapView(_backingMemory, AddressToOffset(va), size);
}
private void PtMap(ulong va, ulong pa, ulong size)
{
while (size != 0)
{
_pageTable.Map(va, pa);
va += PageSize;
pa += PageSize;
size -= PageSize;
}
}
private void PtUnmap(ulong va, ulong size)
{
while (size != 0)
{
_pageTable.Unmap(va);
va += PageSize;
size -= PageSize;
}
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
{
_addressSpace.Reprotect(AddressToOffset(va), size, permission);
}
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{
try
{
AssertMapped(va, (ulong)Unsafe.SizeOf<T>());
return _addressSpaceMirror.Read<T>(AddressToOffset(va));
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return default;
}
}
/// <inheritdoc/>
public T ReadTracked<T>(ulong va) where T : unmanaged
{
try
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), false);
return Read<T>(va);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return default;
}
}
/// <inheritdoc/>
public void Read(ulong va, Span<byte> data)
{
try
{
AssertMapped(va, (ulong)data.Length);
_addressSpaceMirror.Read(AddressToOffset(va), data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
public void Write<T>(ulong va, T value) where T : unmanaged
{
try
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), write: true);
_addressSpaceMirror.Write(AddressToOffset(va), value);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
public void Write(ulong va, ReadOnlySpan<byte> data)
{
try
{
SignalMemoryTracking(va, (ulong)data.Length, write: true);
_addressSpaceMirror.Write(AddressToOffset(va), data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
public void WriteUntracked(ulong va, ReadOnlySpan<byte> data)
{
try
{
AssertMapped(va, (ulong)data.Length);
_addressSpaceMirror.Write(AddressToOffset(va), data);
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
}
}
/// <inheritdoc/>
public bool WriteWithRedundancyCheck(ulong va, ReadOnlySpan<byte> data)
{
try
{
SignalMemoryTracking(va, (ulong)data.Length, false);
Span<byte> target = _addressSpaceMirror.GetSpan(AddressToOffset(va), data.Length);
bool changed = !data.SequenceEqual(target);
if (changed)
{
data.CopyTo(target);
}
return changed;
}
catch (InvalidMemoryRegionException)
{
if (_invalidAccessHandler == null || !_invalidAccessHandler(va))
{
throw;
}
return true;
}
}
/// <inheritdoc/>
public ReadOnlySpan<byte> GetSpan(ulong va, int size, bool tracked = false)
{
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, write: false);
}
else
{
AssertMapped(va, (ulong)size);
}
if (size == 0)
{
return ReadOnlySpan<byte>.Empty;
}
return _addressSpaceMirror.GetSpan(AddressToOffset(va), size);
}
/// <inheritdoc/>
public WritableRegion GetWritableRegion(ulong va, int size, bool tracked = false)
{
if (tracked)
{
SignalMemoryTracking(va, (ulong)size, true);
}
else
{
AssertMapped(va, (ulong)size);
}
return _addressSpaceMirror.GetWritableRegion(AddressToOffset(va), size);
}
/// <inheritdoc/>
public ref T GetRef<T>(ulong va) where T : unmanaged
{
SignalMemoryTracking(va, (ulong)Unsafe.SizeOf<T>(), true);
return ref _addressSpaceMirror.GetRef<T>(AddressToOffset(va));
}
/// <inheritdoc/>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsMapped(ulong va)
{
return ValidateAddress(va) && IsMappedImpl(va);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private bool IsMappedImpl(ulong va)
{
ulong page = va >> PageBits;
int bit = (int)((page & 31) << 1);
int pageIndex = (int)(page >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
return ((pte >> bit) & 3) != 0;
}
/// <inheritdoc/>
public bool IsRangeMapped(ulong va, ulong size)
{
AssertValidAddressAndSize(va, size);
return IsRangeMappedImpl(va, size);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GetPageBlockRange(ulong pageStart, ulong pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex)
{
startMask = ulong.MaxValue << ((int)(pageStart & 31) << 1);
endMask = ulong.MaxValue >> (64 - ((int)(pageEnd & 31) << 1));
pageIndex = (int)(pageStart >> PageToPteShift);
pageEndIndex = (int)((pageEnd - 1) >> PageToPteShift);
}
private bool IsRangeMappedImpl(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
if (pages == 1)
{
return IsMappedImpl(va);
}
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
// Check if either bit in each 2 bit page entry is set.
// OR the block with itself shifted down by 1, and check the first bit of each entry.
ulong mask = BlockMappedMask & startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
pte |= pte >> 1;
if ((pte & mask) != mask)
{
return false;
}
mask = BlockMappedMask;
}
return true;
}
/// <inheritdoc/>
public IEnumerable<HostMemoryRange> GetHostRegions(ulong va, ulong size)
{
AssertValidAddressAndSize(va, size);
return Enumerable.Repeat(new HostMemoryRange((nuint)(ulong)_addressSpaceMirror.GetPointer(AddressToOffset(va), size), size), 1);
}
/// <inheritdoc/>
public IEnumerable<MemoryRange> GetPhysicalRegions(ulong va, ulong size)
{
int pages = GetPagesCount(va, (uint)size, out va);
var regions = new List<MemoryRange>();
ulong regionStart = GetPhysicalAddressChecked(va);
ulong regionSize = PageSize;
for (int page = 0; page < pages - 1; page++)
{
if (!ValidateAddress(va + PageSize))
{
return null;
}
ulong newPa = GetPhysicalAddressChecked(va + PageSize);
if (GetPhysicalAddressChecked(va) + PageSize != newPa)
{
regions.Add(new MemoryRange(regionStart, regionSize));
regionStart = newPa;
regionSize = 0;
}
va += PageSize;
regionSize += PageSize;
}
regions.Add(new MemoryRange(regionStart, regionSize));
return regions;
}
private ulong GetPhysicalAddressChecked(ulong va)
{
if (!IsMapped(va))
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}");
}
return GetPhysicalAddressInternal(va);
}
private ulong GetPhysicalAddressInternal(ulong va)
{
return _pageTable.Read(va) + (va & PageMask);
}
/// <inheritdoc/>
/// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks>
public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
{
AssertValidAddressAndSize(va, size);
if (precise)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
return;
}
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong tag = (ulong)(write ? HostMappedPtBits.WriteTracked : HostMappedPtBits.ReadWriteTracked);
int bit = (int)((pageStart & 31) << 1);
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte = Volatile.Read(ref pageRef);
ulong state = ((pte >> bit) & 3);
if (state >= tag)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
return;
}
else if (state == 0)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong anyTrackingTag = (ulong)HostMappedPtBits.WriteTrackedReplicated;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte = Volatile.Read(ref pageRef);
ulong mappedMask = mask & BlockMappedMask;
ulong mappedPte = pte | (pte >> 1);
if ((mappedPte & mappedMask) != mappedMask)
{
ThrowInvalidMemoryRegionException($"Not mapped: va=0x{va:X16}, size=0x{size:X16}");
}
pte &= mask;
if ((pte & anyTrackingTag) != 0) // Search for any tracking.
{
// Writes trigger any tracking.
// Only trigger tracking from reads if both bits are set on any page.
if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
{
Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
break;
}
}
mask = ulong.MaxValue;
}
}
}
/// <summary>
/// Computes the number of pages in a virtual address range.
/// </summary>
/// <param name="va">Virtual address of the range</param>
/// <param name="size">Size of the range</param>
/// <param name="startVa">The virtual address of the beginning of the first page</param>
/// <remarks>This function does not differentiate between allocated and unallocated pages.</remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int GetPagesCount(ulong va, ulong size, out ulong startVa)
{
// WARNING: Always check if ulong does not overflow during the operations.
startVa = va & ~(ulong)PageMask;
ulong vaSpan = (va - startVa + size + PageMask) & ~(ulong)PageMask;
return (int)(vaSpan / PageSize);
}
/// <inheritdoc/>
public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection)
{
// Protection is inverted on software pages, since the default value is 0.
protection = (~protection) & MemoryPermission.ReadAndWrite;
int pages = GetPagesCount(va, size, out va);
ulong pageStart = va >> PageBits;
if (pages == 1)
{
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.Mapped,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTracked,
_ => (ulong)HostMappedPtBits.ReadWriteTracked,
};
int bit = (int)((pageStart & 31) << 1);
ulong tagMask = 3UL << bit;
ulong invTagMask = ~tagMask;
ulong tag = protTag << bit;
int pageIndex = (int)(pageStart >> PageToPteShift);
ref ulong pageRef = ref _pageBitmap[pageIndex];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while ((pte & tagMask) != 0 && Interlocked.CompareExchange(ref pageRef, (pte & invTagMask) | tag, pte) != pte);
}
else
{
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
ulong protTag = protection switch
{
MemoryPermission.None => (ulong)HostMappedPtBits.MappedReplicated,
MemoryPermission.Write => (ulong)HostMappedPtBits.WriteTrackedReplicated,
_ => (ulong)HostMappedPtBits.ReadWriteTrackedReplicated,
};
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Change the protection of all 2 bit entries that are mapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask &= mask; // Only update mapped pages within the given range.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & (~mappedMask)) | (protTag & mappedMask), pte) != pte);
mask = ulong.MaxValue;
}
}
protection = protection switch
{
MemoryPermission.None => MemoryPermission.ReadAndWrite,
MemoryPermission.Write => MemoryPermission.Read,
_ => MemoryPermission.None
};
_addressSpace.Reprotect(AddressToOffset(va), size, protection, false);
}
/// <inheritdoc/>
public RegionHandle BeginTracking(ulong address, ulong size, int id)
{
return Tracking.BeginTracking(address, size, id);
}
/// <inheritdoc/>
public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
{
return Tracking.BeginGranularTracking(address, size, handles, granularity, id);
}
/// <inheritdoc/>
public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
{
return Tracking.BeginSmartGranularTracking(address, size, granularity, id);
}
/// <summary>
/// Adds the given address mapping to the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be mapped</param>
private void AddMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask &= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
ulong mappedMask;
// Map all 2-bit entries that are unmapped.
do
{
pte = Volatile.Read(ref pageRef);
mappedMask = pte | (pte >> 1);
mappedMask |= (mappedMask & BlockMappedMask) << 1;
mappedMask |= ~mask; // Treat everything outside the range as mapped, thus unchanged.
}
while (Interlocked.CompareExchange(ref pageRef, (pte & mappedMask) | (BlockMappedMask & (~mappedMask)), pte) != pte);
mask = ulong.MaxValue;
}
}
/// <summary>
/// Removes the given address mapping from the page table.
/// </summary>
/// <param name="va">Virtual memory address</param>
/// <param name="size">Size to be unmapped</param>
private void RemoveMapping(ulong va, ulong size)
{
int pages = GetPagesCount(va, size, out _);
ulong pageStart = va >> PageBits;
ulong pageEnd = pageStart + (ulong)pages;
GetPageBlockRange(pageStart, pageEnd, out ulong startMask, out ulong endMask, out int pageIndex, out int pageEndIndex);
startMask = ~startMask;
endMask = ~endMask;
ulong mask = startMask;
while (pageIndex <= pageEndIndex)
{
if (pageIndex == pageEndIndex)
{
mask |= endMask;
}
ref ulong pageRef = ref _pageBitmap[pageIndex++];
ulong pte;
do
{
pte = Volatile.Read(ref pageRef);
}
while (Interlocked.CompareExchange(ref pageRef, pte & mask, pte) != pte);
mask = 0;
}
}
private ulong AddressToOffset(ulong address)
{
if (address < ReservedSize)
{
throw new ArgumentException($"Invalid address 0x{address:x16}");
}
return address - ReservedSize;
}
/// <summary>
/// Disposes of resources used by the memory manager.
/// </summary>
protected override void Destroy()
{
_addressSpace.Dispose();
_addressSpaceMirror.Dispose();
_memoryEh.Dispose();
}
private static void ThrowInvalidMemoryRegionException(string message) => throw new InvalidMemoryRegionException(message);
}
}

View File

@ -0,0 +1,409 @@
using System;
namespace Ryujinx.Cpu.Nce
{
static class NceAsmTable
{
public static uint[] GetTpidrEl0Code = new uint[]
{
GetMrsTpidrEl0(0), // mrs x0, tpidr_el0
0xd65f03c0u, // ret
};
public static uint[] ThreadStartCode = new uint[]
{
0xa9ae53f3u, // stp x19, x20, [sp, #-288]!
0xa9015bf5u, // stp x21, x22, [sp, #16]
0xa90263f7u, // stp x23, x24, [sp, #32]
0xa9036bf9u, // stp x25, x26, [sp, #48]
0xa90473fbu, // stp x27, x28, [sp, #64]
0xa9057bfdu, // stp x29, x30, [sp, #80]
0x6d0627e8u, // stp d8, d9, [sp, #96]
0x6d072feau, // stp d10, d11, [sp, #112]
0x6d0837ecu, // stp d12, d13, [sp, #128]
0x6d093feeu, // stp d14, d15, [sp, #144]
0x6d0a47f0u, // stp d16, d17, [sp, #160]
0x6d0b4ff2u, // stp d18, d19, [sp, #176]
0x6d0c57f4u, // stp d20, d21, [sp, #192]
0x6d0d5ff6u, // stp d22, d23, [sp, #208]
0x6d0e67f8u, // stp d24, d25, [sp, #224]
0x6d0f6ffau, // stp d26, d27, [sp, #240]
0x6d1077fcu, // stp d28, d29, [sp, #256]
0x6d117ffeu, // stp d30, d31, [sp, #272]
0xb9031c1fu, // str wzr, [x0, #796]
0x910003e1u, // mov x1, sp
0xf9019001u, // str x1, [x0, #800]
0xa9410c02u, // ldp x2, x3, [x0, #16]
0xa9421404u, // ldp x4, x5, [x0, #32]
0xa9431c06u, // ldp x6, x7, [x0, #48]
0xa9442408u, // ldp x8, x9, [x0, #64]
0xa9452c0au, // ldp x10, x11, [x0, #80]
0xa946340cu, // ldp x12, x13, [x0, #96]
0xa9473c0eu, // ldp x14, x15, [x0, #112]
0xa9484410u, // ldp x16, x17, [x0, #128]
0xa9494c12u, // ldp x18, x19, [x0, #144]
0xa94a5414u, // ldp x20, x21, [x0, #160]
0xa94b5c16u, // ldp x22, x23, [x0, #176]
0xa94c6418u, // ldp x24, x25, [x0, #192]
0xa94d6c1au, // ldp x26, x27, [x0, #208]
0xa94e741cu, // ldp x28, x29, [x0, #224]
0xad480400u, // ldp q0, q1, [x0, #256]
0xad490c02u, // ldp q2, q3, [x0, #288]
0xad4a1404u, // ldp q4, q5, [x0, #320]
0xad4b1c06u, // ldp q6, q7, [x0, #352]
0xad4c2408u, // ldp q8, q9, [x0, #384]
0xad4d2c0au, // ldp q10, q11, [x0, #416]
0xad4e340cu, // ldp q12, q13, [x0, #448]
0xad4f3c0eu, // ldp q14, q15, [x0, #480]
0xad504410u, // ldp q16, q17, [x0, #512]
0xad514c12u, // ldp q18, q19, [x0, #544]
0xad525414u, // ldp q20, q21, [x0, #576]
0xad535c16u, // ldp q22, q23, [x0, #608]
0xad546418u, // ldp q24, q25, [x0, #640]
0xad556c1au, // ldp q26, q27, [x0, #672]
0xad56741cu, // ldp q28, q29, [x0, #704]
0xad577c1eu, // ldp q30, q31, [x0, #736]
0xa94f041eu, // ldp x30, x1, [x0, #240]
0x9100003fu, // mov sp, x1
0xa9400400u, // ldp x0, x1, [x0]
0xd61f03c0u, // br x30
};
public static uint[] ExceptionHandlerEntryCode = new uint[]
{
0xa9bc53f3u, // stp x19, x20, [sp, #-64]!
0xa9015bf5u, // stp x21, x22, [sp, #16]
0xa90263f7u, // stp x23, x24, [sp, #32]
0xf9001bf9u, // str x25, [sp, #48]
0xaa0003f3u, // mov x19, x0
0xaa0103f4u, // mov x20, x1
0xaa0203f5u, // mov x21, x2
0x910003f6u, // mov x22, sp
0xaa1e03f7u, // mov x23, x30
0xd2800018u, // mov x24, #0x0
0xf2a00018u, // movk x24, #0x0, lsl #16
0xf2c00018u, // movk x24, #0x0, lsl #32
0xf2e00018u, // movk x24, #0x0, lsl #48
0xf85f8319u, // ldur x25, [x24, #-8]
0x8b191319u, // add x25, x24, x25, lsl #4
GetMrsTpidrEl0(1), // mrs x1, tpidr_el0
0xeb19031fu, // cmp x24, x25
0x540000a0u, // b.eq 13c <ExceptionHandlerEntryCode+0x58>
0xf8410702u, // ldr x2, [x24], #16
0xeb02003fu, // cmp x1, x2
0x54000080u, // b.eq 144 <ExceptionHandlerEntryCode+0x60>
0x17fffffbu, // b 124 <ExceptionHandlerEntryCode+0x40>
0xd2800018u, // mov x24, #0x0
0x14000002u, // b 148 <ExceptionHandlerEntryCode+0x64>
0xf85f8318u, // ldur x24, [x24, #-8]
0xb4000438u, // cbz x24, 1cc <ExceptionHandlerEntryCode+0xe8>
0xf9419300u, // ldr x0, [x24, #800]
0x9100001fu, // mov sp, x0
0x7100027fu, // cmp w19, #0x0
0x54000180u, // b.eq 188 <ExceptionHandlerEntryCode+0xa4>
0x52800020u, // mov w0, #0x1
0xb9031f00u, // str w0, [x24, #796]
0xaa1303e0u, // mov x0, x19
0xaa1403e1u, // mov x1, x20
0xaa1503e2u, // mov x2, x21
0xd2800008u, // mov x8, #0x0
0xf2a00008u, // movk x8, #0x0, lsl #16
0xf2c00008u, // movk x8, #0x0, lsl #32
0xf2e00008u, // movk x8, #0x0, lsl #48
0xd63f0100u, // blr x8
0x1400000au, // b 1ac <ExceptionHandlerEntryCode+0xc8>
0xb9431f00u, // ldr w0, [x24, #796]
0x35000120u, // cbnz w0, 1b0 <ExceptionHandlerEntryCode+0xcc>
0x52800020u, // mov w0, #0x1
0xb9031f00u, // str w0, [x24, #796]
0xd2800000u, // mov x0, #0x0
0xf2a00000u, // movk x0, #0x0, lsl #16
0xf2c00000u, // movk x0, #0x0, lsl #32
0xf2e00000u, // movk x0, #0x0, lsl #48
0xd63f0000u, // blr x0
0xb9031f1fu, // str wzr, [x24, #796]
0x910002dfu, // mov sp, x22
0xaa1703feu, // mov x30, x23
0xa9415bf5u, // ldp x21, x22, [sp, #16]
0xa94263f7u, // ldp x23, x24, [sp, #32]
0xa9436bf9u, // ldp x25, x26, [sp, #48]
0xa8c453f3u, // ldp x19, x20, [sp], #64
0xd65f03c0u, // ret
0xaa1303e0u, // mov x0, x19
0xaa1403e1u, // mov x1, x20
0xaa1503e2u, // mov x2, x21
0x910002dfu, // mov sp, x22
0xa9415bf5u, // ldp x21, x22, [sp, #16]
0xa94263f7u, // ldp x23, x24, [sp, #32]
0xf9401bf9u, // ldr x25, [sp, #48]
0xa8c453f3u, // ldp x19, x20, [sp], #64
0xd2800003u, // mov x3, #0x0
0xf2a00003u, // movk x3, #0x0, lsl #16
0xf2c00003u, // movk x3, #0x0, lsl #32
0xf2e00003u, // movk x3, #0x0, lsl #48
0xd61f0060u, // br x3
};
public static uint[] SvcPatchCode = new uint[]
{
0xa9be53f3u, // stp x19, x20, [sp, #-32]!
0xf9000bf5u, // str x21, [sp, #16]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 22c <SvcPatchCode+0x2c>
0x17fffffdu, // b 21c <SvcPatchCode+0x1c>
0xf85f8273u, // ldur x19, [x19, #-8]
0xa9000660u, // stp x0, x1, [x19]
0xa9010e62u, // stp x2, x3, [x19, #16]
0xa9021664u, // stp x4, x5, [x19, #32]
0xa9031e66u, // stp x6, x7, [x19, #48]
0xa9042668u, // stp x8, x9, [x19, #64]
0xa9052e6au, // stp x10, x11, [x19, #80]
0xa906366cu, // stp x12, x13, [x19, #96]
0xa9073e6eu, // stp x14, x15, [x19, #112]
0xa9084670u, // stp x16, x17, [x19, #128]
0xf9400bf5u, // ldr x21, [sp, #16]
0xa8c253e0u, // ldp x0, x20, [sp], #32
0xa9090272u, // stp x18, x0, [x19, #144]
0xa90a5674u, // stp x20, x21, [x19, #160]
0xa90b5e76u, // stp x22, x23, [x19, #176]
0xa90c6678u, // stp x24, x25, [x19, #192]
0xa90d6e7au, // stp x26, x27, [x19, #208]
0xa90e767cu, // stp x28, x29, [x19, #224]
0x910003e0u, // mov x0, sp
0xa90f027eu, // stp x30, x0, [x19, #240]
0xad080660u, // stp q0, q1, [x19, #256]
0xad090e62u, // stp q2, q3, [x19, #288]
0xad0a1664u, // stp q4, q5, [x19, #320]
0xad0b1e66u, // stp q6, q7, [x19, #352]
0xad0c2668u, // stp q8, q9, [x19, #384]
0xad0d2e6au, // stp q10, q11, [x19, #416]
0xad0e366cu, // stp q12, q13, [x19, #448]
0xad0f3e6eu, // stp q14, q15, [x19, #480]
0xad104670u, // stp q16, q17, [x19, #512]
0xad114e72u, // stp q18, q19, [x19, #544]
0xad125674u, // stp q20, q21, [x19, #576]
0xad135e76u, // stp q22, q23, [x19, #608]
0xad146678u, // stp q24, q25, [x19, #640]
0xad156e7au, // stp q26, q27, [x19, #672]
0xad16767cu, // stp q28, q29, [x19, #704]
0xad177e7eu, // stp q30, q31, [x19, #736]
0xf9419260u, // ldr x0, [x19, #800]
0x9100001fu, // mov sp, x0
0x52800020u, // mov w0, #0x1
0xb9031e60u, // str w0, [x19, #796]
0x52800000u, // mov w0, #0x0
0xf941aa68u, // ldr x8, [x19, #848]
0xd63f0100u, // blr x8
0x35000280u, // cbnz w0, 328 <SvcPatchCode+0x128>
0x6d517ffeu, // ldp d30, d31, [sp, #272]
0x6d5077fcu, // ldp d28, d29, [sp, #256]
0x6d4f6ffau, // ldp d26, d27, [sp, #240]
0x6d4e67f8u, // ldp d24, d25, [sp, #224]
0x6d4d5ff6u, // ldp d22, d23, [sp, #208]
0x6d4c57f4u, // ldp d20, d21, [sp, #192]
0x6d4b4ff2u, // ldp d18, d19, [sp, #176]
0x6d4a47f0u, // ldp d16, d17, [sp, #160]
0x6d493feeu, // ldp d14, d15, [sp, #144]
0x6d4837ecu, // ldp d12, d13, [sp, #128]
0x6d472feau, // ldp d10, d11, [sp, #112]
0x6d4627e8u, // ldp d8, d9, [sp, #96]
0xa9457bfdu, // ldp x29, x30, [sp, #80]
0xa94473fbu, // ldp x27, x28, [sp, #64]
0xa9436bf9u, // ldp x25, x26, [sp, #48]
0xa94263f7u, // ldp x23, x24, [sp, #32]
0xa9415bf5u, // ldp x21, x22, [sp, #16]
0xa8d253f3u, // ldp x19, x20, [sp], #288
0xd65f03c0u, // ret
0xb9031e7fu, // str wzr, [x19, #796]
0xa94f027eu, // ldp x30, x0, [x19, #240]
0x9100001fu, // mov sp, x0
0xa9400660u, // ldp x0, x1, [x19]
0xa9410e62u, // ldp x2, x3, [x19, #16]
0xa9421664u, // ldp x4, x5, [x19, #32]
0xa9431e66u, // ldp x6, x7, [x19, #48]
0xa9442668u, // ldp x8, x9, [x19, #64]
0xa9452e6au, // ldp x10, x11, [x19, #80]
0xa946366cu, // ldp x12, x13, [x19, #96]
0xa9473e6eu, // ldp x14, x15, [x19, #112]
0xa9484670u, // ldp x16, x17, [x19, #128]
0xf9404a72u, // ldr x18, [x19, #144]
0xa94a5674u, // ldp x20, x21, [x19, #160]
0xa94b5e76u, // ldp x22, x23, [x19, #176]
0xa94c6678u, // ldp x24, x25, [x19, #192]
0xa94d6e7au, // ldp x26, x27, [x19, #208]
0xa94e767cu, // ldp x28, x29, [x19, #224]
0xad480660u, // ldp q0, q1, [x19, #256]
0xad490e62u, // ldp q2, q3, [x19, #288]
0xad4a1664u, // ldp q4, q5, [x19, #320]
0xad4b1e66u, // ldp q6, q7, [x19, #352]
0xad4c2668u, // ldp q8, q9, [x19, #384]
0xad4d2e6au, // ldp q10, q11, [x19, #416]
0xad4e366cu, // ldp q12, q13, [x19, #448]
0xad4f3e6eu, // ldp q14, q15, [x19, #480]
0xad504670u, // ldp q16, q17, [x19, #512]
0xad514e72u, // ldp q18, q19, [x19, #544]
0xad525674u, // ldp q20, q21, [x19, #576]
0xad535e76u, // ldp q22, q23, [x19, #608]
0xad546678u, // ldp q24, q25, [x19, #640]
0xad556e7au, // ldp q26, q27, [x19, #672]
0xad56767cu, // ldp q28, q29, [x19, #704]
0xad577e7eu, // ldp q30, q31, [x19, #736]
0xf9404e73u, // ldr x19, [x19, #152]
0x14000000u, // b 3b4 <SvcPatchCode+0x1b4>
};
public static uint[] MrsTpidrroEl0PatchCode = new uint[]
{
0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
0xa90157f4u, // stp x20, x21, [sp, #16]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 3e4 <MrsTpidrroEl0PatchCode+0x2c>
0x17fffffdu, // b 3d4 <MrsTpidrroEl0PatchCode+0x1c>
0xf85f8273u, // ldur x19, [x19, #-8]
0xf9418673u, // ldr x19, [x19, #776]
0xf90003f3u, // str x19, [sp]
0xa94157f4u, // ldp x20, x21, [sp, #16]
0xf94007f3u, // ldr x19, [sp, #8]
0xf84207e0u, // ldr x0, [sp], #32
0x14000000u, // b 3fc <MrsTpidrroEl0PatchCode+0x44>
};
public static uint[] MrsTpidrEl0PatchCode = new uint[]
{
0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
0xa90157f4u, // stp x20, x21, [sp, #16]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 42c <MrsTpidrEl0PatchCode+0x2c>
0x17fffffdu, // b 41c <MrsTpidrEl0PatchCode+0x1c>
0xf85f8273u, // ldur x19, [x19, #-8]
0xf9418273u, // ldr x19, [x19, #768]
0xf90003f3u, // str x19, [sp]
0xa94157f4u, // ldp x20, x21, [sp, #16]
0xf94007f3u, // ldr x19, [sp, #8]
0xf84207e0u, // ldr x0, [sp], #32
0x14000000u, // b 444 <MrsTpidrEl0PatchCode+0x44>
};
public static uint[] MrsCtrEl0PatchCode = new uint[]
{
0xa9be4fffu, // stp xzr, x19, [sp, #-32]!
0xa90157f4u, // stp x20, x21, [sp, #16]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 474 <MrsCtrEl0PatchCode+0x2c>
0x17fffffdu, // b 464 <MrsCtrEl0PatchCode+0x1c>
0xf85f8273u, // ldur x19, [x19, #-8]
0xf9419e73u, // ldr x19, [x19, #824]
0xf90003f3u, // str x19, [sp]
0xa94157f4u, // ldp x20, x21, [sp, #16]
0xf94007f3u, // ldr x19, [sp, #8]
0xf84207e0u, // ldr x0, [sp], #32
0x14000000u, // b 48c <MrsCtrEl0PatchCode+0x44>
};
public static uint[] MsrTpidrEl0PatchCode = new uint[]
{
0xa9be03f3u, // stp x19, x0, [sp, #-32]!
0xa90157f4u, // stp x20, x21, [sp, #16]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 4bc <MsrTpidrEl0PatchCode+0x2c>
0x17fffffdu, // b 4ac <MsrTpidrEl0PatchCode+0x1c>
0xf85f8273u, // ldur x19, [x19, #-8]
0xf94007f4u, // ldr x20, [sp, #8]
0xf9018274u, // str x20, [x19, #768]
0xa94157f4u, // ldp x20, x21, [sp, #16]
0xf84207f3u, // ldr x19, [sp], #32
0x14000000u, // b 4d0 <MsrTpidrEl0PatchCode+0x40>
};
public static uint[] MrsCntpctEl0PatchCode = new uint[]
{
0xa9b407e0u, // stp x0, x1, [sp, #-192]!
0xa9010fe2u, // stp x2, x3, [sp, #16]
0xa90217e4u, // stp x4, x5, [sp, #32]
0xa9031fe6u, // stp x6, x7, [sp, #48]
0xa90427e8u, // stp x8, x9, [sp, #64]
0xa9052feau, // stp x10, x11, [sp, #80]
0xa90637ecu, // stp x12, x13, [sp, #96]
0xa9073feeu, // stp x14, x15, [sp, #112]
0xa90847f0u, // stp x16, x17, [sp, #128]
0xa9094ff2u, // stp x18, x19, [sp, #144]
0xa90a57f4u, // stp x20, x21, [sp, #160]
0xf9005ffeu, // str x30, [sp, #184]
0xd2800013u, // mov x19, #0x0
0xf2a00013u, // movk x19, #0x0, lsl #16
0xf2c00013u, // movk x19, #0x0, lsl #32
0xf2e00013u, // movk x19, #0x0, lsl #48
GetMrsTpidrEl0(20), // mrs x20, tpidr_el0
0xf8410675u, // ldr x21, [x19], #16
0xeb15029fu, // cmp x20, x21
0x54000040u, // b.eq 528 <MrsCntpctEl0PatchCode+0x54>
0x17fffffdu, // b 518 <MrsCntpctEl0PatchCode+0x44>
0xf85f8273u, // ldur x19, [x19, #-8]
0x52800020u, // mov w0, #0x1
0xb9031e60u, // str w0, [x19, #796]
0xd2800000u, // mov x0, #0x0
0xf2a00000u, // movk x0, #0x0, lsl #16
0xf2c00000u, // movk x0, #0x0, lsl #32
0xf2e00000u, // movk x0, #0x0, lsl #48
0xd63f0000u, // blr x0
0xb9031e7fu, // str wzr, [x19, #796]
0xf9005be0u, // str x0, [sp, #176]
0xf9405ffeu, // ldr x30, [sp, #184]
0xa94a57f4u, // ldp x20, x21, [sp, #160]
0xa9494ff2u, // ldp x18, x19, [sp, #144]
0xa94847f0u, // ldp x16, x17, [sp, #128]
0xa9473feeu, // ldp x14, x15, [sp, #112]
0xa94637ecu, // ldp x12, x13, [sp, #96]
0xa9452feau, // ldp x10, x11, [sp, #80]
0xa94427e8u, // ldp x8, x9, [sp, #64]
0xa9431fe6u, // ldp x6, x7, [sp, #48]
0xa94217e4u, // ldp x4, x5, [sp, #32]
0xa9410fe2u, // ldp x2, x3, [sp, #16]
0xa8cb07e0u, // ldp x0, x1, [sp], #176
0xf84107e0u, // ldr x0, [sp], #16
0x14000000u, // b 584 <MrsCntpctEl0PatchCode+0xb0>
};
private static uint GetMrsTpidrEl0(uint rd)
{
if (OperatingSystem.IsMacOS())
{
return 0xd53bd060u | rd; // TPIDRRO
}
else
{
return 0xd53bd040u | rd; // TPIDR
}
}
}
}

View File

@ -0,0 +1,99 @@
using ARMeilleure.Memory;
using ARMeilleure.Signal;
using Ryujinx.Cpu.Jit;
using Ryujinx.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
class NceCpuContext : ICpuContext
{
private delegate void ThreadStart(IntPtr nativeContextPtr);
private delegate IntPtr GetTpidrEl0();
private static MemoryBlock _codeBlock;
private static ThreadStart _threadStart;
private static GetTpidrEl0 _getTpidrEl0;
private readonly ITickSource _tickSource;
private readonly ICpuMemoryManager _memoryManager;
static NceCpuContext()
{
ulong threadStartCodeSize = (ulong)NceAsmTable.ThreadStartCode.Length * 4;
ulong enEntryCodeOffset = threadStartCodeSize;
ulong ehEntryCodeSize = (ulong)NceAsmTable.ExceptionHandlerEntryCode.Length * 4;
ulong getTpidrEl0CodeOffset = threadStartCodeSize + ehEntryCodeSize;
ulong getTpidrEl0CodeSize = (ulong)NceAsmTable.GetTpidrEl0Code.Length * 4;
ulong size = BitUtils.AlignUp(threadStartCodeSize + ehEntryCodeSize + getTpidrEl0CodeSize, 0x1000UL);
MemoryBlock codeBlock = new MemoryBlock(size);
codeBlock.Write(0, MemoryMarshal.Cast<uint, byte>(NceAsmTable.ThreadStartCode.AsSpan()));
codeBlock.Write(getTpidrEl0CodeOffset, MemoryMarshal.Cast<uint, byte>(NceAsmTable.GetTpidrEl0Code.AsSpan()));
NativeSignalHandler.Initialize(new JitMemoryAllocator());
NativeSignalHandler.InitializeSignalHandler(MemoryBlock.GetPageSize(), (IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr) =>
{
uint[] ehEntryCode = NcePatcher.GenerateExceptionHandlerEntry(oldSignalHandlerSegfaultPtr, signalHandlerPtr);
codeBlock.Write(enEntryCodeOffset, MemoryMarshal.Cast<uint, byte>(ehEntryCode.AsSpan()));
codeBlock.Reprotect(0, size, MemoryPermission.ReadAndExecute, true);
return codeBlock.GetPointer(enEntryCodeOffset, ehEntryCodeSize);
}, NceThreadPal.UnixSuspendSignal);
_threadStart = Marshal.GetDelegateForFunctionPointer<ThreadStart>(codeBlock.GetPointer(0, threadStartCodeSize));
_getTpidrEl0 = Marshal.GetDelegateForFunctionPointer<GetTpidrEl0>(codeBlock.GetPointer(getTpidrEl0CodeOffset, getTpidrEl0CodeSize));
_codeBlock = codeBlock;
}
public NceCpuContext(ITickSource tickSource, ICpuMemoryManager memory, bool for64Bit)
{
_tickSource = tickSource;
_memoryManager = memory;
}
/// <inheritdoc/>
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
{
return new NceExecutionContext(exceptionCallbacks);
}
/// <inheritdoc/>
public void Execute(IExecutionContext context, ulong address)
{
NceExecutionContext nec = (NceExecutionContext)context;
NceNativeInterface.RegisterThread(nec, _tickSource);
int tableIndex = NceThreadTable.Register(_getTpidrEl0(), nec.NativeContextPtr);
nec.SetStartAddress(address);
_threadStart(nec.NativeContextPtr);
NceThreadTable.Unregister(tableIndex);
}
/// <inheritdoc/>
public void InvalidateCacheRegion(ulong address, ulong size)
{
}
/// <inheritdoc/>
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
{
NcePatcher.Patch(_memoryManager, textAddress, textSize, patchRegionAddress, patchRegionSize);
}
/// <inheritdoc/>
public IDiskCacheLoadState LoadDiskCache(string titleIdText, string displayVersion, bool enabled)
{
return new DiskCacheLoadState();
}
/// <inheritdoc/>
public void PrepareCodeRange(ulong address, ulong size)
{
}
}
}

View File

@ -0,0 +1,19 @@
using ARMeilleure.Memory;
namespace Ryujinx.Cpu.Nce
{
public class NceEngine : ICpuEngine
{
public ITickSource TickSource{ get; }
public NceEngine(ITickSource tickSource)
{
TickSource = tickSource;
}
public ICpuContext CreateCpuContext(ICpuMemoryManager memoryManager, bool for64Bit)
{
return new NceCpuContext(TickSource, memoryManager, for64Bit);
}
}
}

View File

@ -0,0 +1,131 @@
using ARMeilleure.State;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
class NceExecutionContext : IExecutionContext
{
private readonly NceNativeContext _context;
private readonly ExceptionCallbacks _exceptionCallbacks;
internal IntPtr NativeContextPtr => _context.BasePtr;
public ulong Pc => 0UL;
public long TpidrEl0
{
get => (long)_context.GetStorage().TpidrEl0;
set => _context.GetStorage().TpidrEl0 = (ulong)value;
}
public long TpidrroEl0
{
get => (long)_context.GetStorage().TpidrroEl0;
set => _context.GetStorage().TpidrroEl0 = (ulong)value;
}
public uint Pstate
{
get => _context.GetStorage().Pstate;
set => _context.GetStorage().Pstate = value;
}
public uint Fpcr
{
get => _context.GetStorage().Fpcr;
set => _context.GetStorage().Fpcr = value;
}
public uint Fpsr
{
get => _context.GetStorage().Fpsr;
set => _context.GetStorage().Fpsr = value;
}
public bool IsAarch32
{
get => false;
set
{
if (value)
{
throw new NotSupportedException();
}
}
}
public bool Running { get; private set; }
private delegate bool SupervisorCallHandler(int imm);
private SupervisorCallHandler _svcHandler;
public NceExecutionContext(ExceptionCallbacks exceptionCallbacks)
{
_svcHandler = OnSupervisorCall;
IntPtr svcHandlerPtr = Marshal.GetFunctionPointerForDelegate(_svcHandler);
_context = new NceNativeContext();
ref var storage = ref _context.GetStorage();
storage.SvcCallHandler = svcHandlerPtr;
storage.InManaged = 1u;
storage.CtrEl0 = 0x8444c004; // TODO: Get value from host CPU instead of using guest one?
Running = true;
_exceptionCallbacks = exceptionCallbacks;
}
public ulong GetX(int index) => _context.GetStorage().X[index];
public void SetX(int index, ulong value) => _context.GetStorage().X[index] = value;
public V128 GetV(int index) => _context.GetStorage().V[index];
public void SetV(int index, V128 value) => _context.GetStorage().V[index] = value;
// TODO
public bool GetPstateFlag(PState flag) => false;
public void SetPstateFlag(PState flag, bool value) { }
// TODO
public bool GetFPstateFlag(FPState flag) => false;
public void SetFPstateFlag(FPState flag, bool value) { }
public void SetStartAddress(ulong address)
{
ref var storage = ref _context.GetStorage();
storage.X[30] = address;
storage.HostThreadHandle = NceThreadPal.GetCurrentThreadHandle();
}
public bool OnSupervisorCall(int imm)
{
_exceptionCallbacks.SupervisorCallback?.Invoke(this, 0UL, imm);
return Running;
}
public bool OnInterrupt()
{
_exceptionCallbacks.InterruptCallback?.Invoke(this);
return Running;
}
public void RequestInterrupt()
{
IntPtr threadHandle = _context.GetStorage().HostThreadHandle;
if (threadHandle != IntPtr.Zero)
{
NceThreadPal.SuspendThread(threadHandle);
}
}
public void StopRunning()
{
Running = false;
}
public void Dispose()
{
_context.Dispose();
}
}
}

View File

@ -0,0 +1,13 @@
using ARMeilleure.Memory;
using Ryujinx.Memory;
namespace Ryujinx.Cpu.Nce
{
class NceMemoryAllocator : IJitMemoryAllocator
{
public IJitMemoryBlock Allocate(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.None);
public IJitMemoryBlock Reserve(ulong size) => new NceMemoryBlock(size, MemoryAllocationFlags.Reserve);
public ulong GetPageSize() => MemoryBlock.GetPageSize();
}
}

View File

@ -0,0 +1,24 @@
using ARMeilleure.Memory;
using Ryujinx.Memory;
using System;
namespace Ryujinx.Cpu.Nce
{
class NceMemoryBlock : IJitMemoryBlock
{
private readonly MemoryBlock _impl;
public IntPtr Pointer => _impl.Pointer;
public NceMemoryBlock(ulong size, MemoryAllocationFlags flags)
{
_impl = new MemoryBlock(size, flags);
}
public void Commit(ulong offset, ulong size) => _impl.Commit(offset, size);
public void MapAsRx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadAndExecute);
public void MapAsRwx(ulong offset, ulong size) => _impl.Reprotect(offset, size, MemoryPermission.ReadWriteExecute);
public void Dispose() => _impl.Dispose();
}
}

View File

@ -0,0 +1,43 @@
using ARMeilleure.State;
using Ryujinx.Common.Memory;
using Ryujinx.Memory;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Cpu.Nce
{
class NceNativeContext : IDisposable
{
public struct NativeCtxStorage
{
public Array32<ulong> X;
public Array32<V128> V;
public ulong TpidrEl0; // 0x300
public ulong TpidrroEl0; // 0x308
public uint Pstate; // 0x310
public uint Fpcr; // 0x314
public uint Fpsr; // 0x318
public uint InManaged; // 0x31C
public ulong HostSp; // 0x320
public IntPtr HostThreadHandle; // 0x328
public ulong HostX30; // 0x330
public ulong CtrEl0; // 0x338
public ulong Reserved340; // 0x340
public ulong Reserved348; // 0x348
public IntPtr SvcCallHandler; // 0x350
}
private readonly MemoryBlock _block;
public IntPtr BasePtr => _block.Pointer;
public NceNativeContext()
{
_block = new MemoryBlock((ulong)Unsafe.SizeOf<NativeCtxStorage>());
}
public unsafe ref NativeCtxStorage GetStorage() => ref Unsafe.AsRef<NativeCtxStorage>((void*)_block.Pointer);
public void Dispose() => _block.Dispose();
}
}

View File

@ -0,0 +1,55 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
static class NceNativeInterface
{
private delegate ulong GetTickCounterDelegate();
private delegate bool SuspendThreadHandlerDelegate();
private static GetTickCounterDelegate _getTickCounter;
private static SuspendThreadHandlerDelegate _suspendThreadHandler;
private static IntPtr _getTickCounterPtr;
private static IntPtr _suspendThreadHandlerPtr;
[ThreadStatic]
private static NceExecutionContext _context;
[ThreadStatic]
private static ITickSource _tickSource;
static NceNativeInterface()
{
_getTickCounter = GetTickCounter;
_suspendThreadHandler = SuspendThreadHandler;
_getTickCounterPtr = Marshal.GetFunctionPointerForDelegate(_getTickCounter);
_suspendThreadHandlerPtr = Marshal.GetFunctionPointerForDelegate(_suspendThreadHandler);
}
public static void RegisterThread(NceExecutionContext context, ITickSource tickSource)
{
_context = context;
_tickSource = tickSource;
}
public static ulong GetTickCounter()
{
return _tickSource.Counter;
}
public static bool SuspendThreadHandler()
{
return _context.OnInterrupt();
}
public static IntPtr GetTickCounterAccessFunctionPointer()
{
return _getTickCounterPtr;
}
public static IntPtr GetSuspendThreadHandlerFunctionPointer()
{
return _suspendThreadHandlerPtr;
}
}
}

View File

@ -0,0 +1,293 @@
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
static class NcePatcher
{
private struct Context
{
public readonly ICpuMemoryManager MemoryManager;
public ulong PatchRegionAddress;
public ulong PatchRegionSize;
public Context(ICpuMemoryManager memoryManager, ulong patchRegionAddress, ulong patchRegionSize)
{
MemoryManager = memoryManager;
PatchRegionAddress = patchRegionAddress;
PatchRegionSize = patchRegionSize;
}
public ulong GetPatchWriteAddress(int length)
{
ulong byteLength = (ulong)length * 4;
if (PatchRegionSize < byteLength)
{
throw new Exception("No enough space for patch.");
}
ulong address = PatchRegionAddress;
PatchRegionAddress += byteLength;
PatchRegionSize -= byteLength;
return address;
}
}
public static void Patch(
ICpuMemoryManager memoryManager,
ulong textAddress,
ulong textSize,
ulong patchRegionAddress,
ulong patchRegionSize)
{
Context context = new Context(memoryManager, patchRegionAddress, patchRegionSize);
ulong address = textAddress;
while (address < textAddress + textSize)
{
uint inst = memoryManager.Read<uint>(address);
if ((inst & ~(0xffffu << 5)) == 0xd4000001u) // svc #0
{
uint svcId = (ushort)(inst >> 5);
PatchInstruction(memoryManager, address, WriteSvcPatch(ref context, address, svcId));
Logger.Debug?.Print(LogClass.Cpu, $"Patched SVC #{svcId} at 0x{address:X}.");
}
else if ((inst & ~0x1f) == 0xd53bd060) // mrs x0, tpidrro_el0
{
uint rd = inst & 0x1f;
PatchInstruction(memoryManager, address, WriteMrsTpidrroEl0Patch(ref context, address, rd));
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidrro_el0 at 0x{address:X}.");
}
else if ((inst & ~0x1f) == 0xd53bd040) // mrs x0, tpidr_el0
{
uint rd = inst & 0x1f;
PatchInstruction(memoryManager, address, WriteMrsTpidrEl0Patch(ref context, address, rd));
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, tpidr_el0 at 0x{address:X}.");
}
else if ((inst & ~0x1f) == 0xd53b0020 && OperatingSystem.IsMacOS()) // mrs x0, ctr_el0
{
uint rd = inst & 0x1f;
PatchInstruction(memoryManager, address, WriteMrsCtrEl0Patch(ref context, address, rd));
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, ctr_el0 at 0x{address:X}.");
}
else if ((inst & ~0x1f) == 0xd51bd040) // msr tpidr_el0, x0
{
uint rd = inst & 0x1f;
PatchInstruction(memoryManager, address, WriteMsrTpidrEl0Patch(ref context, address, rd));
Logger.Debug?.Print(LogClass.Cpu, $"Patched MSR tpidr_el0, x{rd} at 0x{address:X}.");
}
else if ((inst & ~0x1f) == 0xd53be020) // mrs x0, cntpct_el0
{
uint rd = inst & 0x1f;
PatchInstruction(memoryManager, address, WriteMrsCntpctEl0Patch(ref context, address, rd));
Logger.Debug?.Print(LogClass.Cpu, $"Patched MRS x{rd}, cntpct_el0 at 0x{address:X}.");
}
address += 4;
}
ulong patchRegionConsumed = BitUtils.AlignUp(context.PatchRegionAddress - patchRegionAddress, 0x1000UL);
if (patchRegionConsumed != 0)
{
memoryManager.Reprotect(patchRegionAddress, patchRegionConsumed, MemoryPermission.ReadAndExecute);
}
}
private static void PatchInstruction(ICpuMemoryManager memoryManager, ulong instructionAddress, ulong targetAddress)
{
memoryManager.Write(instructionAddress, 0x14000000u | GetImm26(instructionAddress, targetAddress));
}
private static ulong WriteSvcPatch(ref Context context, ulong svcAddress, uint svcId)
{
uint[] code = GetCopy(NceAsmTable.SvcPatchCode);
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
int mov2Index = Array.IndexOf(code, 0x52800000u, movIndex + 1);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[mov2Index] |= svcId << 5;
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, svcAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
private static ulong WriteMrsTpidrroEl0Patch(ref Context context, ulong mrsAddress, uint rd)
{
uint[] code = GetCopy(NceAsmTable.MrsTpidrroEl0PatchCode);
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[code.Length - 2] |= rd;
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
private static ulong WriteMrsTpidrEl0Patch(ref Context context, ulong mrsAddress, uint rd)
{
uint[] code = GetCopy(NceAsmTable.MrsTpidrEl0PatchCode);
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[code.Length - 2] |= rd;
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
private static ulong WriteMrsCtrEl0Patch(ref Context context, ulong mrsAddress, uint rd)
{
uint[] code = GetCopy(NceAsmTable.MrsCtrEl0PatchCode);
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[code.Length - 2] |= rd;
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
private static ulong WriteMsrTpidrEl0Patch(ref Context context, ulong msrAddress, uint rd)
{
uint r2 = rd == 0 ? 1u : 0u;
uint[] code = GetCopy(NceAsmTable.MsrTpidrEl0PatchCode);
code[0] |= rd << 10;
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, msrAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
private static ulong WriteMrsCntpctEl0Patch(ref Context context, ulong mrsAddress, uint rd)
{
uint[] code = GetCopy(NceAsmTable.MrsCntpctEl0PatchCode);
int movIndex = Array.IndexOf(code, 0xd2800013u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
int mov2Index = Array.IndexOf(code, 0xD2800000u, movIndex + 1);
WriteTickCounterAccessFunctionPointer(code, mov2Index);
ulong targetAddress = context.GetPatchWriteAddress(code.Length);
code[code.Length - 2] |= rd;
code[code.Length - 1] |= GetImm26(targetAddress + (ulong)(code.Length - 1) * 4, mrsAddress + 4);
WriteCode(context.MemoryManager, targetAddress, code);
return targetAddress;
}
public static uint[] GenerateExceptionHandlerEntry(IntPtr oldSignalHandlerSegfaultPtr, IntPtr signalHandlerPtr)
{
uint[] code = GetCopy(NceAsmTable.ExceptionHandlerEntryCode);
int movIndex = Array.IndexOf(code, 0xd2800018u);
WritePointer(code, movIndex, (ulong)NceThreadTable.EntriesPointer);
int mov2Index = Array.IndexOf(code, 0xd2800008u, movIndex + 1);
WritePointer(code, mov2Index, (ulong)signalHandlerPtr);
int mov3Index = Array.IndexOf(code, 0xd2800000u, mov2Index + 1);
WritePointer(code, mov3Index, (ulong)NceNativeInterface.GetSuspendThreadHandlerFunctionPointer());
int mov4Index = Array.IndexOf(code, 0xd2800003u, mov3Index + 1);
WritePointer(code, mov4Index, (ulong)oldSignalHandlerSegfaultPtr);
int cmpIndex = Array.IndexOf(code, 0x7100027fu);
code[cmpIndex] |= (uint)NceThreadPal.UnixSuspendSignal << 10;
return code;
}
private static void WriteTickCounterAccessFunctionPointer(uint[] code, int movIndex)
{
WritePointer(code, movIndex, (ulong)NceNativeInterface.GetTickCounterAccessFunctionPointer());
}
private static void WritePointer(uint[] code, int movIndex, ulong ptr)
{
code[movIndex] |= (uint)(ushort)ptr << 5;
code[movIndex + 1] |= (uint)(ushort)(ptr >> 16) << 5;
code[movIndex + 2] |= (uint)(ushort)(ptr >> 32) << 5;
code[movIndex + 3] |= (uint)(ushort)(ptr >> 48) << 5;
}
private static uint GetImm26(ulong sourceAddress, ulong targetAddress)
{
long offset = (long)(targetAddress - sourceAddress);
long offsetTrunc = (offset >> 2) & 0x3FFFFFF;
if ((offsetTrunc << 38) >> 36 != offset)
{
throw new Exception($"Offset out of range: 0x{sourceAddress:X} -> 0x{targetAddress:X} (0x{offset:X})");
}
return (uint)offsetTrunc;
}
private static uint[] GetCopy(uint[] code)
{
uint[] codeCopy = new uint[code.Length];
code.CopyTo(codeCopy, 0);
return codeCopy;
}
private static void WriteCode(ICpuMemoryManager memoryManager, ulong address, uint[] code)
{
for (int i = 0; i < code.Length; i++)
{
memoryManager.Write(address + (ulong)i * sizeof(uint), code[i]);
}
}
}
}

View File

@ -0,0 +1,40 @@
using System;
namespace Ryujinx.Cpu.Nce
{
static class NceThreadPal
{
private const int SigUsr2Linux = 12;
private const int SigUsr2MacOS = 31;
public static int UnixSuspendSignal => OperatingSystem.IsMacOS() ? SigUsr2MacOS : SigUsr2Linux;
public static IntPtr GetCurrentThreadHandle()
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsAndroid())
{
return NceThreadPalUnix.GetCurrentThreadHandle();
}
else
{
throw new PlatformNotSupportedException();
}
}
public static void SuspendThread(IntPtr handle)
{
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
{
NceThreadPalUnix.SuspendThread(handle);
}
else if (OperatingSystem.IsAndroid())
{
NceThreadPalAndroid.SuspendThread(handle);
}
else
{
throw new PlatformNotSupportedException();
}
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
static class NceThreadPalAndroid
{
[DllImport("libc", SetLastError = true)]
private static extern int pthread_kill(IntPtr thread, int sig);
public static void SuspendThread(IntPtr handle)
{
int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
if (result != 0)
{
throw new Exception($"Thread kill returned error 0x{result:X}.");
}
}
}
}

View File

@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.Cpu.Nce
{
static class NceThreadPalUnix
{
[DllImport("libc", SetLastError = true)]
private static extern IntPtr pthread_self();
[DllImport("libc", SetLastError = true)]
private static extern int pthread_threadid_np(IntPtr arg0, out ulong tid);
[DllImport("libpthread", SetLastError = true)]
private static extern int pthread_kill(IntPtr thread, int sig);
public static IntPtr GetCurrentThreadHandle()
{
return pthread_self();
}
public static ulong GetCurrentThreadId()
{
pthread_threadid_np(IntPtr.Zero, out ulong tid);
return tid;
}
public static void SuspendThread(IntPtr handle)
{
int result = pthread_kill(handle, NceThreadPal.UnixSuspendSignal);
if (result != 0)
{
throw new Exception($"Thread kill returned error 0x{result:X}.");
}
}
}
}

View File

@ -0,0 +1,97 @@
using Ryujinx.Memory;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.Cpu.Nce
{
static class NceThreadTable
{
private const int MaxThreads = 4096;
private struct Entry
{
public IntPtr ThreadId;
public IntPtr NativeContextPtr;
public Entry(IntPtr threadId, IntPtr nativeContextPtr)
{
ThreadId = threadId;
NativeContextPtr = nativeContextPtr;
}
}
private static MemoryBlock _block;
public static IntPtr EntriesPointer => _block.Pointer + 8;
static NceThreadTable()
{
_block = new MemoryBlock((ulong)Unsafe.SizeOf<Entry>() * MaxThreads + 8UL);
_block.Write(0UL, 0UL);
}
public static int Register(IntPtr threadId, IntPtr nativeContextPtr)
{
Span<Entry> entries = GetStorage();
lock (_block)
{
ref ulong currentThreadCount = ref GetThreadsCount();
for (int i = 0; i < MaxThreads; i++)
{
if (entries[i].ThreadId == IntPtr.Zero)
{
entries[i] = new Entry(threadId, nativeContextPtr);
if (currentThreadCount < (ulong)i + 1)
{
currentThreadCount = (ulong)i + 1;
}
return i;
}
}
}
throw new Exception($"Number of active threads exceeds limit of {MaxThreads}.");
}
public static void Unregister(int tableIndex)
{
Span<Entry> entries = GetStorage();
lock (_block)
{
if (entries[tableIndex].ThreadId != IntPtr.Zero)
{
entries[tableIndex] = default;
ulong currentThreadCount = GetThreadsCount();
for (int i = (int)currentThreadCount - 1; i >= 0; i--)
{
if (entries[i].ThreadId != IntPtr.Zero)
{
break;
}
currentThreadCount = (ulong)i;
}
GetThreadsCount() = currentThreadCount;
}
}
}
private static ref ulong GetThreadsCount()
{
return ref _block.GetRef<ulong>(0UL);
}
private static unsafe Span<Entry> GetStorage()
{
return new Span<Entry>((void*)_block.GetPointer(8UL, (ulong)Unsafe.SizeOf<Entry>() * MaxThreads), MaxThreads);
}
}
}

378
src/Ryujinx.Cpu/Nce/nce.S Normal file
View File

@ -0,0 +1,378 @@
.text
.macro longmov0 reg
mov \reg, #0
movk \reg, #0, lsl #16
movk \reg, #0, lsl #32
movk \reg, #0, lsl #48
.endm
// r1 = EntriesPointer
// r2 = current_thread_id_local
// r3 = expected_thread_id
// r4 = ThreadsCount_local
.macro loadctxptr_reg r1, r2, r3
longmov0 \r1
mrs \r2, tpidr_el0
1:
ldr \r3, [\r1], #16
cmp \r2, \r3
beq 2f
b 1b
2:
ldr \r1, [\r1, #-8]
.endm
.macro loadctxptr
loadctxptr_reg x19, x20, x21
.endm
.global GetTpidrEl0Code
GetTpidrEl0Code:
mrs x0, tpidr_el0
ret
.global ThreadStartCode
ThreadStartCode:
stp x19, x20, [sp, #-0x120]!
stp x21, x22, [sp, #0x10]
stp x23, x24, [sp, #0x20]
stp x25, x26, [sp, #0x30]
stp x27, x28, [sp, #0x40]
stp x29, x30, [sp, #0x50]
stp d8, d9, [sp, #0x60]
stp d10, d11, [sp, #0x70]
stp d12, d13, [sp, #0x80]
stp d14, d15, [sp, #0x90]
stp d16, d17, [sp, #0xA0]
stp d18, d19, [sp, #0xB0]
stp d20, d21, [sp, #0xC0]
stp d22, d23, [sp, #0xD0]
stp d24, d25, [sp, #0xE0]
stp d26, d27, [sp, #0xF0]
stp d28, d29, [sp, #0x100]
stp d30, d31, [sp, #0x110]
str wzr, [x0, #0x31C]
mov x1, sp
str x1, [x0, #0x320]
ldp x2, x3, [x0, #0x10]
ldp x4, x5, [x0, #0x20]
ldp x6, x7, [x0, #0x30]
ldp x8, x9, [x0, #0x40]
ldp x10, x11, [x0, #0x50]
ldp x12, x13, [x0, #0x60]
ldp x14, x15, [x0, #0x70]
ldp x16, x17, [x0, #0x80]
ldp x18, x19, [x0, #0x90]
ldp x20, x21, [x0, #0xA0]
ldp x22, x23, [x0, #0xB0]
ldp x24, x25, [x0, #0xC0]
ldp x26, x27, [x0, #0xD0]
ldp x28, x29, [x0, #0xE0]
ldp q0, q1, [x0, #0x100]
ldp q2, q3, [x0, #0x120]
ldp q4, q5, [x0, #0x140]
ldp q6, q7, [x0, #0x160]
ldp q8, q9, [x0, #0x180]
ldp q10, q11, [x0, #0x1A0]
ldp q12, q13, [x0, #0x1C0]
ldp q14, q15, [x0, #0x1E0]
ldp q16, q17, [x0, #0x200]
ldp q18, q19, [x0, #0x220]
ldp q20, q21, [x0, #0x240]
ldp q22, q23, [x0, #0x260]
ldp q24, q25, [x0, #0x280]
ldp q26, q27, [x0, #0x2A0]
ldp q28, q29, [x0, #0x2C0]
ldp q30, q31, [x0, #0x2E0]
ldp x30, x1, [x0, #0xF0]
mov sp, x1
ldp x0, x1, [x0, #0x0]
br x30
// Inputs
// r1 = EntriesPointer
// r2 = current_thread_id_local
// r3 = expected_thread_id
// r4 = EntriesPointerEnd
// Outputs
// r1 = EntryPointer or 0x0 on not found
.macro loadctxptr_safe_reg r1, r2, r3, r4
longmov0 \r1
ldr \r4, [\r1, #-8]
add \r4, \r1, \r4, lsl #4
mrs \r2, tpidr_el0
1:
cmp \r1, \r4
beq 2f
ldr \r3, [\r1], #16
cmp \r2, \r3
beq 3f
b 1b
2:
mov \r1, 0x0
b 4f
3:
ldr \r1, [\r1, #-8]
4:
.endm
.global ExceptionHandlerEntryCode
ExceptionHandlerEntryCode:
stp x19, x20, [sp, #-0x40]!
stp x21, x22, [sp, #0x10]
stp x23, x24, [sp, #0x20]
str x25, [sp, #0x30]
// signo
mov x19, x0
// siginfo_t *si
mov x20, x1
// void *thread_id
mov x21, x2
mov x22, sp
mov x23, x30
// x24 = EntriesPointer
// x1 = si
// x2 = thread_id
loadctxptr_safe_reg x24, x1, x2, x25
cbz x24, 4f
ldr x0, [x24, 0x320]
mov sp, x0
cmp w19, #0
beq 1f
mov w0, #1
str w0, [x24, 0x31C]
mov x0, x19
mov x1, x20
mov x2, x21
mov x8, #0
movk x8, #0, lsl #16
movk x8, #0, lsl #32
movk x8, #0, lsl #48
blr x8
b 2f
1:
ldr w0, [x24, 0x31C]
cbnz w0, 3f
mov w0, #1
str w0, [x24, 0x31C]
mov x0, #0
movk x0, #0, lsl #16
movk x0, #0, lsl #32
movk x0, #0, lsl #48
blr x0
2:
str wzr, [x24, 0x31C]
3:
mov sp, x22
mov x30, x23
ldp x21, x22, [sp, #0x10]
ldp x23, x24, [sp, #0x20]
ldp x25, x26, [sp, #0x30]
ldp x19, x20, [sp], #0x40
ret
4:
// ThreadId is invalid, forward to other handler.
mov x0, x19
mov x1, x20
mov x2, x21
mov sp, x22
ldp x21, x22, [sp, #0x10]
ldp x23, x24, [sp, #0x20]
ldr x25, [sp, #0x30]
ldp x19, x20, [sp], #0x40
longmov0 x3
br x3
.global SvcPatchCode
SvcPatchCode:
stp x19, x20, [sp, #-0x20]!
str x21, [sp, #0x10]
loadctxptr
stp x0, x1, [x19, #0x0]
stp x2, x3, [x19, #0x10]
stp x4, x5, [x19, #0x20]
stp x6, x7, [x19, #0x30]
stp x8, x9, [x19, #0x40]
stp x10, x11, [x19, #0x50]
stp x12, x13, [x19, #0x60]
stp x14, x15, [x19, #0x70]
stp x16, x17, [x19, #0x80]
ldr x21, [sp, #0x10]
ldp x0, x20, [sp], #0x20
stp x18, x0, [x19, #0x90]
stp x20, x21, [x19, #0xA0]
stp x22, x23, [x19, #0xB0]
stp x24, x25, [x19, #0xC0]
stp x26, x27, [x19, #0xD0]
stp x28, x29, [x19, #0xE0]
mov x0, sp
stp x30, x0, [x19, #0xF0]
stp q0, q1, [x19, #0x100]
stp q2, q3, [x19, #0x120]
stp q4, q5, [x19, #0x140]
stp q6, q7, [x19, #0x160]
stp q8, q9, [x19, #0x180]
stp q10, q11, [x19, #0x1A0]
stp q12, q13, [x19, #0x1C0]
stp q14, q15, [x19, #0x1E0]
stp q16, q17, [x19, #0x200]
stp q18, q19, [x19, #0x220]
stp q20, q21, [x19, #0x240]
stp q22, q23, [x19, #0x260]
stp q24, q25, [x19, #0x280]
stp q26, q27, [x19, #0x2A0]
stp q28, q29, [x19, #0x2C0]
stp q30, q31, [x19, #0x2E0]
ldr x0, [x19, #0x320]
mov sp, x0
mov w0, #1
str w0, [x19, #0x31C]
mov w0, #0
ldr x8, [x19, #0x350]
blr x8
cbnz w0, 1f
ldp d30, d31, [sp, #0x110]
ldp d28, d29, [sp, #0x100]
ldp d26, d27, [sp, #0xF0]
ldp d24, d25, [sp, #0xE0]
ldp d22, d23, [sp, #0xD0]
ldp d20, d21, [sp, #0xC0]
ldp d18, d19, [sp, #0xB0]
ldp d16, d17, [sp, #0xA0]
ldp d14, d15, [sp, #0x90]
ldp d12, d13, [sp, #0x80]
ldp d10, d11, [sp, #0x70]
ldp d8, d9, [sp, #0x60]
ldp x29, x30, [sp, #0x50]
ldp x27, x28, [sp, #0x40]
ldp x25, x26, [sp, #0x30]
ldp x23, x24, [sp, #0x20]
ldp x21, x22, [sp, #0x10]
ldp x19, x20, [sp], #0x120
ret
1:
str wzr, [x19, #0x31C]
ldp x30, x0, [x19, #0xF0]
mov sp, x0
ldp x0, x1, [x19, #0x0]
ldp x2, x3, [x19, #0x10]
ldp x4, x5, [x19, #0x20]
ldp x6, x7, [x19, #0x30]
ldp x8, x9, [x19, #0x40]
ldp x10, x11, [x19, #0x50]
ldp x12, x13, [x19, #0x60]
ldp x14, x15, [x19, #0x70]
ldp x16, x17, [x19, #0x80]
ldr x18, [x19, #0x90]
ldp x20, x21, [x19, #0xA0]
ldp x22, x23, [x19, #0xB0]
ldp x24, x25, [x19, #0xC0]
ldp x26, x27, [x19, #0xD0]
ldp x28, x29, [x19, #0xE0]
ldp q0, q1, [x19, #0x100]
ldp q2, q3, [x19, #0x120]
ldp q4, q5, [x19, #0x140]
ldp q6, q7, [x19, #0x160]
ldp q8, q9, [x19, #0x180]
ldp q10, q11, [x19, #0x1A0]
ldp q12, q13, [x19, #0x1C0]
ldp q14, q15, [x19, #0x1E0]
ldp q16, q17, [x19, #0x200]
ldp q18, q19, [x19, #0x220]
ldp q20, q21, [x19, #0x240]
ldp q22, q23, [x19, #0x260]
ldp q24, q25, [x19, #0x280]
ldp q26, q27, [x19, #0x2A0]
ldp q28, q29, [x19, #0x2C0]
ldp q30, q31, [x19, #0x2E0]
ldr x19, [x19, #0x98]
b #0
.global MrsTpidrroEl0PatchCode
MrsTpidrroEl0PatchCode:
stp xzr, x19, [sp, #-0x20]!
stp x20, x21, [sp, #0x10]
loadctxptr
ldr x19, [x19, #0x308]
str x19, [sp]
ldp x20, x21, [sp, #0x10]
ldr x19, [sp, #8]
ldr x0, [sp], #0x20
b #0
.global MrsTpidrEl0PatchCode
MrsTpidrEl0PatchCode:
stp xzr, x19, [sp, #-0x20]!
stp x20, x21, [sp, #0x10]
loadctxptr
ldr x19, [x19, #0x300]
str x19, [sp]
ldp x20, x21, [sp, #0x10]
ldr x19, [sp, #8]
ldr x0, [sp], #0x20
b #0
.global MrsCtrEl0PatchCode
MrsCtrEl0PatchCode:
stp xzr, x19, [sp, #-0x20]!
stp x20, x21, [sp, #0x10]
loadctxptr
ldr x19, [x19, #0x338]
str x19, [sp]
ldp x20, x21, [sp, #0x10]
ldr x19, [sp, #8]
ldr x0, [sp], #0x20
b #0
.global MsrTpidrEl0PatchCode
MsrTpidrEl0PatchCode:
stp x19, x0, [sp, #-0x20]!
stp x20, x21, [sp, #0x10]
loadctxptr
ldr x20, [sp, #8]
str x20, [x19, #0x300]
ldp x20, x21, [sp, #0x10]
ldr x19, [sp], #0x20
b #0
.global MrsCntpctEl0PatchCode
MrsCntpctEl0PatchCode:
stp x0, x1, [sp, #-0xC0]!
stp x2, x3, [sp, #0x10]
stp x4, x5, [sp, #0x20]
stp x6, x7, [sp, #0x30]
stp x8, x9, [sp, #0x40]
stp x10, x11, [sp, #0x50]
stp x12, x13, [sp, #0x60]
stp x14, x15, [sp, #0x70]
stp x16, x17, [sp, #0x80]
stp x18, x19, [sp, #0x90]
stp x20, x21, [sp, #0xA0]
str x30, [sp, #0xB8]
loadctxptr
mov w0, #1
str w0, [x19, #0x31C]
mov x0, #0
movk x0, #0, lsl #16
movk x0, #0, lsl #32
movk x0, #0, lsl #48
blr x0
str wzr, [x19, #0x31C]
str x0, [sp, #0xB0]
ldr x30, [sp, #0xB8]
ldp x20, x21, [sp, #0xA0]
ldp x18, x19, [sp, #0x90]
ldp x16, x17, [sp, #0x80]
ldp x14, x15, [sp, #0x70]
ldp x12, x13, [sp, #0x60]
ldp x10, x11, [sp, #0x50]
ldp x8, x9, [sp, #0x40]
ldp x6, x7, [sp, #0x30]
ldp x4, x5, [sp, #0x20]
ldp x2, x3, [sp, #0x10]
ldp x0, x1, [sp], #0xB0
ldr x0, [sp], #0x10
b #0

View File

@ -70,7 +70,7 @@ namespace Ryujinx.Cpu.Signal
config = new SignalHandlerConfig();
}
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null)
public static void InitializeSignalHandler(ulong pageSize, Func<IntPtr, IntPtr, IntPtr> customSignalHandlerFactory = null, int userSignal = -1)
{
if (_initialized)
{
@ -103,7 +103,7 @@ namespace Ryujinx.Cpu.Signal
_signalHandlerPtr = customSignalHandlerFactory(UnixSignalHandlerRegistration.GetSegfaultExceptionHandler().sa_handler, _signalHandlerPtr);
}
var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr);
var old = UnixSignalHandlerRegistration.RegisterExceptionHandler(_signalHandlerPtr, userSignal);
config.UnixOldSigaction = (nuint)(ulong)old.sa_handler;
config.UnixOldSigaction3Arg = old.sa_flags & 4;

View File

@ -82,7 +82,7 @@ namespace Ryujinx.Cpu.Signal
return old;
}
public static SigAction RegisterExceptionHandler(IntPtr action)
public static SigAction RegisterExceptionHandler(IntPtr action, int userSignal = -1)
{
int result;
SigAction old;
@ -150,6 +150,21 @@ namespace Ryujinx.Cpu.Signal
}
}
if (userSignal != -1)
{
result = sigaction(userSignal, ref sig, out SigAction oldu);
if (oldu.sa_handler != IntPtr.Zero)
{
throw new InvalidOperationException($"SIG{userSignal} is already in use.");
}
if (result != 0)
{
throw new InvalidOperationException($"Could not register SIG{userSignal} sigaction. Error: {result}");
}
}
return old;
}

View File

@ -16,13 +16,15 @@ namespace Ryujinx.HLE.HOS
ulong codeSize);
}
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, IMemoryManager
class ArmProcessContext<T> : IArmProcessContext where T : class, IVirtualMemoryManagerTracked, ICpuMemoryManager
{
private readonly ulong _pid;
private readonly GpuContext _gpuContext;
private readonly ICpuContext _cpuContext;
private T _memoryManager;
public ulong ReservedSize { get; }
public IVirtualMemoryManager AddressSpace => _memoryManager;
public ulong AddressSpaceSize { get; }
@ -33,7 +35,8 @@ namespace Ryujinx.HLE.HOS
GpuContext gpuContext,
T memoryManager,
ulong addressSpaceSize,
bool for64Bit)
bool for64Bit,
ulong reservedSize = 0UL)
{
if (memoryManager is IRefCounted rc)
{
@ -46,8 +49,8 @@ namespace Ryujinx.HLE.HOS
_gpuContext = gpuContext;
_cpuContext = cpuEngine.CreateCpuContext(memoryManager, for64Bit);
_memoryManager = memoryManager;
AddressSpaceSize = addressSpaceSize;
ReservedSize = reservedSize;
}
public IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks)
@ -78,6 +81,11 @@ namespace Ryujinx.HLE.HOS
_cpuContext.InvalidateCacheRegion(address, size);
}
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
{
_cpuContext.PatchCodeForNce(textAddress, textSize, patchRegionAddress, patchRegionSize);
}
public void Dispose()
{
if (_memoryManager is IRefCounted rc)

View File

@ -4,6 +4,7 @@ using Ryujinx.Cpu;
using Ryujinx.Cpu.AppleHv;
using Ryujinx.Cpu.Jit;
using Ryujinx.Cpu.LightningJit;
using Ryujinx.Cpu.Nce;
using Ryujinx.Graphics.Gpu;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Process;
@ -46,14 +47,31 @@ namespace Ryujinx.HLE.HOS
public IProcessContext Create(KernelContext context, ulong pid, ulong addressSpaceSize, InvalidAccessHandler invalidAccessHandler, bool for64Bit)
{
IArmProcessContext processContext;
AddressSpace addressSpace = null;
bool isArm64Host = RuntimeInformation.ProcessArchitecture == Architecture.Arm64;
if (OperatingSystem.IsMacOS() && isArm64Host && for64Bit && context.Device.Configuration.UseHypervisor)
{
var cpuEngine = new HvEngine(_tickSource);
var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
processContext = new ArmProcessContext<HvMemoryManager>(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit);
if (OperatingSystem.IsMacOS())
{
var cpuEngine = new HvEngine(_tickSource);
var memoryManager = new HvMemoryManager(context.Memory, addressSpaceSize, invalidAccessHandler);
processContext = new ArmProcessContext<HvMemoryManager>(pid, cpuEngine, _gpu, memoryManager, addressSpaceSize, for64Bit);
}
else
{
if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace))
{
throw new Exception("Address space creation failed");
}
Logger.Info?.Print(LogClass.Cpu, $"NCE Base AS Address: 0x{addressSpace.Base.Pointer.ToInt64():X} Size: 0x{addressSpace.AddressSpaceSize:X}");
var cpuEngine = new NceEngine(_tickSource);
var memoryManager = new MemoryManagerNative(addressSpace, context.Memory, addressSpaceSize, invalidAccessHandler);
processContext = new ArmProcessContext<MemoryManagerNative>(pid, cpuEngine, _gpu, memoryManager, addressSpace.AddressSpaceSize, for64Bit, memoryManager.ReservedSize);
}
}
else
{
@ -70,8 +88,6 @@ namespace Ryujinx.HLE.HOS
? new LightningJitEngine(_tickSource)
: new JitEngine(_tickSource);
AddressSpace addressSpace = null;
if (mode == MemoryManagerMode.HostMapped || mode == MemoryManagerMode.HostMappedUnsafe)
{
if (!AddressSpace.TryCreate(context.Memory, addressSpaceSize, MemoryBlock.GetPageSize() == MemoryManagerHostMapped.PageSize, out addressSpace))

View File

@ -107,6 +107,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryRegion memRegion,
ulong address,
ulong size,
ulong reservedSize,
KMemoryBlockSlabManager slabManager)
{
if ((uint)addrSpaceType > (uint)AddressSpaceType.Addr39Bits)
@ -128,6 +129,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
memRegion,
address,
size,
reservedSize,
slabManager);
if (result != Result.Success)
@ -155,6 +157,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
MemoryRegion memRegion,
ulong address,
ulong size,
ulong reservedSize,
KMemoryBlockSlabManager slabManager)
{
ulong endAddr = address + size;
@ -178,7 +181,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x200000;
codeRegionSize = 0x3fe00000;
AslrRegionStart = 0x200000;
AslrRegionEnd = AslrRegionStart + 0xffe00000;
AslrRegionEnd = 0x100000000;
stackAndTlsIoStart = 0x200000;
stackAndTlsIoEnd = 0x40000000;
break;
@ -191,7 +194,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x8000000;
codeRegionSize = 0x78000000;
AslrRegionStart = 0x8000000;
AslrRegionEnd = AslrRegionStart + 0xff8000000;
AslrRegionEnd = 0x1000000000;
stackAndTlsIoStart = 0x8000000;
stackAndTlsIoEnd = 0x80000000;
break;
@ -204,7 +207,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
CodeRegionStart = 0x200000;
codeRegionSize = 0x3fe00000;
AslrRegionStart = 0x200000;
AslrRegionEnd = AslrRegionStart + 0xffe00000;
AslrRegionEnd = 0x100000000;
stackAndTlsIoStart = 0x200000;
stackAndTlsIoEnd = 0x40000000;
break;
@ -222,8 +225,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
codeRegionSize = BitUtils.AlignUp<ulong>(endAddr, RegionAlignment) - CodeRegionStart;
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
AslrRegionStart = 0x8000000;
addrSpaceEnd = 1UL << addressSpaceWidth;
AslrRegionStart = reservedSize + 0x8000000;
addrSpaceEnd = reservedSize + (1UL << addressSpaceWidth);
AslrRegionEnd = addrSpaceEnd;
}
else
@ -234,8 +237,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Memory
tlsIoRegion.Size = 0x1000000000;
CodeRegionStart = BitUtils.AlignDown(address, RegionAlignment);
codeRegionSize = BitUtils.AlignUp(endAddr, RegionAlignment) - CodeRegionStart;
AslrRegionStart = 0x8000000;
AslrRegionEnd = AslrRegionStart + 0x7ff8000000;
AslrRegionStart = reservedSize + 0x8000000;
AslrRegionEnd = 0x8000000000;
stackAndTlsIoStart = 0;
stackAndTlsIoEnd = 0;
}

View File

@ -7,11 +7,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
interface IProcessContext : IDisposable
{
IVirtualMemoryManager AddressSpace { get; }
ulong ReservedSize { get; }
ulong AddressSpaceSize { get; }
IExecutionContext CreateExecutionContext(ExceptionCallbacks exceptionCallbacks);
void Execute(IExecutionContext context, ulong codeAddress);
void InvalidateCacheRegion(ulong address, ulong size);
void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize);
}
}

View File

@ -139,7 +139,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
ulong codeAddress = creationInfo.CodeAddress;
ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize;
ulong codeSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
@ -154,6 +154,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
memRegion,
codeAddress,
codeSize,
Context.ReservedSize,
slabManager);
if (result != Result.Success)
@ -189,7 +190,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
KResourceLimit resourceLimit,
MemoryRegion memRegion,
IProcessContextFactory contextFactory,
ThreadStart customThreadStart = null)
ThreadStart customThreadStart = null,
ulong entrypointOffset = 0UL)
{
ResourceLimit = resourceLimit;
_memRegion = memRegion;
@ -247,7 +249,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
bool aslrEnabled = creationInfo.Flags.HasFlag(ProcessCreationFlags.EnableAslr);
ulong codeAddress = creationInfo.CodeAddress;
ulong codeAddress = creationInfo.CodeAddress + Context.ReservedSize;
ulong codeSize = codePagesCount * KPageTableBase.PageSize;
@ -258,6 +260,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
memRegion,
codeAddress,
codeSize,
Context.ReservedSize,
slabManager);
if (result != Result.Success)
@ -303,6 +306,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
CleanUpForError();
}
_entrypoint += entrypointOffset;
return result;
}
@ -347,7 +352,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
Flags = creationInfo.Flags;
TitleId = creationInfo.TitleId;
_entrypoint = creationInfo.CodeAddress;
_entrypoint = creationInfo.CodeAddress + Context.ReservedSize;
_imageSize = (ulong)creationInfo.CodePagesCount * KPageTableBase.PageSize;
switch (Flags & ProcessCreationFlags.AddressSpaceMask)

View File

@ -7,6 +7,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
class ProcessContext : IProcessContext
{
public IVirtualMemoryManager AddressSpace { get; }
public ulong ReservedSize => 0UL;
public ulong AddressSpaceSize { get; }
@ -30,6 +31,10 @@ namespace Ryujinx.HLE.HOS.Kernel.Process
{
}
public void PatchCodeForNce(ulong textAddress, ulong textSize, ulong patchRegionAddress, ulong patchRegionSize)
{
}
public void Dispose()
{
}

View File

@ -133,6 +133,8 @@ namespace Ryujinx.HLE.Loaders.Processes
return resultCode;
}
private const int ReservedPatchSize = 0x100000;
public static bool LoadKip(KernelContext context, KipExecutable kip)
{
uint endOffset = kip.DataOffset + (uint)kip.Data.Length;
@ -197,7 +199,9 @@ namespace Ryujinx.HLE.Loaders.Processes
return false;
}
result = LoadIntoMemory(process, kip, codeBaseAddress);
// TODO: Support NCE of KIPs too.
result = LoadIntoMemory(process, kip, codeBaseAddress, 0UL);
if (result != Result.Success)
{
Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@ -257,6 +261,7 @@ namespace Ryujinx.HLE.Loaders.Processes
_ => "",
}).ToUpper());
ulong[] nsoPatch = new ulong[executables.Length];
ulong[] nsoBase = new ulong[executables.Length];
for (int index = 0; index < executables.Length; index++)
@ -281,6 +286,10 @@ namespace Ryujinx.HLE.Loaders.Processes
nsoSize = BitUtils.AlignUp<uint>(nsoSize, KPageTableBase.PageSize);
nsoPatch[index] = codeStart + codeSize;
codeSize += ReservedPatchSize;
nsoBase[index] = codeStart + codeSize;
codeSize += nsoSize;
@ -304,7 +313,7 @@ namespace Ryujinx.HLE.Loaders.Processes
programId,
codeStart,
codePagesCount,
(ProcessCreationFlags)meta.Flags | ProcessCreationFlags.IsApplication,
(ProcessCreationFlags)meta.Flags,
0,
personalMmHeapPagesCount);
@ -381,7 +390,8 @@ namespace Ryujinx.HLE.Loaders.Processes
MemoryMarshal.Cast<byte, uint>(npdm.KernelCapabilityData),
resourceLimit,
memoryRegion,
processContextFactory);
processContextFactory,
entrypointOffset: ReservedPatchSize);
if (result != Result.Success)
{
@ -392,9 +402,13 @@ namespace Ryujinx.HLE.Loaders.Processes
for (int index = 0; index < executables.Length; index++)
{
Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBase[index]:x16}...");
ulong nsoPatchAddress = process.Context.ReservedSize + nsoPatch[index];
ulong nsoBaseAddress = process.Context.ReservedSize + nsoBase[index];
Logger.Info?.Print(LogClass.Loader, $"Loading image {index} at 0x{nsoBaseAddress:x16}...");
result = LoadIntoMemory(process, executables[index], nsoBaseAddress, nsoPatchAddress);
result = LoadIntoMemory(process, executables[index], nsoBase[index]);
if (result != Result.Success)
{
Logger.Error?.Print(LogClass.Loader, $"Process initialization returned error \"{result}\".");
@ -433,7 +447,7 @@ namespace Ryujinx.HLE.Loaders.Processes
device.System.State.DesiredTitleLanguage);
}
public static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress)
private static Result LoadIntoMemory(KProcess process, IExecutable image, ulong baseAddress, ulong patchAddress)
{
ulong textStart = baseAddress + image.TextOffset;
ulong roStart = baseAddress + image.RoOffset;
@ -453,6 +467,8 @@ namespace Ryujinx.HLE.Loaders.Processes
process.CpuMemory.Fill(bssStart, image.BssSize, 0);
process.Context.PatchCodeForNce(textStart, (ulong)image.Text.Length, patchAddress, ReservedPatchSize);
Result SetProcessMemoryPermission(ulong address, ulong size, KMemoryPermission permission)
{
if (size == 0)

View File

@ -96,6 +96,11 @@ namespace Ryujinx.Memory
}
}
/// <inheritdoc/>
public void Reprotect(ulong va, ulong size, MemoryPermission permission)
{
}
/// <inheritdoc/>
public T Read<T>(ulong va) where T : unmanaged
{

View File

@ -44,6 +44,14 @@ namespace Ryujinx.Memory
/// <param name="size">Size of the range to be unmapped</param>
void Unmap(ulong va, ulong size);
/// <summary>
/// Reprotects a previously mapped range of virtual memory.
/// </summary>
/// <param name="va">Virtual address of the range to be reprotected</param>
/// <param name="size">Size of the range to be reprotected</param>
/// <param name="permission">New protection of the memory range</param>
void Reprotect(ulong va, ulong size, MemoryPermission permission);
/// <summary>
/// Reads data from CPU mapped memory.
/// </summary>

View File

@ -16,15 +16,15 @@ namespace Ryujinx.Memory
public static IntPtr Allocate(ulong size, bool forJit)
{
return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit);
return AllocateInternal(size, MmapProts.PROT_READ | MmapProts.PROT_WRITE, forJit, false);
}
public static IntPtr Reserve(ulong size, bool forJit)
{
return AllocateInternal(size, MmapProts.PROT_NONE, forJit);
return AllocateInternal(size, MmapProts.PROT_NONE, forJit, false);
}
private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared = false)
private static IntPtr AllocateInternal(ulong size, MmapProts prot, bool forJit, bool shared)
{
MmapFlags flags = MmapFlags.MAP_ANONYMOUS;