feat(libkernel): query real-time alarms with getitimer

This commit is contained in:
lorkydey 2026-10-11 15:27:59 +02:00
parent a11c8f7f70
commit 1fc9b4d93f
3 changed files with 140 additions and 0 deletions

View File

@ -394,6 +394,16 @@ if(BUILD_TESTING)
set_tests_properties(guest_cond_unowned_mutex PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
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:$<TARGET_FILE_DIR:libkernel>")
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)

View File

@ -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<std::chrono::microseconds>(*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;

View File

@ -0,0 +1,99 @@
#include "SceTypes.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
#include <array>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <limits>
#include <stdexcept>
#include <thread>
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<int>::min(), std::numeric_limits<int>::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<Handler>(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<std::thread, 4> 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<unsigned int>::max());
Require(ReadRemaining() > static_cast<std::int64_t>(std::numeric_limits<int>::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);
}