From cf6cd714884c41e9550757e364c2f4f5b04fc7f3 Mon Sep 17 00:00:00 2001 From: gdkchan Date: Tue, 1 Dec 2020 20:23:43 -0300 Subject: IPC refactor part 2: Use ReplyAndReceive on HLE services and remove special handling from kernel (#1458) * IPC refactor part 2: Use ReplyAndReceive on HLE services and remove special handling from kernel * Fix for applet transfer memory + some nits * Keep handles if possible to avoid server handle table exhaustion * Fix IPC ZeroFill bug * am: Correctly implement CreateManagedDisplayLayer and implement CreateManagedDisplaySeparableLayer CreateManagedDisplaySeparableLayer is requires since 10.x+ when appletResourceUserId != 0 * Make it exit properly * Make ServiceNotImplementedException show the full message again * Allow yielding execution to avoid starving other threads * Only wait if active * Merge IVirtualMemoryManager and IAddressSpaceManager * Fix Ro loading data from the wrong process Co-authored-by: Thog --- Ryujinx.Memory/AddressSpaceManager.cs | 549 ++++++++++++++++++++++++++++++++++ 1 file changed, 549 insertions(+) create mode 100644 Ryujinx.Memory/AddressSpaceManager.cs (limited to 'Ryujinx.Memory/AddressSpaceManager.cs') diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs new file mode 100644 index 00000000..c6d6cab5 --- /dev/null +++ b/Ryujinx.Memory/AddressSpaceManager.cs @@ -0,0 +1,549 @@ +using Ryujinx.Common; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Memory +{ + /// + /// Represents a address space manager. + /// Supports virtual memory region mapping, address translation and read/write access to mapped regions. + /// + public sealed class AddressSpaceManager : IVirtualMemoryManager + { + public const int PageBits = 12; + public const int PageSize = 1 << PageBits; + public const int PageMask = PageSize - 1; + + private const int PtLevelBits = 9; // 9 * 4 + 12 = 48 (max address space size) + private const int PtLevelSize = 1 << PtLevelBits; + private const int PtLevelMask = PtLevelSize - 1; + + private const ulong Unmapped = ulong.MaxValue; + + /// + /// Address space width in bits. + /// + public int AddressSpaceBits { get; } + + private readonly ulong _addressSpaceSize; + + private readonly MemoryBlock _backingMemory; + + private readonly ulong[][][][] _pageTable; + + /// + /// Creates a new instance of the memory manager. + /// + /// Physical backing memory where virtual memory will be mapped to + /// Size of the address space + public AddressSpaceManager(MemoryBlock backingMemory, ulong addressSpaceSize) + { + ulong asSize = PageSize; + int asBits = PageBits; + + while (asSize < addressSpaceSize) + { + asSize <<= 1; + asBits++; + } + + AddressSpaceBits = asBits; + _addressSpaceSize = asSize; + _backingMemory = backingMemory; + _pageTable = new ulong[PtLevelSize][][][]; + } + + /// + /// Maps a virtual memory range into a physical memory range. + /// + /// + /// Addresses and size must be page aligned. + /// + /// Virtual memory address + /// Physical memory address + /// Size to be mapped + public void Map(ulong va, ulong pa, ulong size) + { + while (size != 0) + { + PtMap(va, pa); + + va += PageSize; + pa += PageSize; + size -= PageSize; + } + } + + /// + /// Unmaps a previously mapped range of virtual memory. + /// + /// Virtual address of the range to be unmapped + /// Size of the range to be unmapped + public void Unmap(ulong va, ulong size) + { + while (size != 0) + { + PtUnmap(va); + + va += PageSize; + size -= PageSize; + } + } + + /// + /// Reads data from mapped memory. + /// + /// Type of the data being read + /// Virtual address of the data in memory + /// The data + /// Throw for unhandled invalid or unmapped memory accesses + public T Read(ulong va) where T : unmanaged + { + return MemoryMarshal.Cast(GetSpan(va, Unsafe.SizeOf()))[0]; + } + + /// + /// Reads data from mapped memory. + /// + /// Virtual address of the data in memory + /// Span to store the data being read into + /// Throw for unhandled invalid or unmapped memory accesses + public void Read(ulong va, Span data) + { + ReadImpl(va, data); + } + + /// + /// Writes data to mapped memory. + /// + /// Type of the data being written + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + public void Write(ulong va, T value) where T : unmanaged + { + Write(va, MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref value, 1))); + } + + /// + /// Writes data to mapped memory. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + public void Write(ulong va, ReadOnlySpan data) + { + if (data.Length == 0) + { + return; + } + + if (IsContiguousAndMapped(va, data.Length)) + { + data.CopyTo(_backingMemory.GetSpan(GetPhysicalAddressInternal(va), data.Length)); + } + else + { + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = GetPhysicalAddressInternal(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + data.Slice(0, size).CopyTo(_backingMemory.GetSpan(pa, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + data.Slice(offset, size).CopyTo(_backingMemory.GetSpan(pa, size)); + } + } + } + + /// + /// Gets a read-only span of data from mapped memory. + /// + /// + /// This may perform a allocation if the data is not contiguous in memory. + /// For this reason, the span is read-only, you can't modify the data. + /// + /// Virtual address of the data + /// Size of the data + /// True if read tracking is triggered on the span + /// A read-only span of the data + /// Throw for unhandled invalid or unmapped memory accesses + public ReadOnlySpan GetSpan(ulong va, int size, bool tracked = false) + { + if (size == 0) + { + return ReadOnlySpan.Empty; + } + + if (IsContiguousAndMapped(va, size)) + { + return _backingMemory.GetSpan(GetPhysicalAddressInternal(va), size); + } + else + { + Span data = new byte[size]; + + ReadImpl(va, data); + + return data; + } + } + + /// + /// Gets a region of memory that can be written to. + /// + /// + /// If the requested region is not contiguous in physical memory, + /// this will perform an allocation, and flush the data (writing it + /// back to the backing memory) on disposal. + /// + /// Virtual address of the data + /// Size of the data + /// A writable region of memory containing the data + /// Throw for unhandled invalid or unmapped memory accesses + public WritableRegion GetWritableRegion(ulong va, int size) + { + if (size == 0) + { + return new WritableRegion(null, va, Memory.Empty); + } + + if (IsContiguousAndMapped(va, size)) + { + return new WritableRegion(null, va, _backingMemory.GetMemory(GetPhysicalAddressInternal(va), size)); + } + else + { + Memory memory = new byte[size]; + + GetSpan(va, size).CopyTo(memory.Span); + + return new WritableRegion(this, va, memory); + } + } + + /// + /// Gets a reference for the given type at the specified virtual memory address. + /// + /// + /// The data must be located at a contiguous memory region. + /// + /// Type of the data to get the reference + /// Virtual address of the data + /// A reference to the data in memory + /// Throw if the specified memory region is not contiguous in physical memory + public ref T GetRef(ulong va) where T : unmanaged + { + if (!IsContiguous(va, Unsafe.SizeOf())) + { + ThrowMemoryNotContiguous(); + } + + return ref _backingMemory.GetRef(GetPhysicalAddressInternal(va)); + } + + private void ThrowMemoryNotContiguous() => throw new MemoryNotContiguousException(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguousAndMapped(ulong va, int size) => IsContiguous(va, size) && IsMapped(va); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool IsContiguous(ulong va, int size) + { + if (!ValidateAddress(va)) + { + return false; + } + + ulong endVa = (va + (ulong)size + PageMask) & ~(ulong)PageMask; + + va &= ~(ulong)PageMask; + + int pages = (int)((endVa - va) / PageSize); + + for (int page = 0; page < pages - 1; page++) + { + if (!ValidateAddress(va + PageSize)) + { + return false; + } + + if (GetPhysicalAddressInternal(va) + PageSize != GetPhysicalAddressInternal(va + PageSize)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + /// + /// Gets the physical regions that make up the given virtual address region. + /// If any part of the virtual region is unmapped, null is returned. + /// + /// Virtual address of the range + /// Size of the range + /// Array of physical regions + public (ulong address, ulong size)[] GetPhysicalRegions(ulong va, ulong size) + { + throw new NotImplementedException(); + } + + private void ReadImpl(ulong va, Span data) + { + if (data.Length == 0) + { + return; + } + + int offset = 0, size; + + if ((va & PageMask) != 0) + { + ulong pa = GetPhysicalAddressInternal(va); + + size = Math.Min(data.Length, PageSize - (int)(va & PageMask)); + + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(0, size)); + + offset += size; + } + + for (; offset < data.Length; offset += size) + { + ulong pa = GetPhysicalAddressInternal(va + (ulong)offset); + + size = Math.Min(data.Length - offset, PageSize); + + _backingMemory.GetSpan(pa, size).CopyTo(data.Slice(offset, size)); + } + } + + /// + /// Checks if the page at a given virtual address is mapped. + /// + /// Virtual address to check + /// True if the address is mapped, false otherwise + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool IsMapped(ulong va) + { + if (!ValidateAddress(va)) + { + return false; + } + + return PtRead(va) != Unmapped; + } + + /// + /// Checks if a memory range is mapped. + /// + /// Virtual address of the range + /// Size of the range in bytes + /// True if the entire range is mapped, false otherwise + public bool IsRangeMapped(ulong va, ulong size) + { + if (size == 0UL) + { + return true; + } + + ulong endVa = (va + size + PageMask) & ~(ulong)PageMask; + + va &= ~(ulong)PageMask; + + while (va < endVa) + { + if (!IsMapped(va)) + { + return false; + } + + va += PageSize; + } + + return true; + } + + private bool ValidateAddress(ulong va) + { + return va < _addressSpaceSize; + } + + /// + /// Performs address translation of the address inside a mapped memory range. + /// + /// + /// If the address is invalid or unmapped, -1 will be returned. + /// + /// Virtual address to be translated + /// The physical address + public ulong GetPhysicalAddress(ulong va) + { + // We return -1L if the virtual address is invalid or unmapped. + if (!ValidateAddress(va) || !IsMapped(va)) + { + return ulong.MaxValue; + } + + return GetPhysicalAddressInternal(va); + } + + private ulong GetPhysicalAddressInternal(ulong va) + { + return PtRead(va) + (va & PageMask); + } + + /// + /// Reprotect a region of virtual memory for tracking. Sets software protection bits. + /// + /// Virtual address base + /// Size of the region to protect + /// Memory protection to set + public void TrackingReprotect(ulong va, ulong size, MemoryPermission protection) + { + throw new NotImplementedException(); + } + + private ulong PtRead(ulong va) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + return Unmapped; + } + + if (_pageTable[l0][l1] == null) + { + return Unmapped; + } + + if (_pageTable[l0][l1][l2] == null) + { + return Unmapped; + } + + return _pageTable[l0][l1][l2][l3]; + } + + private void PtMap(ulong va, ulong value) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + _pageTable[l0] = new ulong[PtLevelSize][][]; + } + + if (_pageTable[l0][l1] == null) + { + _pageTable[l0][l1] = new ulong[PtLevelSize][]; + } + + if (_pageTable[l0][l1][l2] == null) + { + _pageTable[l0][l1][l2] = new ulong[PtLevelSize]; + + for (int i = 0; i < _pageTable[l0][l1][l2].Length; i++) + { + _pageTable[l0][l1][l2][i] = Unmapped; + } + } + + _pageTable[l0][l1][l2][l3] = value; + } + + private void PtUnmap(ulong va) + { + int l3 = (int)(va >> PageBits) & PtLevelMask; + int l2 = (int)(va >> (PageBits + PtLevelBits)) & PtLevelMask; + int l1 = (int)(va >> (PageBits + PtLevelBits * 2)) & PtLevelMask; + int l0 = (int)(va >> (PageBits + PtLevelBits * 3)) & PtLevelMask; + + if (_pageTable[l0] == null) + { + return; + } + + if (_pageTable[l0][l1] == null) + { + return; + } + + if (_pageTable[l0][l1][l2] == null) + { + return; + } + + _pageTable[l0][l1][l2][l3] = Unmapped; + + bool empty = true; + + for (int i = 0; i < _pageTable[l0][l1][l2].Length; i++) + { + empty &= (_pageTable[l0][l1][l2][i] == Unmapped); + } + + if (empty) + { + _pageTable[l0][l1][l2] = null; + + RemoveIfAllNull(l0, l1); + } + } + + private void RemoveIfAllNull(int l0, int l1) + { + bool empty = true; + + for (int i = 0; i < _pageTable[l0][l1].Length; i++) + { + empty &= (_pageTable[l0][l1][i] == null); + } + + if (empty) + { + _pageTable[l0][l1] = null; + + RemoveIfAllNull(l0); + } + } + + private void RemoveIfAllNull(int l0) + { + bool empty = true; + + for (int i = 0; i < _pageTable[l0].Length; i++) + { + empty &= (_pageTable[l0][i] == null); + } + + if (empty) + { + _pageTable[l0] = null; + } + } + } +} -- cgit v1.2.3