feat(libkernel): query real-time alarms with getitimer
This commit is contained in:
parent
a11c8f7f70
commit
1fc9b4d93f
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue