diff --git a/core/libs/SceTypes.hpp b/core/libs/SceTypes.hpp index f7405fa94..51d48be50 100644 --- a/core/libs/SceTypes.hpp +++ b/core/libs/SceTypes.hpp @@ -95,6 +95,14 @@ struct KernelAioRwRequest { std::int32_t fd; }; +struct KernelAioSchedulingParam { + std::int32_t schedulingWindowSize; + std::int32_t delayedCountLimit; + std::uint32_t enableSplit; + std::uint32_t splitSize; + std::uint32_t splitChunkSize; +}; + struct KernelBatchMapEntry { void* start; std::uint64_t offset; diff --git a/core/libs/prx/libkernel/Aio/src/Aio.cpp b/core/libs/prx/libkernel/Aio/src/Aio.cpp index 040773e22..1d83f2eed 100644 --- a/core/libs/prx/libkernel/Aio/src/Aio.cpp +++ b/core/libs/prx/libkernel/Aio/src/Aio.cpp @@ -28,6 +28,10 @@ constexpr std::int32_t AioRequestNumMax = 128; constexpr std::int32_t AioIdNumMax = 128; constexpr std::uint32_t AioWaitAnd = 1; constexpr std::uint32_t AioWaitOr = 2; +constexpr std::int32_t AioSchedulingWindowMax = 128; +constexpr std::int32_t AioDelayedCountMax = 128; +constexpr std::uint32_t AioSplitSizeMax = 0x1000000; +constexpr std::uint32_t AioSplitChunkSizeMax = 0x1000000; std::mutex g_mutex; std::array g_states{}; @@ -204,6 +208,20 @@ void APS5_VABI sceKernelAioInitializeParam(void* param) { if (param == nullptr) throw std::invalid_argument("sceKernelAioInitializeParam: param is null"); } +int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, int32_t schedulingWindowSize, int32_t delayedCountLimit, uint32_t enableSplit, uint32_t splitSize, uint32_t splitChunkSize) { + if (param == nullptr) throw std::invalid_argument("sceKernelAioSetParam: param is null"); + if (schedulingWindowSize < 0 || schedulingWindowSize > AioSchedulingWindowMax) throw std::runtime_error("sceKernelAioSetParam: a scheduling window outside 0..128 is not modelled"); + if (delayedCountLimit < 0 || delayedCountLimit > AioDelayedCountMax) throw std::runtime_error("sceKernelAioSetParam: a delayed count limit outside 0..128 is not modelled"); + if (enableSplit > 1) throw std::runtime_error("sceKernelAioSetParam: a split flag other than 0 or 1 is not modelled"); + if (splitSize > AioSplitSizeMax || splitChunkSize > AioSplitChunkSizeMax) throw std::runtime_error("sceKernelAioSetParam: a split size above 0x1000000 is not modelled"); + param->schedulingWindowSize = schedulingWindowSize; + param->delayedCountLimit = delayedCountLimit; + param->enableSplit = enableSplit; + param->splitSize = splitSize; + param->splitChunkSize = splitChunkSize; + return 0; +} + int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, int32_t size, int32_t prio, int32_t* id) { (void)prio; return SubmitCommands(req, size, false, id); diff --git a/core/libs/tests/GuestAio.cpp b/core/libs/tests/GuestAio.cpp index 0bbc3a952..e1a51c74e 100644 --- a/core/libs/tests/GuestAio.cpp +++ b/core/libs/tests/GuestAio.cpp @@ -22,6 +22,7 @@ std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence); int APS5_VABI sceKernelAioDeleteRequest(std::int32_t id, std::int32_t* ret); int APS5_VABI sceKernelAioInitializeImpl(void* param, std::int32_t size); void APS5_VABI sceKernelAioInitializeParam(void* param); +int APS5_VABI sceKernelAioSetParam(KernelAioSchedulingParam* param, std::int32_t schedulingWindowSize, std::int32_t delayedCountLimit, std::uint32_t enableSplit, std::uint32_t splitSize, std::uint32_t splitChunkSize); int APS5_VABI sceKernelAioSubmitReadCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); int APS5_VABI sceKernelAioSubmitWriteCommands(KernelAioRwRequest* req, std::int32_t size, std::int32_t prio, std::int32_t* id); int APS5_VABI sceKernelAioPollRequest(std::int32_t id, std::int32_t* state); @@ -48,7 +49,35 @@ int main() { const auto file = root / "data.bin"; const std::string seed = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; { std::ofstream stream(file, std::ios::binary); stream << seed; } - Check(sceKernelAioInitializeImpl(nullptr, 0) == 0); + static_assert(sizeof(KernelAioSchedulingParam) == 20); + std::array schedulingParams{}; + schedulingParams.fill({7, 7, 7, 7, 7}); + Check(sceKernelAioSetParam(&schedulingParams[1], 64, 16, 1, 0x200000, 0x40000) == 0); + Check(schedulingParams[1].schedulingWindowSize == 64 && schedulingParams[1].delayedCountLimit == 16); + Check(schedulingParams[1].enableSplit == 1 && schedulingParams[1].splitSize == 0x200000 && schedulingParams[1].splitChunkSize == 0x40000); + Check(schedulingParams[0].schedulingWindowSize == 7 && schedulingParams[0].splitChunkSize == 7); + Check(schedulingParams[2].schedulingWindowSize == 7 && schedulingParams[2].splitChunkSize == 7); + Check(sceKernelAioSetParam(&schedulingParams[2], 128, 128, 0, 0x1000000, 0x1000000) == 0); + Check(schedulingParams[2].schedulingWindowSize == 128 && schedulingParams[2].delayedCountLimit == 128); + Check(schedulingParams[2].enableSplit == 0 && schedulingParams[2].splitSize == 0x1000000 && schedulingParams[2].splitChunkSize == 0x1000000); + const auto setParamThrows = [&](std::int32_t window, std::int32_t delayed, std::uint32_t split, std::uint32_t size, std::uint32_t chunk) { + KernelAioSchedulingParam untouchedParam{7, 7, 7, 7, 7}; + bool thrown = false; + try { sceKernelAioSetParam(&untouchedParam, window, delayed, split, size, chunk); } catch (const std::exception&) { thrown = true; } + return thrown && untouchedParam.schedulingWindowSize == 7 && untouchedParam.delayedCountLimit == 7 && + untouchedParam.enableSplit == 7 && untouchedParam.splitSize == 7 && untouchedParam.splitChunkSize == 7; + }; + Check(setParamThrows(129, 32, 1, 0x100000, 0x100000)); + Check(setParamThrows(-1, 32, 1, 0x100000, 0x100000)); + Check(setParamThrows(32, 129, 1, 0x100000, 0x100000)); + Check(setParamThrows(32, -1, 1, 0x100000, 0x100000)); + Check(setParamThrows(32, 32, 2, 0x100000, 0x100000)); + Check(setParamThrows(32, 32, 1, 0x1000001, 0x100000)); + Check(setParamThrows(32, 32, 1, 0x100000, 0x1000001)); + bool nullThrew = false; + try { sceKernelAioSetParam(nullptr, 32, 32, 1, 0x100000, 0x100000); } catch (const std::invalid_argument&) { nullThrew = true; } + Check(nullThrew); + Check(sceKernelAioInitializeImpl(schedulingParams.data(), static_cast(sizeof(schedulingParams))) == 0); int initParam = 0; sceKernelAioInitializeParam(&initParam); const int fd = sceKernelOpen(file.string().c_str(), SCE_KERNEL_O_RDONLY, 0); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 61d212ad0..7f334fe8b 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -408,6 +408,7 @@ Throughout the project, every function at every stage either **does exactly what - [Runtime-loaded modules](../../core/relinker/relinker/src/guest/GuestModuleBuilder.cpp) (`--module-dir`) that another module needs are initialized at startup with no start arguments, before the module that needs them is loaded; the console is assumed to start them when that module is loaded. - [sceKernelPread, sceKernelPwrite, sceKernelPreadv, sceKernelPwritev](../../core/libs/prx/libkernel/File/src/Stdio.cpp) (libkernel) on Windows transfer at the given offset with an overlapped `ReadFile`/`WriteFile` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console. Handles that are not disk files return `ESPIPE`. `readv` on a pipe or console handle stops after the first iovec read, a short read the console is assumed to allow; each iovec is transferred separately, so a failure after the first transfer returns the bytes already done - [sceKernelAioSubmitReadCommands, sceKernelAioSubmitWriteCommands and their Multiple variants](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) on Windows transfer through the same positioned path as `sceKernelPread`/`sceKernelPwrite` and restore the descriptor's file pointer afterwards, so a concurrent `read`/`write` on the same descriptor can see the moved pointer in between, unlike on the console +- [sceKernelAioSetParam](../../core/libs/prx/libkernel/Aio/src/Aio.cpp) (libkernel) writes the five values into the 20-byte scheduling struct the first argument points to, following the declarations in [Orbital](https://github.com/AlexAltea/orbital/blob/cc408367da6a6ebbf5ac1ac559fefeadc73b2831/tools/ida/db_types.json), [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/5dfdb4b0b166e83dc9ae275001afb695f12c7174/src/SharpProspero/Interop/Kernel/KernelAio.cs) and [ampr_emu](https://github.com/drakmor/ampr_emu/blob/cfa85df379f6eeeb165d7badf9b648e266fe77b7/ps5/include/sce/_fs.h). Whether the console checks the values is unknown, so a null struct, a scheduling window or delayed count limit outside 0..128, a split flag other than 0 or 1 and a split size or chunk size above 0x1000000 (SharpProspero's maximums) throw. The scheduling settings are not used: `sceKernelAioInitializeImpl` ignores the struct and requests run synchronously on submit - [sceKernelGetFsSandboxRandomWord](../../core/libs/prx/libkernel/System/src/Sandbox.cpp) (libkernel) returns a fixed directory name, `gkbHtXc3Wq`. On the console the name is the process's randomized path (syscall 602, `randomized_path`), and the sandbox exposes system files under it, such as `common/lib` and `common/cert`. Nothing is mounted under the name here, so `/gkbHtXc3Wq/common/...` resolves under the guest root like any other path and does not exist - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) flushes only the host files and directories recorded through `RecordWrittenPath_nid_no_patch` (the libc, libkernel, save data and app content file functions) and their changed parent directories. A host write in a library that does not record its path is not flushed by `sceKernelSync` - [sceKernelSync](../../core/libs/prx/libc/src/General.cpp) (libkernel) on Windows skips a recorded file it cannot open for writing (read-only, or held open by a handle that denies write sharing), since `FlushFileBuffers` needs a writable handle. On Linux, it skips a recorded file it cannot open for reading. Such a file's data may not reach the disk before a host crash; the guest reads the same data either way