From f214df5d2cd6461322640deb853ac18e713ad10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20Lam?= Date: Sun, 14 Feb 2021 00:08:07 +0100 Subject: [PATCH] IOS/FS: Allow IPC interface to be used internally from IOS HLE This makes it more convenient to emulate timings for IPC commands that perform internal IOS <-> IOS IPC, for example ES relying on FS for filesystem access. --- Source/Core/Core/IOS/FS/FileSystemProxy.cpp | 182 +++++++++++++------- Source/Core/Core/IOS/FS/FileSystemProxy.h | 26 ++- Source/Core/Core/IOS/IOS.h | 32 ++++ Source/Core/Core/State.cpp | 2 +- 4 files changed, 172 insertions(+), 70 deletions(-) diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp index bb4e76a341..dd7ae5ceb8 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.cpp +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.cpp @@ -22,10 +22,16 @@ namespace IOS::HLE { using namespace IOS::HLE::FS; -static IPCReply GetFSReply(s32 return_value, u64 extra_tb_ticks = 0) +static constexpr u64 GetIPCOverheadTicks() { // According to hardware tests, FS takes at least 2700 TB ticks to reply to commands. - return IPCReply{return_value, (2700 + extra_tb_ticks) * SystemTimers::TIMER_RATIO}; + return 2700; +} + +static IPCReply GetFSReply(s32 return_value, u64 extra_tb_ticks = 0) +{ + return IPCReply{return_value, + (GetIPCOverheadTicks() + extra_tb_ticks) * SystemTimers::TIMER_RATIO}; } /// Duration of a superblock write (in timebase ticks). @@ -95,10 +101,11 @@ FSDevice::FSDevice(Kernel& ios, const std::string& device_name) : Device(ios, de void FSDevice::DoState(PointerWrap& p) { - p.Do(m_fd_map); - p.Do(m_cache_fd); - p.Do(m_cache_chain_index); p.Do(m_dirty_cache); + p.Do(m_cache_chain_index); + p.Do(m_cache_fd); + p.Do(m_next_fd); + p.Do(m_fd_map); } template @@ -153,59 +160,78 @@ static IPCReply GetReplyForSuperblockOperation(int ios_version, ResultCode resul std::optional FSDevice::Open(const OpenRequest& request) { + return MakeIPCReply([&](Ticks t) { + return Open(request.uid, request.gid, request.path, static_cast(request.flags & 3), + request.fd, t); + }); +} + +s64 FSDevice::Open(FS::Uid uid, FS::Gid gid, const std::string& path, FS::Mode mode, + std::optional ipc_fd, Ticks ticks) +{ + ticks.AddTimeBaseTicks(GetIPCOverheadTicks()); + if (m_fd_map.size() >= 16) - return GetFSReply(ConvertResult(ResultCode::NoFreeHandle)); + return ConvertResult(ResultCode::NoFreeHandle); - if (request.path.size() >= 64) - return GetFSReply(ConvertResult(ResultCode::Invalid)); + if (path.size() >= 64) + return ConvertResult(ResultCode::Invalid); - if (request.path == "/dev/fs") + const u64 fd = ipc_fd.has_value() ? u64(*ipc_fd) : m_next_fd++; + + if (path == "/dev/fs") { - m_fd_map[request.fd] = {request.gid, request.uid, INVALID_FD}; - return GetFSReply(IPC_SUCCESS); + m_fd_map[fd] = {gid, uid, INVALID_FD}; + return fd; } - const u64 ticks = EstimateFileLookupTicks(request.path, FileLookupMode::Normal); + ticks.AddTimeBaseTicks(EstimateFileLookupTicks(path, FileLookupMode::Normal)); - auto backend_fd = m_ios.GetFS()->OpenFile(request.uid, request.gid, request.path, - static_cast(request.flags & 3)); - LogResult(backend_fd, "OpenFile({})", request.path); + auto backend_fd = m_ios.GetFS()->OpenFile(uid, gid, path, mode); + LogResult(backend_fd, "OpenFile({})", path); if (!backend_fd) - return GetFSReply(ConvertResult(backend_fd.Error()), ticks); + return ConvertResult(backend_fd.Error()); - m_fd_map[request.fd] = {request.gid, request.uid, backend_fd->Release()}; - std::strncpy(m_fd_map[request.fd].name.data(), request.path.c_str(), 64); - return GetFSReply(IPC_SUCCESS, ticks); + auto& handle = m_fd_map[fd] = {gid, uid, backend_fd->Release()}; + std::strncpy(handle.name.data(), path.c_str(), handle.name.size()); + return fd; } std::optional FSDevice::Close(u32 fd) { - u64 ticks = 0; - if (m_fd_map[fd].fs_fd != INVALID_FD) + return MakeIPCReply([&](Ticks t) { return Close(static_cast(fd), t); }); +} + +s32 FSDevice::Close(u64 fd, Ticks ticks) +{ + ticks.AddTimeBaseTicks(GetIPCOverheadTicks()); + + const auto& handle = m_fd_map[fd]; + if (handle.fs_fd != INVALID_FD) { if (fd == m_cache_fd) { - ticks += SimulateFlushFileCache(); - m_cache_fd = INVALID_FD; + ticks.AddTimeBaseTicks(SimulateFlushFileCache()); + m_cache_fd.reset(); } - if (m_fd_map[fd].superblock_flush_needed) - ticks += GetSuperblockWriteTbTicks(m_ios.GetVersion()); + if (handle.superblock_flush_needed) + ticks.AddTimeBaseTicks(GetSuperblockWriteTbTicks(m_ios.GetVersion())); - const ResultCode result = m_ios.GetFS()->Close(m_fd_map[fd].fs_fd); - LogResult(result, "Close({})", m_fd_map[fd].name.data()); + const ResultCode result = m_ios.GetFS()->Close(handle.fs_fd); + LogResult(result, "Close({})", handle.name.data()); m_fd_map.erase(fd); if (result != ResultCode::Success) - return GetFSReply(ConvertResult(result)); + return ConvertResult(result); } else { m_fd_map.erase(fd); } - return GetFSReply(IPC_SUCCESS, ticks); + return IPC_SUCCESS; } -u64 FSDevice::SimulatePopulateFileCache(u32 fd, u32 offset, u32 file_size) +u64 FSDevice::SimulatePopulateFileCache(u64 fd, u32 offset, u32 file_size) { if (HasCacheForFile(fd, offset)) return 0; @@ -221,22 +247,23 @@ u64 FSDevice::SimulatePopulateFileCache(u32 fd, u32 offset, u32 file_size) u64 FSDevice::SimulateFlushFileCache() { - if (m_cache_fd == INVALID_FD || !m_dirty_cache) + if (!m_cache_fd.has_value() || !m_dirty_cache) return 0; m_dirty_cache = false; - m_fd_map[m_cache_fd].superblock_flush_needed = true; + m_fd_map[*m_cache_fd].superblock_flush_needed = true; return GetClusterWriteTbTicks(m_ios.GetVersion()); } // Simulate parts of the FS read/write logic to estimate ticks for file operations correctly. -u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, const ReadWriteRequest& request) +u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, u64 fd, IPCCommandType command, + u32 size) { u64 ticks = 0; - const bool is_write = request.command == IPC_CMD_WRITE; + const bool is_write = command == IPC_CMD_WRITE; const Result status = m_ios.GetFS()->GetFileStatus(handle.fs_fd); u32 offset = status->offset; - u32 count = request.size; + u32 count = size; if (!is_write && count + offset > status->size) count = status->size - offset; @@ -244,17 +271,17 @@ u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, const ReadWriteReq { u32 copy_length; // Fast path (if not cached): FS copies an entire cluster directly from/to the request. - if (!HasCacheForFile(request.fd, offset) && count >= CLUSTER_DATA_SIZE && + if (!HasCacheForFile(fd, offset) && count >= CLUSTER_DATA_SIZE && offset % CLUSTER_DATA_SIZE == 0) { ticks += (is_write ? GetClusterWriteTbTicks : GetClusterReadTbTicks)(m_ios.GetVersion()); copy_length = CLUSTER_DATA_SIZE; if (is_write) - m_fd_map[request.fd].superblock_flush_needed = true; + m_fd_map[fd].superblock_flush_needed = true; } else { - ticks += SimulatePopulateFileCache(request.fd, offset, status->size); + ticks += SimulatePopulateFileCache(fd, offset, status->size); const u32 start = offset - m_cache_chain_index * CLUSTER_DATA_SIZE; copy_length = std::min(CLUSTER_DATA_SIZE - start, count); @@ -276,7 +303,7 @@ u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, const ReadWriteReq return ticks; } -bool FSDevice::HasCacheForFile(u32 fd, u32 offset) const +bool FSDevice::HasCacheForFile(u64 fd, u32 offset) const { const u16 chain_index = static_cast(offset / CLUSTER_DATA_SIZE); return m_cache_fd == fd && m_cache_chain_index == chain_index; @@ -284,52 +311,81 @@ bool FSDevice::HasCacheForFile(u32 fd, u32 offset) const std::optional FSDevice::Read(const ReadWriteRequest& request) { - const Handle& handle = m_fd_map[request.fd]; + return MakeIPCReply([&](Ticks t) { + return Read(request.fd, Memory::GetPointer(request.buffer), request.size, request.buffer, t); + }); +} + +s32 FSDevice::Read(u64 fd, u8* data, u32 size, std::optional ipc_buffer_addr, Ticks ticks) +{ + ticks.AddTimeBaseTicks(GetIPCOverheadTicks()); + + const Handle& handle = m_fd_map[fd]; if (handle.fs_fd == INVALID_FD) - return GetFSReply(ConvertResult(ResultCode::Invalid)); + return ConvertResult(ResultCode::Invalid); // Simulate the FS read logic to estimate ticks. Note: this must be done before reading. - const u64 ticks = EstimateTicksForReadWrite(handle, request); + ticks.AddTimeBaseTicks(EstimateTicksForReadWrite(handle, fd, IPC_CMD_READ, size)); + + const Result result = m_ios.GetFS()->ReadBytesFromFile(handle.fs_fd, data, size); + if (ipc_buffer_addr) + LogResult(result, "Read({}, 0x{:08x}, {})", handle.name.data(), *ipc_buffer_addr, size); - const Result result = m_ios.GetFS()->ReadBytesFromFile( - handle.fs_fd, Memory::GetPointer(request.buffer), request.size); - LogResult(result, "Read({}, 0x{:08x}, {})", handle.name.data(), request.buffer, request.size); if (!result) - return GetFSReply(ConvertResult(result.Error())); + return ConvertResult(result.Error()); - return GetFSReply(*result, ticks); + return *result; } std::optional FSDevice::Write(const ReadWriteRequest& request) { - const Handle& handle = m_fd_map[request.fd]; + return MakeIPCReply([&](Ticks t) { + return Write(request.fd, Memory::GetPointer(request.buffer), request.size, request.buffer, t); + }); +} + +s32 FSDevice::Write(u64 fd, const u8* data, u32 size, std::optional ipc_buffer_addr, + Ticks ticks) +{ + ticks.AddTimeBaseTicks(GetIPCOverheadTicks()); + + const Handle& handle = m_fd_map[fd]; if (handle.fs_fd == INVALID_FD) - return GetFSReply(ConvertResult(ResultCode::Invalid)); + return ConvertResult(ResultCode::Invalid); // Simulate the FS write logic to estimate ticks. Must be done before writing. - const u64 ticks = EstimateTicksForReadWrite(handle, request); + ticks.AddTimeBaseTicks(EstimateTicksForReadWrite(handle, fd, IPC_CMD_WRITE, size)); + + const Result result = m_ios.GetFS()->WriteBytesToFile(handle.fs_fd, data, size); + if (ipc_buffer_addr) + LogResult(result, "Write({}, 0x{:08x}, {})", handle.name.data(), *ipc_buffer_addr, size); - const Result result = m_ios.GetFS()->WriteBytesToFile( - handle.fs_fd, Memory::GetPointer(request.buffer), request.size); - LogResult(result, "Write({}, 0x{:08x}, {})", handle.name.data(), request.buffer, request.size); if (!result) - return GetFSReply(ConvertResult(result.Error())); + return ConvertResult(result.Error()); - return GetFSReply(*result, ticks); + return *result; } std::optional FSDevice::Seek(const SeekRequest& request) { - const Handle& handle = m_fd_map[request.fd]; - if (handle.fs_fd == INVALID_FD) - return GetFSReply(ConvertResult(ResultCode::Invalid)); + return MakeIPCReply([&](Ticks t) { + return Seek(request.fd, request.offset, HLE::FS::SeekMode(request.mode), t); + }); +} - const Result result = - m_ios.GetFS()->SeekFile(handle.fs_fd, request.offset, FS::SeekMode(request.mode)); - LogResult(result, "Seek({}, 0x{:08x}, {})", handle.name.data(), request.offset, request.mode); +s32 FSDevice::Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks) +{ + ticks.AddTimeBaseTicks(GetIPCOverheadTicks()); + + const Handle& handle = m_fd_map[fd]; + if (handle.fs_fd == INVALID_FD) + return ConvertResult(ResultCode::Invalid); + + const Result result = m_ios.GetFS()->SeekFile(handle.fs_fd, offset, mode); + LogResult(result, "Seek({}, 0x{:08x}, {})", handle.name.data(), offset, mode); if (!result) - return GetFSReply(ConvertResult(result.Error())); - return GetFSReply(*result); + return ConvertResult(result.Error()); + return *result; } #pragma pack(push, 1) diff --git a/Source/Core/Core/IOS/FS/FileSystemProxy.h b/Source/Core/Core/IOS/FS/FileSystemProxy.h index e2967eb5dc..a2a1c20428 100644 --- a/Source/Core/Core/IOS/FS/FileSystemProxy.h +++ b/Source/Core/Core/IOS/FS/FileSystemProxy.h @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include "Common/CommonTypes.h" #include "Core/IOS/Device.h" @@ -24,6 +26,16 @@ class FSDevice : public Device public: FSDevice(Kernel& ios, const std::string& device_name); + // These are the equivalent of the IPC command handlers so IPC overhead is included + // in timing calculations. + s64 Open(FS::Uid uid, FS::Gid gid, const std::string& path, FS::Mode mode, + std::optional ipc_fd = {}, Ticks ticks = {}); + s32 Close(u64 fd, Ticks ticks = {}); + s32 Read(u64 fd, u8* data, u32 size, std::optional ipc_buffer_addr = {}, Ticks ticks = {}); + s32 Write(u64 fd, const u8* data, u32 size, std::optional ipc_buffer_addr = {}, + Ticks ticks = {}); + s32 Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks = {}); + void DoState(PointerWrap& p) override; std::optional Open(const OpenRequest& request) override; @@ -76,14 +88,16 @@ private: IPCReply GetUsage(const Handle& handle, const IOCtlVRequest& request); IPCReply Shutdown(const Handle& handle, const IOCtlRequest& request); - u64 EstimateTicksForReadWrite(const Handle& handle, const ReadWriteRequest& request); - u64 SimulatePopulateFileCache(u32 fd, u32 offset, u32 file_size); + u64 EstimateTicksForReadWrite(const Handle& handle, u64 fd, IPCCommandType command, u32 size); + u64 SimulatePopulateFileCache(u64 fd, u32 offset, u32 file_size); u64 SimulateFlushFileCache(); - bool HasCacheForFile(u32 fd, u32 offset) const; + bool HasCacheForFile(u64 fd, u32 offset) const; - std::map m_fd_map; - u32 m_cache_fd = INVALID_FD; - u16 m_cache_chain_index = 0; bool m_dirty_cache = false; + u16 m_cache_chain_index = 0; + std::optional m_cache_fd; + // The first 0x18 IDs are reserved for the PPC. + u64 m_next_fd = 0x18; + std::map m_fd_map; }; } // namespace IOS::HLE diff --git a/Source/Core/Core/IOS/IOS.h b/Source/Core/Core/IOS/IOS.h index ba823894b9..15bcc1cccf 100644 --- a/Source/Core/Core/IOS/IOS.h +++ b/Source/Core/Core/IOS/IOS.h @@ -45,6 +45,38 @@ struct IPCReply u64 reply_delay_ticks; }; +// Used to make it more convenient for functions to return timing information +// without having to explicitly keep track of ticks in callers. +class Ticks +{ +public: + Ticks(u64* ticks = nullptr) : m_ticks(ticks) {} + + void Add(u64 ticks) + { + if (m_ticks != nullptr) + *m_ticks += ticks; + } + + void AddTimeBaseTicks(u64 tb_ticks) { Add(tb_ticks * SystemTimers::TIMER_RATIO); } + +private: + u64* m_ticks = nullptr; +}; + +template +IPCReply MakeIPCReply(u64 ticks, const ResultProducer& fn) +{ + const s32 result_value = fn(Ticks{&ticks}); + return IPCReply{result_value, ticks}; +} + +template +IPCReply MakeIPCReply(const ResultProducer& fn) +{ + return MakeIPCReply(0, fn); +} + enum IPCCommandType : u32 { IPC_CMD_OPEN = 1, diff --git a/Source/Core/Core/State.cpp b/Source/Core/Core/State.cpp index cca921c421..3d20c0fb05 100644 --- a/Source/Core/Core/State.cpp +++ b/Source/Core/Core/State.cpp @@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent; static std::thread g_save_thread; // Don't forget to increase this after doing changes on the savestate system -constexpr u32 STATE_VERSION = 128; // Last changed in PR 9366 +constexpr u32 STATE_VERSION = 129; // Last changed in PR 9511 // Maps savestate versions to Dolphin versions. // Versions after 42 don't need to be added to this list,