diff --git a/core/libs/prx/libkernel/CMakeLists.txt b/core/libs/prx/libkernel/CMakeLists.txt index 41cb24584..bf0ec661b 100644 --- a/core/libs/prx/libkernel/CMakeLists.txt +++ b/core/libs/prx/libkernel/CMakeLists.txt @@ -394,6 +394,16 @@ if(BUILD_TESTING) set_tests_properties(guest_cond_unowned_mutex PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$") endif() + add_test_executable(guest_getitimer_tests ${LIBS_INCLUDE_DIR}/tests/GuestGetitimer.cpp) + target_include_directories(guest_getitimer_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_getitimer_tests PRIVATE libkernel libc) + configure_windows_unwind(guest_getitimer_tests) + add_test(NAME guest_getitimer COMMAND guest_getitimer_tests) + set_tests_properties(guest_getitimer PROPERTIES TIMEOUT 20) + if(WIN32) + set_tests_properties(guest_getitimer PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_cpu_time_clocks_tests ${LIBS_INCLUDE_DIR}/tests/GuestCpuTimeClocks.cpp) target_include_directories(guest_cpu_time_clocks_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_cpu_time_clocks_tests PRIVATE libkernel libc) diff --git a/core/libs/prx/libkernel/System/src/Signals.cpp b/core/libs/prx/libkernel/System/src/Signals.cpp index 62d698299..f4a57b7ff 100644 --- a/core/libs/prx/libkernel/System/src/Signals.cpp +++ b/core/libs/prx/libkernel/System/src/Signals.cpp @@ -150,6 +150,37 @@ extern "C" unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int seconds) return remaining; } +struct GuestItimerval { + KernelTimeval interval; + KernelTimeval value; +}; +static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); + +extern "C" int APS5_VABI getitimer_nid_postfix(int which, GuestItimerval* output) { + if (which < 0 || which > 2) { + *__error_nid_postfix() = 22; + return -1; + } + if (which != 0) throw std::runtime_error("getitimer: CPU interval timers are not implemented"); + if (output == nullptr) { + *__error_nid_postfix() = 14; + return -1; + } + GuestItimerval result{}; + auto& alarms = Alarms(); + { + std::lock_guard lock(alarms.lock); + const auto now = std::chrono::steady_clock::now(); + if (alarms.deadline && *alarms.deadline > now) { + const auto micros = std::chrono::duration_cast(*alarms.deadline - now).count(); + result.value.tv_sec = micros / 1000000; + result.value.tv_usec = micros % 1000000; + } + } + *output = result; + return 0; +} + struct GuestStack { void* sp; std::size_t size; diff --git a/core/libs/tests/GuestGetitimer.cpp b/core/libs/tests/GuestGetitimer.cpp new file mode 100644 index 000000000..57e484e58 --- /dev/null +++ b/core/libs/tests/GuestGetitimer.cpp @@ -0,0 +1,99 @@ +#include "SceTypes.hpp" +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct GuestItimerval { + KernelTimeval interval; + KernelTimeval value; +}; + +static_assert(sizeof(GuestItimerval) == 32 && offsetof(GuestItimerval, value) == 16); + +extern "C" { +int APS5_VABI getitimer_nid_postfix(int, GuestItimerval*); +unsigned int APS5_VABI GuestAlarm_nid_no_patch(unsigned int); +int* APS5_VABI __error_nid_postfix(); +using Handler = void (APS5_VABI *)(int); +Handler APS5_VABI signal_nid_postfix(int, Handler); +} + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +static std::int64_t ReadRemaining() { + struct { + std::uint64_t before; + GuestItimerval timer; + std::uint64_t after; + } guarded; + std::memset(&guarded, 0xA5, sizeof(guarded)); + *__error_nid_postfix() = 12345; + Require(getitimer_nid_postfix(0, &guarded.timer) == 0, "getitimer failed"); + Require(*__error_nid_postfix() == 12345, "getitimer changed errno on success"); + Require(guarded.before == 0xA5A5A5A5A5A5A5A5ull && guarded.after == guarded.before, "getitimer wrote outside itimerval"); + Require(guarded.timer.interval.tv_sec == 0 && guarded.timer.interval.tv_usec == 0, "alarm acquired a repeat interval"); + Require(guarded.timer.value.tv_sec >= 0 && guarded.timer.value.tv_usec >= 0 + && guarded.timer.value.tv_usec < 1000000, "invalid timeval normalization"); + return guarded.timer.value.tv_sec * 1000000 + guarded.timer.value.tv_usec; +} + +int main() { + GuestAlarm_nid_no_patch(0); + Require(ReadRemaining() == 0, "inactive alarm is not zero"); + + for (int invalid : {-1, 3, std::numeric_limits::min(), std::numeric_limits::max()}) { + GuestItimerval output{{11, 22}, {33, 44}}; + const auto saved = output; + Require(getitimer_nid_postfix(invalid, &output) == -1 && *__error_nid_postfix() == 22, "invalid timer did not report EINVAL"); + Require(std::memcmp(&output, &saved, sizeof(output)) == 0, "invalid timer changed output"); + Require(getitimer_nid_postfix(invalid, nullptr) == -1 && *__error_nid_postfix() == 22, "invalid timer validation order changed"); + } + Require(getitimer_nid_postfix(0, nullptr) == -1 && *__error_nid_postfix() == 14, "null output did not report EFAULT"); + for (int unsupported : {1, 2}) { + GuestItimerval output{{11, 22}, {33, 44}}; + const auto saved = output; + bool threw = false; + try { + getitimer_nid_postfix(unsupported, &output); + } catch (const std::runtime_error&) { + threw = true; + } + Require(threw && std::memcmp(&output, &saved, sizeof(output)) == 0, "unsupported CPU timer reported fabricated state"); + } + + const auto ignore = reinterpret_cast(std::uintptr_t{1}); + Require(signal_nid_postfix(14, ignore) == nullptr, "SIGALRM was not initially defaulted"); + Require(GuestAlarm_nid_no_patch(30) == 0, "alarm was already active"); + const auto first = ReadRemaining(); + Require(first > 0 && first <= 30000000, "armed alarm is not visible"); + std::this_thread::sleep_for(std::chrono::milliseconds(25)); + Require(ReadRemaining() < first, "getitimer does not count down"); + std::array readers; + for (auto& reader : readers) reader = std::thread([] { + for (int i = 0; i < 100; ++i) Require(ReadRemaining() > 0, "reading consumed the alarm"); + }); + for (auto& reader : readers) reader.join(); + Require(GuestAlarm_nid_no_patch(0) > 0, "getitimer cancelled the alarm"); + Require(ReadRemaining() == 0, "cancelled alarm is not zero"); + + GuestAlarm_nid_no_patch(std::numeric_limits::max()); + Require(ReadRemaining() > static_cast(std::numeric_limits::max()) * 1000000, + "long alarm was truncated to signed 32 bits"); + GuestAlarm_nid_no_patch(1); + std::this_thread::sleep_for(std::chrono::milliseconds(1100)); + Require(ReadRemaining() == 0, "expired alarm is not zero"); + GuestAlarm_nid_no_patch(0); +}