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;
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 <typename... Args>
@ -153,59 +160,78 @@ static IPCReply GetReplyForSuperblockOperation(int ios_version, ResultCode resul
std::optional<IPCReply> FSDevice::Open(const OpenRequest& request)
{
return MakeIPCReply([&](Ticks t) {
return Open(request.uid, request.gid, request.path, static_cast<Mode>(request.flags & 3),
request.fd, t);
});
}
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());
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<Mode>(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<IPCReply> FSDevice::Close(u32 fd)
{
u64 ticks = 0;
if (m_fd_map[fd].fs_fd != INVALID_FD)
return MakeIPCReply([&](Ticks t) { return Close(static_cast<u64>(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<FileStatus> 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<u32>(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<u16>(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<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)
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<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)
return GetFSReply(ConvertResult(result.Error()));
return ConvertResult(result.Error());
return GetFSReply(*result, ticks);
return *result;
}
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)
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<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)
return GetFSReply(ConvertResult(result.Error()));
return ConvertResult(result.Error());
return GetFSReply(*result, ticks);
return *result;
}
std::optional<IPCReply> 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<u32> 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<u32> 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)

View File

@ -6,7 +6,9 @@
#include <array>
#include <map>
#include <optional>
#include <string>
#include <utility>
#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<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;
std::optional<IPCReply> 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<u32, Handle> 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<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

View File

@ -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 <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
{
IPC_CMD_OPEN = 1,

View File

@ -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,