9a9b91c940
It's finally done.. Signed-off-by: kaguya <vpshinomiya@protonmail.com>
419 lines
12 KiB
C++
419 lines
12 KiB
C++
|
||
#include <bits/ensure.h>
|
||
#include <bits/errors.hpp>
|
||
#include <pthread.h>
|
||
#include <sys/time.h>
|
||
#include <time.h>
|
||
|
||
#include <frg/allocation.hpp>
|
||
#include <hel-syscalls.h>
|
||
#include <hel.h>
|
||
#include <mlibc/all-sysdeps.hpp>
|
||
#include <mlibc/allocator.hpp>
|
||
#include <mlibc/debug.hpp>
|
||
#include <mlibc/posix-pipe.hpp>
|
||
#include <protocols/posix/supercalls.hpp>
|
||
|
||
#include "posix.frigg_bragi.hpp"
|
||
|
||
struct TrackerPage {
|
||
uint64_t seqlock;
|
||
int32_t state;
|
||
int32_t padding;
|
||
int64_t refClock;
|
||
int64_t baseRealtime;
|
||
};
|
||
|
||
extern thread_local TrackerPage *__mlibc_clk_tracker_page;
|
||
|
||
namespace mlibc {
|
||
|
||
int sys_clock_get(int clock, time_t *secs, long *nanos) {
|
||
// This implementation is inherently signal-safe.
|
||
if (clock == CLOCK_MONOTONIC || clock == CLOCK_MONOTONIC_RAW
|
||
|| clock == CLOCK_MONOTONIC_COARSE) {
|
||
uint64_t tick;
|
||
HEL_CHECK(helGetClock(&tick));
|
||
*secs = tick / 1000000000;
|
||
*nanos = tick % 1000000000;
|
||
} else if (clock == CLOCK_REALTIME) {
|
||
cacheFileTable();
|
||
|
||
// Start the seqlock read.
|
||
auto seqlock = __atomic_load_n(&__mlibc_clk_tracker_page->seqlock, __ATOMIC_ACQUIRE);
|
||
__ensure(!(seqlock & 1));
|
||
|
||
// Perform the actual loads.
|
||
auto ref = __atomic_load_n(&__mlibc_clk_tracker_page->refClock, __ATOMIC_RELAXED);
|
||
auto base = __atomic_load_n(&__mlibc_clk_tracker_page->baseRealtime, __ATOMIC_RELAXED);
|
||
|
||
// Finish the seqlock read.
|
||
__atomic_thread_fence(__ATOMIC_ACQUIRE);
|
||
__ensure(__atomic_load_n(&__mlibc_clk_tracker_page->seqlock, __ATOMIC_RELAXED) == seqlock);
|
||
|
||
// Calculate the current time.
|
||
uint64_t tick;
|
||
HEL_CHECK(helGetClock(&tick));
|
||
__ensure(
|
||
tick >= (uint64_t)__mlibc_clk_tracker_page->refClock
|
||
); // TODO: Respect the seqlock!
|
||
tick -= ref;
|
||
tick += base;
|
||
*secs = tick / 1000000000;
|
||
*nanos = tick % 1000000000;
|
||
} else if (clock == CLOCK_PROCESS_CPUTIME_ID) {
|
||
mlibc::infoLogger() << "\e[31mmlibc: clock_gettime does not support the CPU time clocks"
|
||
"\e[39m"
|
||
<< frg::endlog;
|
||
*secs = 0;
|
||
*nanos = 0;
|
||
} else if (clock == CLOCK_BOOTTIME) {
|
||
uint64_t tick;
|
||
HEL_CHECK(helGetClock(&tick));
|
||
|
||
*secs = tick / 1000000000;
|
||
*nanos = tick % 1000000000;
|
||
} else {
|
||
mlibc::panicLogger() << "mlibc: Unexpected clock " << clock << frg::endlog;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
int sys_clock_getres(int clock, time_t *secs, long *nanos) {
|
||
(void)clock;
|
||
(void)secs;
|
||
(void)nanos;
|
||
mlibc::infoLogger() << "mlibc: clock_getres is a stub" << frg::endlog;
|
||
return 0;
|
||
}
|
||
|
||
int sys_setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value) {
|
||
SignalGuard sguard;
|
||
|
||
if (which != ITIMER_REAL) {
|
||
mlibc::infoLogger() << "mlibc: setitimers other than ITIMER_REAL are unsupported"
|
||
<< frg::endlog;
|
||
return EINVAL;
|
||
}
|
||
|
||
managarm::posix::SetIntervalTimerRequest<MemoryAllocator> req(getSysdepsAllocator());
|
||
req.set_which(which);
|
||
req.set_value_sec(new_value->it_value.tv_sec);
|
||
req.set_value_usec(new_value->it_value.tv_usec);
|
||
req.set_interval_sec(new_value->it_interval.tv_sec);
|
||
req.set_interval_usec(new_value->it_interval.tv_usec);
|
||
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::SetIntervalTimerResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
__ensure(resp.error() == managarm::posix::Errors::SUCCESS);
|
||
|
||
if (old_value) {
|
||
old_value->it_value.tv_sec = resp.value_sec();
|
||
old_value->it_value.tv_usec = resp.value_usec();
|
||
old_value->it_interval.tv_sec = resp.interval_sec();
|
||
old_value->it_interval.tv_usec = resp.interval_usec();
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
namespace {
|
||
|
||
bool timerThreadInit = false;
|
||
|
||
struct PosixTimerContext {
|
||
int setupSem = 0;
|
||
int workerSem = 0;
|
||
sigevent *sigev;
|
||
};
|
||
|
||
struct TimerHandle {
|
||
uint64_t id;
|
||
int notify_type;
|
||
pthread_t thread = {};
|
||
};
|
||
|
||
void timer_handle(int, siginfo_t *, void *) {}
|
||
|
||
void *timer_setup(void *arg) {
|
||
auto ctx = reinterpret_cast<PosixTimerContext *>(arg);
|
||
|
||
sigset_t set = {};
|
||
sigaddset(&set, SIGTIMER);
|
||
|
||
// wait for parent setup to be complete
|
||
while (__atomic_load_n(&ctx->setupSem, __ATOMIC_RELAXED) == 0)
|
||
;
|
||
pthread_testcancel();
|
||
|
||
// copy out the function and argument, as the lifetime of the context ends with
|
||
// incrementing workerSem
|
||
auto notify = ctx->sigev->sigev_notify_function;
|
||
union sigval val = ctx->sigev->sigev_value;
|
||
|
||
// notify the parent that the context can be dropped
|
||
__atomic_store_n(&ctx->workerSem, 1, __ATOMIC_RELEASE);
|
||
|
||
siginfo_t si;
|
||
int signo;
|
||
|
||
while (true) {
|
||
pthread_testcancel();
|
||
while (sys_sigtimedwait(&set, &si, nullptr, &signo))
|
||
;
|
||
pthread_testcancel();
|
||
if (si.si_code == SI_TIMER && signo == SIGTIMER)
|
||
notify(val);
|
||
}
|
||
|
||
return nullptr;
|
||
}
|
||
|
||
} // namespace
|
||
|
||
int sys_timer_create(clockid_t clk, struct sigevent *__restrict evp, timer_t *__restrict res) {
|
||
SignalGuard sguard;
|
||
|
||
if (!res)
|
||
return EINVAL;
|
||
|
||
managarm::posix::TimerCreateRequest<MemoryAllocator> req(getSysdepsAllocator());
|
||
req.set_clockid(clk);
|
||
|
||
// TODO: pass sigev_value
|
||
if (!evp) {
|
||
req.set_sigev_signo(SIGALRM);
|
||
req.set_sigev_tid(sys_gettid());
|
||
} else if (evp->sigev_notify == SIGEV_NONE) {
|
||
req.set_sigev_signo(0);
|
||
req.set_sigev_tid(0);
|
||
} else if (evp->sigev_notify == SIGEV_SIGNAL) {
|
||
req.set_sigev_signo(evp->sigev_signo);
|
||
req.set_sigev_tid(sys_gettid());
|
||
} else if (evp->sigev_notify == SIGEV_THREAD_ID) {
|
||
req.set_sigev_signo(evp->sigev_signo);
|
||
req.set_sigev_tid(evp->sigev_notify_thread_id);
|
||
} else if (evp->sigev_notify == SIGEV_THREAD) {
|
||
if (!timerThreadInit) {
|
||
struct sigaction sa{};
|
||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||
sa.sa_sigaction = timer_handle;
|
||
sys_sigaction(SIGTIMER, &sa, nullptr);
|
||
timerThreadInit = true;
|
||
}
|
||
|
||
pthread_attr_t attr;
|
||
if (evp->sigev_notify_attributes)
|
||
attr = *evp->sigev_notify_attributes;
|
||
else
|
||
pthread_attr_init(&attr);
|
||
|
||
int ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
|
||
if (ret)
|
||
return ret;
|
||
|
||
PosixTimerContext context{};
|
||
context.sigev = evp;
|
||
|
||
// mask for all signals except the libc-reserved RT signal range
|
||
sigset_t mask = {
|
||
#if ULONG_MAX == 0xFFFF'FFFF
|
||
0x7FFF'FFFF, 0xFFFF'FFFC
|
||
#else
|
||
0xFFFF'FFFC'7FFF'FFFF
|
||
#endif
|
||
};
|
||
// but also mask SIGTIMER
|
||
sigaddset(&mask, SIGTIMER);
|
||
HelWord original_set;
|
||
uint64_t unused;
|
||
|
||
HEL_CHECK(helSyscall2_2(
|
||
kHelObserveSuperCall + posix::superSigMask,
|
||
SIG_BLOCK,
|
||
*reinterpret_cast<const HelWord *>(&mask),
|
||
&original_set,
|
||
&unused
|
||
));
|
||
|
||
pthread_t pthread;
|
||
ret = pthread_create(&pthread, &attr, timer_setup, &context);
|
||
|
||
// restore previous signal mask
|
||
HEL_CHECK(helSyscall2_2(
|
||
kHelObserveSuperCall + posix::superSigMask, SIG_SETMASK, original_set, &unused, &unused
|
||
));
|
||
|
||
if (ret)
|
||
return ret;
|
||
|
||
req.set_sigev_signo(SIGTIMER);
|
||
req.set_sigev_tid(reinterpret_cast<Tcb *>(pthread)->tid);
|
||
infoLogger() << "mlibc: timer_create: created timer thread "
|
||
<< reinterpret_cast<Tcb *>(pthread)->tid << frg::endlog;
|
||
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::TimerCreateResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
if (resp.error() != managarm::posix::Errors::SUCCESS) {
|
||
pthread_cancel(pthread);
|
||
__atomic_store_n(&context.setupSem, 1, __ATOMIC_RELEASE);
|
||
return resp.error() | toErrno;
|
||
}
|
||
|
||
// notify worker that setup is complete
|
||
__atomic_store_n(&context.setupSem, 1, __ATOMIC_RELEASE);
|
||
// await worker setup to let the context go out of scope
|
||
while (__atomic_load_n(&context.workerSem, __ATOMIC_RELAXED) == 0)
|
||
;
|
||
|
||
*res = frg::construct<TimerHandle>(
|
||
getSysdepsAllocator(), resp.timer_id(), evp->sigev_notify, pthread
|
||
);
|
||
return 0;
|
||
} else {
|
||
mlibc::infoLogger() << "mlibc: timer_create: unsupported sigevent type "
|
||
<< evp->sigev_notify << frg::endlog;
|
||
return EINVAL;
|
||
}
|
||
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::TimerCreateResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
if (resp.error() != managarm::posix::Errors::SUCCESS)
|
||
return resp.error() | toErrno;
|
||
|
||
*res = frg::construct<TimerHandle>(
|
||
getSysdepsAllocator(), resp.timer_id(), evp ? evp->sigev_notify : SIGEV_SIGNAL, pthread_t{}
|
||
);
|
||
return 0;
|
||
}
|
||
|
||
int sys_timer_settime(
|
||
timer_t t, int flags, const struct itimerspec *__restrict val, struct itimerspec *__restrict old
|
||
) {
|
||
SignalGuard sguard;
|
||
|
||
auto timerHandle = reinterpret_cast<TimerHandle *>(t);
|
||
managarm::posix::TimerSetRequest<MemoryAllocator> req(getSysdepsAllocator());
|
||
req.set_timer(timerHandle->id);
|
||
req.set_flags(flags);
|
||
req.set_value_sec(val->it_value.tv_sec);
|
||
req.set_value_nsec(val->it_value.tv_nsec);
|
||
req.set_interval_sec(val->it_interval.tv_sec);
|
||
req.set_interval_nsec(val->it_interval.tv_nsec);
|
||
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::TimerSetResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
if (resp.error() != managarm::posix::Errors::SUCCESS)
|
||
return resp.error() | toErrno;
|
||
|
||
if (old) {
|
||
old->it_value.tv_sec = resp.value_sec();
|
||
old->it_value.tv_nsec = resp.value_nsec();
|
||
old->it_interval.tv_sec = resp.interval_sec();
|
||
old->it_interval.tv_nsec = resp.interval_nsec();
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int sys_timer_gettime(timer_t t, struct itimerspec *val) {
|
||
SignalGuard sguard;
|
||
|
||
auto timerHandle = reinterpret_cast<TimerHandle *>(t);
|
||
managarm::posix::TimerGetRequest<MemoryAllocator> req(getSysdepsAllocator());
|
||
req.set_timer((timerHandle->id));
|
||
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::TimerGetResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
if (resp.error() != managarm::posix::Errors::SUCCESS)
|
||
return resp.error() | toErrno;
|
||
|
||
if (val) {
|
||
val->it_value.tv_sec = resp.value_sec();
|
||
val->it_value.tv_nsec = resp.value_nsec();
|
||
val->it_interval.tv_sec = resp.interval_sec();
|
||
val->it_interval.tv_nsec = resp.interval_nsec();
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int sys_timer_delete(timer_t t) {
|
||
SignalGuard sguard;
|
||
|
||
auto timerHandle = reinterpret_cast<TimerHandle *>(t);
|
||
|
||
if (timerHandle->notify_type == SIGEV_THREAD) {
|
||
pthread_cancel(timerHandle->thread);
|
||
pthread_kill(timerHandle->thread, SIGTIMER);
|
||
}
|
||
|
||
managarm::posix::TimerDeleteRequest<MemoryAllocator> req(getSysdepsAllocator());
|
||
req.set_timer(timerHandle->id);
|
||
auto [offer, send_req, recv_resp] = exchangeMsgsSync(
|
||
getPosixLane(),
|
||
helix_ng::offer(
|
||
helix_ng::sendBragiHeadOnly(req, getSysdepsAllocator()), helix_ng::recvInline()
|
||
)
|
||
);
|
||
HEL_CHECK(offer.error());
|
||
HEL_CHECK(send_req.error());
|
||
HEL_CHECK(recv_resp.error());
|
||
|
||
managarm::posix::TimerDeleteResponse<MemoryAllocator> resp(getSysdepsAllocator());
|
||
resp.ParseFromArray(recv_resp.data(), recv_resp.length());
|
||
return resp.error() | toErrno;
|
||
}
|
||
|
||
} // namespace mlibc
|