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:
parent
1073463d35
commit
f214df5d2c
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue