using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common.Logging; using System; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; using CpuAddress = System.UInt64; using DspAddress = System.UInt64; namespace Ryujinx.Audio.Renderer.Server.MemoryPool { /// /// Memory pool mapping helper. /// public class PoolMapper { const uint CurrentProcessPseudoHandle = 0xFFFF8001; /// /// The result of . /// public enum UpdateResult : uint { /// /// No error reported. /// Success = 0, /// /// The user parameters were invalid. /// InvalidParameter = 1, /// /// mapping failed. /// MapError = 2, /// /// unmapping failed. /// UnmapError = 3 } /// /// The handle of the process owning the CPU memory manipulated. /// private uint _processHandle; /// /// The that will be manipulated. /// private Memory _memoryPools; /// /// If set to true, this will try to force map memory pool even if their state are considered invalid. /// private bool _isForceMapEnabled; /// /// Create a new used for system mapping. /// /// The handle of the process owning the CPU memory manipulated. /// If set to true, this will try to force map memory pool even if their state are considered invalid. public PoolMapper(uint processHandle, bool isForceMapEnabled) { _processHandle = processHandle; _isForceMapEnabled = isForceMapEnabled; _memoryPools = Memory.Empty; } /// /// Create a new used for user mapping. /// /// The handle of the process owning the CPU memory manipulated. /// The user memory pools. /// If set to true, this will try to force map memory pool even if their state are considered invalid. public PoolMapper(uint processHandle, Memory memoryPool, bool isForceMapEnabled) { _processHandle = processHandle; _memoryPools = memoryPool; _isForceMapEnabled = isForceMapEnabled; } /// /// Initialize the for system use. /// /// The for system use. /// The to assign. /// The size to assign. /// Returns true if mapping on the succeeded. public bool InitializeSystemPool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) { if (memoryPool.Location != MemoryPoolState.LocationType.Dsp) { return false; } return InitializePool(ref memoryPool, cpuAddress, size); } /// /// Initialize the . /// /// The . /// The to assign. /// The size to assign. /// Returns true if mapping on the succeeded. public bool InitializePool(ref MemoryPoolState memoryPool, CpuAddress cpuAddress, ulong size) { memoryPool.SetCpuAddress(cpuAddress, size); return Map(ref memoryPool) != 0; } /// /// Get the process handle associated to the . /// /// The . /// Returns the process handle associated to the . public uint GetProcessHandle(ref MemoryPoolState memoryPool) { if (memoryPool.Location == MemoryPoolState.LocationType.Cpu) { return CurrentProcessPseudoHandle; } else if (memoryPool.Location == MemoryPoolState.LocationType.Dsp) { return _processHandle; } return 0; } /// /// Map the on the . /// /// The to map. /// Returns the DSP address mapped. public DspAddress Map(ref MemoryPoolState memoryPool) { DspAddress result = AudioProcessorMemoryManager.Map(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); if (result != 0) { memoryPool.DspAddress = result; } return result; } /// /// Unmap the from the . /// /// The to unmap. /// Returns true if unmapped. public bool Unmap(ref MemoryPoolState memoryPool) { if (memoryPool.IsUsed) { return false; } AudioProcessorMemoryManager.Unmap(GetProcessHandle(ref memoryPool), memoryPool.CpuAddress, memoryPool.Size); memoryPool.SetCpuAddress(0, 0); memoryPool.DspAddress = 0; return true; } /// /// Find a associated to the region given. /// /// The region . /// The region size. /// Returns the found or if not found. private Span FindMemoryPool(CpuAddress cpuAddress, ulong size) { if (!_memoryPools.IsEmpty && _memoryPools.Length > 0) { for (int i = 0; i < _memoryPools.Length; i++) { if (_memoryPools.Span[i].Contains(cpuAddress, size)) { return _memoryPools.Span.Slice(i, 1); } } } return Span.Empty; } /// /// Force unmap the given . /// /// The to force unmap public void ForceUnmap(ref AddressInfo addressInfo) { if (_isForceMapEnabled) { Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); if (!memoryPool.IsEmpty) { AudioProcessorMemoryManager.Unmap(_processHandle, memoryPool[0].CpuAddress, memoryPool[0].Size); return; } AudioProcessorMemoryManager.Unmap(_processHandle, addressInfo.CpuAddress, 0); } } /// /// Try to attach the given region to the . /// /// The error information if an error was generated. /// The to attach the region to. /// The region . /// The region size. /// Returns true if mapping was performed. public bool TryAttachBuffer(out ErrorInfo errorInfo, ref AddressInfo addressInfo, CpuAddress cpuAddress, ulong size) { errorInfo = new ErrorInfo(); addressInfo.Setup(cpuAddress, size); if (AssignDspAddress(ref addressInfo)) { errorInfo.ErrorCode = 0x0; errorInfo.ExtraErrorInfo = 0x0; return true; } else { errorInfo.ErrorCode = ResultCode.InvalidAddressInfo; errorInfo.ExtraErrorInfo = addressInfo.CpuAddress; return _isForceMapEnabled; } } /// /// Update a using user parameters. /// /// The to update. /// Input user parameter. /// Output user parameter. /// Returns the of the operations performed. public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) { MemoryPoolUserState inputState = inParameter.State; MemoryPoolUserState outputState; const uint pageSize = 0x1000; if (inputState != MemoryPoolUserState.RequestAttach && inputState != MemoryPoolUserState.RequestDetach) { return UpdateResult.Success; } if (inParameter.CpuAddress == 0 || (inParameter.CpuAddress % pageSize) != 0) { return UpdateResult.InvalidParameter; } if (inParameter.Size == 0 || (inParameter.Size % pageSize) != 0) { return UpdateResult.InvalidParameter; } if (inputState == MemoryPoolUserState.RequestAttach) { bool initializeSuccess = InitializePool(ref memoryPool, inParameter.CpuAddress, inParameter.Size); if (!initializeSuccess) { memoryPool.SetCpuAddress(0, 0); Logger.Error?.Print(LogClass.AudioRenderer, $"Map of memory pool (address: 0x{inParameter.CpuAddress:x}, size 0x{inParameter.Size:x}) failed!"); return UpdateResult.MapError; } outputState = MemoryPoolUserState.Attached; } else { if (memoryPool.CpuAddress != inParameter.CpuAddress || memoryPool.Size != inParameter.Size) { return UpdateResult.InvalidParameter; } if (!Unmap(ref memoryPool)) { Logger.Error?.Print(LogClass.AudioRenderer, $"Unmap of memory pool (address: 0x{memoryPool.CpuAddress:x}, size 0x{memoryPool.Size:x}) failed!"); return UpdateResult.UnmapError; } outputState = MemoryPoolUserState.Detached; } outStatus.State = outputState; return UpdateResult.Success; } /// /// Map the to the . /// /// The to map. /// Returns true if mapping was performed. private bool AssignDspAddress(ref AddressInfo addressInfo) { if (addressInfo.CpuAddress == 0) { return false; } if (_memoryPools.Length > 0) { Span memoryPool = FindMemoryPool(addressInfo.CpuAddress, addressInfo.Size); if (!memoryPool.IsEmpty) { addressInfo.SetupMemoryPool(memoryPool); return true; } } if (_isForceMapEnabled) { DspAddress dspAddress = AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); addressInfo.ForceMappedDspAddress = dspAddress; AudioProcessorMemoryManager.Map(_processHandle, addressInfo.CpuAddress, addressInfo.Size); } else { unsafe { addressInfo.SetupMemoryPool(MemoryPoolState.Null); } } return false; } /// /// Remove the usage flag from all the . /// /// The to reset. public static void ClearUsageState(Memory memoryPool) { foreach (ref MemoryPoolState info in memoryPool.Span) { info.IsUsed = false; } } } }