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.
This commit is contained in:
Léo Lam 2021-02-14 00:08:07 +01:00
parent 1073463d35
commit f214df5d2c
No known key found for this signature in database
GPG Key ID: 0DF30F9081000741
4 changed files with 172 additions and 70 deletions

View File

@ -22,10 +22,16 @@ namespace IOS::HLE
{ {
using namespace IOS::HLE::FS; 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. // 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). /// 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) 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_dirty_cache);
p.Do(m_cache_chain_index);
p.Do(m_cache_fd);
p.Do(m_next_fd);
p.Do(m_fd_map);
} }
template <typename... Args> template <typename... Args>
@ -153,59 +160,78 @@ static IPCReply GetReplyForSuperblockOperation(int ios_version, ResultCode resul
std::optional<IPCReply> FSDevice::Open(const OpenRequest& request) std::optional<IPCReply> FSDevice::Open(const OpenRequest& request)
{ {
if (m_fd_map.size() >= 16) return MakeIPCReply([&](Ticks t) {
return GetFSReply(ConvertResult(ResultCode::NoFreeHandle)); return Open(request.uid, request.gid, request.path, static_cast<Mode>(request.flags & 3),
request.fd, t);
if (request.path.size() >= 64) });
return GetFSReply(ConvertResult(ResultCode::Invalid));
if (request.path == "/dev/fs")
{
m_fd_map[request.fd] = {request.gid, request.uid, INVALID_FD};
return GetFSReply(IPC_SUCCESS);
} }
const u64 ticks = EstimateFileLookupTicks(request.path, FileLookupMode::Normal); s64 FSDevice::Open(FS::Uid uid, FS::Gid gid, const std::string& path, FS::Mode mode,
std::optional<u32> ipc_fd, Ticks ticks)
{
ticks.AddTimeBaseTicks(GetIPCOverheadTicks());
auto backend_fd = m_ios.GetFS()->OpenFile(request.uid, request.gid, request.path, if (m_fd_map.size() >= 16)
static_cast<Mode>(request.flags & 3)); return ConvertResult(ResultCode::NoFreeHandle);
LogResult(backend_fd, "OpenFile({})", request.path);
if (path.size() >= 64)
return ConvertResult(ResultCode::Invalid);
const u64 fd = ipc_fd.has_value() ? u64(*ipc_fd) : m_next_fd++;
if (path == "/dev/fs")
{
m_fd_map[fd] = {gid, uid, INVALID_FD};
return fd;
}
ticks.AddTimeBaseTicks(EstimateFileLookupTicks(path, FileLookupMode::Normal));
auto backend_fd = m_ios.GetFS()->OpenFile(uid, gid, path, mode);
LogResult(backend_fd, "OpenFile({})", path);
if (!backend_fd) 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()}; auto& handle = m_fd_map[fd] = {gid, uid, backend_fd->Release()};
std::strncpy(m_fd_map[request.fd].name.data(), request.path.c_str(), 64); std::strncpy(handle.name.data(), path.c_str(), handle.name.size());
return GetFSReply(IPC_SUCCESS, ticks); return fd;
} }
std::optional<IPCReply> FSDevice::Close(u32 fd) std::optional<IPCReply> FSDevice::Close(u32 fd)
{ {
u64 ticks = 0; return MakeIPCReply([&](Ticks t) { return Close(static_cast<u64>(fd), t); });
if (m_fd_map[fd].fs_fd != INVALID_FD) }
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) if (fd == m_cache_fd)
{ {
ticks += SimulateFlushFileCache(); ticks.AddTimeBaseTicks(SimulateFlushFileCache());
m_cache_fd = INVALID_FD; m_cache_fd.reset();
} }
if (m_fd_map[fd].superblock_flush_needed) if (handle.superblock_flush_needed)
ticks += GetSuperblockWriteTbTicks(m_ios.GetVersion()); ticks.AddTimeBaseTicks(GetSuperblockWriteTbTicks(m_ios.GetVersion()));
const ResultCode result = m_ios.GetFS()->Close(m_fd_map[fd].fs_fd); const ResultCode result = m_ios.GetFS()->Close(handle.fs_fd);
LogResult(result, "Close({})", m_fd_map[fd].name.data()); LogResult(result, "Close({})", handle.name.data());
m_fd_map.erase(fd); m_fd_map.erase(fd);
if (result != ResultCode::Success) if (result != ResultCode::Success)
return GetFSReply(ConvertResult(result)); return ConvertResult(result);
} }
else else
{ {
m_fd_map.erase(fd); 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)) if (HasCacheForFile(fd, offset))
return 0; return 0;
@ -221,22 +247,23 @@ u64 FSDevice::SimulatePopulateFileCache(u32 fd, u32 offset, u32 file_size)
u64 FSDevice::SimulateFlushFileCache() u64 FSDevice::SimulateFlushFileCache()
{ {
if (m_cache_fd == INVALID_FD || !m_dirty_cache) if (!m_cache_fd.has_value() || !m_dirty_cache)
return 0; return 0;
m_dirty_cache = false; 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()); return GetClusterWriteTbTicks(m_ios.GetVersion());
} }
// Simulate parts of the FS read/write logic to estimate ticks for file operations correctly. // 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; u64 ticks = 0;
const bool is_write = request.command == IPC_CMD_WRITE; const bool is_write = command == IPC_CMD_WRITE;
const Result<FileStatus> status = m_ios.GetFS()->GetFileStatus(handle.fs_fd); const Result<FileStatus> status = m_ios.GetFS()->GetFileStatus(handle.fs_fd);
u32 offset = status->offset; u32 offset = status->offset;
u32 count = request.size; u32 count = size;
if (!is_write && count + offset > status->size) if (!is_write && count + offset > status->size)
count = status->size - offset; count = status->size - offset;
@ -244,17 +271,17 @@ u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, const ReadWriteReq
{ {
u32 copy_length; u32 copy_length;
// Fast path (if not cached): FS copies an entire cluster directly from/to the request. // 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) offset % CLUSTER_DATA_SIZE == 0)
{ {
ticks += (is_write ? GetClusterWriteTbTicks : GetClusterReadTbTicks)(m_ios.GetVersion()); ticks += (is_write ? GetClusterWriteTbTicks : GetClusterReadTbTicks)(m_ios.GetVersion());
copy_length = CLUSTER_DATA_SIZE; copy_length = CLUSTER_DATA_SIZE;
if (is_write) if (is_write)
m_fd_map[request.fd].superblock_flush_needed = true; m_fd_map[fd].superblock_flush_needed = true;
} }
else 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; const u32 start = offset - m_cache_chain_index * CLUSTER_DATA_SIZE;
copy_length = std::min<u32>(CLUSTER_DATA_SIZE - start, count); copy_length = std::min<u32>(CLUSTER_DATA_SIZE - start, count);
@ -276,7 +303,7 @@ u64 FSDevice::EstimateTicksForReadWrite(const Handle& handle, const ReadWriteReq
return ticks; return ticks;
} }
bool FSDevice::HasCacheForFile(u32 fd, u32 offset) const bool FSDevice::HasCacheForFile(u64 fd, u32 offset) const
{ {
const u16 chain_index = static_cast<u16>(offset / CLUSTER_DATA_SIZE); const u16 chain_index = static_cast<u16>(offset / CLUSTER_DATA_SIZE);
return m_cache_fd == fd && m_cache_chain_index == chain_index; 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<IPCReply> FSDevice::Read(const ReadWriteRequest& request) std::optional<IPCReply> 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<u32> ipc_buffer_addr, Ticks ticks)
{
ticks.AddTimeBaseTicks(GetIPCOverheadTicks());
const Handle& handle = m_fd_map[fd];
if (handle.fs_fd == INVALID_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. // 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<u32> 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<u32> 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) if (!result)
return GetFSReply(ConvertResult(result.Error())); return ConvertResult(result.Error());
return GetFSReply(*result, ticks); return *result;
} }
std::optional<IPCReply> FSDevice::Write(const ReadWriteRequest& request) std::optional<IPCReply> 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<u32> ipc_buffer_addr,
Ticks ticks)
{
ticks.AddTimeBaseTicks(GetIPCOverheadTicks());
const Handle& handle = m_fd_map[fd];
if (handle.fs_fd == INVALID_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. // 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<u32> 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<u32> 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) if (!result)
return GetFSReply(ConvertResult(result.Error())); return ConvertResult(result.Error());
return GetFSReply(*result, ticks); return *result;
} }
std::optional<IPCReply> FSDevice::Seek(const SeekRequest& request) std::optional<IPCReply> FSDevice::Seek(const SeekRequest& request)
{ {
const Handle& handle = m_fd_map[request.fd]; return MakeIPCReply([&](Ticks t) {
if (handle.fs_fd == INVALID_FD) return Seek(request.fd, request.offset, HLE::FS::SeekMode(request.mode), t);
return GetFSReply(ConvertResult(ResultCode::Invalid)); });
}
const Result<u32> result = s32 FSDevice::Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks)
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); ticks.AddTimeBaseTicks(GetIPCOverheadTicks());
const Handle& handle = m_fd_map[fd];
if (handle.fs_fd == INVALID_FD)
return ConvertResult(ResultCode::Invalid);
const Result<u32> result = m_ios.GetFS()->SeekFile(handle.fs_fd, offset, mode);
LogResult(result, "Seek({}, 0x{:08x}, {})", handle.name.data(), offset, mode);
if (!result) if (!result)
return GetFSReply(ConvertResult(result.Error())); return ConvertResult(result.Error());
return GetFSReply(*result); return *result;
} }
#pragma pack(push, 1) #pragma pack(push, 1)

View File

@ -6,7 +6,9 @@
#include <array> #include <array>
#include <map> #include <map>
#include <optional>
#include <string> #include <string>
#include <utility>
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Core/IOS/Device.h" #include "Core/IOS/Device.h"
@ -24,6 +26,16 @@ class FSDevice : public Device
public: public:
FSDevice(Kernel& ios, const std::string& device_name); 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<u32> ipc_fd = {}, Ticks ticks = {});
s32 Close(u64 fd, Ticks ticks = {});
s32 Read(u64 fd, u8* data, u32 size, std::optional<u32> ipc_buffer_addr = {}, Ticks ticks = {});
s32 Write(u64 fd, const u8* data, u32 size, std::optional<u32> ipc_buffer_addr = {},
Ticks ticks = {});
s32 Seek(u64 fd, u32 offset, FS::SeekMode mode, Ticks ticks = {});
void DoState(PointerWrap& p) override; void DoState(PointerWrap& p) override;
std::optional<IPCReply> Open(const OpenRequest& request) override; std::optional<IPCReply> Open(const OpenRequest& request) override;
@ -76,14 +88,16 @@ private:
IPCReply GetUsage(const Handle& handle, const IOCtlVRequest& request); IPCReply GetUsage(const Handle& handle, const IOCtlVRequest& request);
IPCReply Shutdown(const Handle& handle, const IOCtlRequest& request); IPCReply Shutdown(const Handle& handle, const IOCtlRequest& request);
u64 EstimateTicksForReadWrite(const Handle& handle, const ReadWriteRequest& request); u64 EstimateTicksForReadWrite(const Handle& handle, u64 fd, IPCCommandType command, u32 size);
u64 SimulatePopulateFileCache(u32 fd, u32 offset, u32 file_size); u64 SimulatePopulateFileCache(u64 fd, u32 offset, u32 file_size);
u64 SimulateFlushFileCache(); u64 SimulateFlushFileCache();
bool HasCacheForFile(u32 fd, u32 offset) const; bool HasCacheForFile(u64 fd, u32 offset) const;
std::map<u32, Handle> m_fd_map;
u32 m_cache_fd = INVALID_FD;
u16 m_cache_chain_index = 0;
bool m_dirty_cache = false; bool m_dirty_cache = false;
u16 m_cache_chain_index = 0;
std::optional<u64> m_cache_fd;
// The first 0x18 IDs are reserved for the PPC.
u64 m_next_fd = 0x18;
std::map<u64, Handle> m_fd_map;
}; };
} // namespace IOS::HLE } // namespace IOS::HLE

View File

@ -45,6 +45,38 @@ struct IPCReply
u64 reply_delay_ticks; 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 <typename ResultProducer>
IPCReply MakeIPCReply(u64 ticks, const ResultProducer& fn)
{
const s32 result_value = fn(Ticks{&ticks});
return IPCReply{result_value, ticks};
}
template <typename ResultProducer>
IPCReply MakeIPCReply(const ResultProducer& fn)
{
return MakeIPCReply(0, fn);
}
enum IPCCommandType : u32 enum IPCCommandType : u32
{ {
IPC_CMD_OPEN = 1, IPC_CMD_OPEN = 1,

View File

@ -74,7 +74,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread; static std::thread g_save_thread;
// Don't forget to increase this after doing changes on the savestate system // 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. // Maps savestate versions to Dolphin versions.
// Versions after 42 don't need to be added to this list, // Versions after 42 don't need to be added to this list,