#include <sys/timer.h>
#include <sys/systm.h>
#include <sys/sysmacros.h>
#include <sys/param.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/policy.h>
#include <sys/port_impl.h>
#include <sys/port_kernel.h>
#include <sys/contract/process_impl.h>
static kmem_cache_t *clock_timer_cache;
static clock_backend_t *clock_backend[CLOCK_MAX];
static int timer_port_callback(void *, int *, pid_t, int, void *);
static void timer_close_port(void *, int, pid_t, int);
#define CLOCK_BACKEND(clk) \
((clk) < CLOCK_MAX && (clk) >= 0 ? clock_backend[(clk)] : NULL)
int timer_max = _TIMER_MAX;
static void
timer_lock(proc_t *p, itimer_t *it)
{
ASSERT(MUTEX_HELD(&p->p_lock));
while (it->it_lock & ITLK_LOCKED) {
it->it_blockers++;
cv_wait(&it->it_cv, &p->p_lock);
it->it_blockers--;
}
it->it_lock |= ITLK_LOCKED;
}
static void
timer_unlock(proc_t *p, itimer_t *it)
{
ASSERT(MUTEX_HELD(&p->p_lock));
ASSERT(it->it_lock & ITLK_LOCKED);
it->it_lock &= ~ITLK_LOCKED;
cv_signal(&it->it_cv);
}
static void
timer_delete_locked(proc_t *p, timer_t tid, itimer_t *it)
{
ASSERT(MUTEX_HELD(&p->p_lock));
ASSERT(!(it->it_lock & ITLK_REMOVE));
ASSERT(it->it_lock & ITLK_LOCKED);
it->it_lock |= ITLK_REMOVE;
while (it->it_blockers) {
timer_unlock(p, it);
cv_wait(&it->it_cv, &p->p_lock);
timer_lock(p, it);
}
ASSERT(p->p_itimer_sz > tid);
ASSERT(p->p_itimer[tid] == it);
p->p_itimer[tid] = NULL;
mutex_exit(&p->p_lock);
it->it_backend->clk_timer_delete(it);
if (it->it_portev) {
mutex_enter(&it->it_mutex);
if (it->it_portev) {
port_kevent_t *pev;
(void) port_dissociate_ksource(it->it_portfd,
PORT_SOURCE_TIMER, (port_source_t *)it->it_portsrc);
pev = (port_kevent_t *)it->it_portev;
it->it_portev = NULL;
it->it_flags &= ~IT_PORT;
it->it_pending = 0;
mutex_exit(&it->it_mutex);
(void) port_remove_done_event(pev);
port_free_event(pev);
} else {
mutex_exit(&it->it_mutex);
}
}
mutex_enter(&p->p_lock);
if (it->it_pending > 0) {
it->it_sigq->sq_func = NULL;
} else {
kmem_free(it->it_sigq, sizeof (sigqueue_t));
}
ASSERT(it->it_blockers == 0);
kmem_cache_free(clock_timer_cache, it);
}
static itimer_t *
timer_grab(proc_t *p, timer_t tid)
{
itimer_t **itp, *it;
if (tid < 0) {
return (NULL);
}
mutex_enter(&p->p_lock);
if ((itp = p->p_itimer) == NULL || tid >= p->p_itimer_sz ||
(it = itp[tid]) == NULL) {
mutex_exit(&p->p_lock);
return (NULL);
}
timer_lock(p, it);
if (it->it_lock & ITLK_REMOVE) {
timer_unlock(p, it);
mutex_exit(&p->p_lock);
return (NULL);
}
mutex_exit(&p->p_lock);
return (it);
}
static void
timer_release(proc_t *p, itimer_t *it)
{
mutex_enter(&p->p_lock);
timer_unlock(p, it);
mutex_exit(&p->p_lock);
}
static void
timer_delete_grabbed(proc_t *p, timer_t tid, itimer_t *it)
{
mutex_enter(&p->p_lock);
timer_delete_locked(p, tid, it);
mutex_exit(&p->p_lock);
}
void
clock_timer_init()
{
clock_timer_cache = kmem_cache_create("timer_cache",
sizeof (itimer_t), 0, NULL, NULL, NULL, NULL, NULL, 0);
timer_max = MAX(NCPU * 4, timer_max);
}
void
clock_add_backend(clockid_t clock, clock_backend_t *backend)
{
ASSERT(clock >= 0 && clock < CLOCK_MAX);
ASSERT(clock_backend[clock] == NULL);
clock_backend[clock] = backend;
}
clock_backend_t *
clock_get_backend(clockid_t clock)
{
if (clock < 0 || clock >= CLOCK_MAX)
return (NULL);
return (clock_backend[clock]);
}
int
clock_settime(clockid_t clock, timespec_t *tp)
{
timespec_t t;
clock_backend_t *backend;
int error;
if ((backend = CLOCK_BACKEND(clock)) == NULL)
return (set_errno(EINVAL));
if (secpolicy_settime(CRED()) != 0)
return (set_errno(EPERM));
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyin(tp, &t, sizeof (timespec_t)) != 0)
return (set_errno(EFAULT));
} else {
timespec32_t t32;
if (copyin(tp, &t32, sizeof (timespec32_t)) != 0)
return (set_errno(EFAULT));
TIMESPEC32_TO_TIMESPEC(&t, &t32);
}
if (itimerspecfix(&t))
return (set_errno(EINVAL));
error = backend->clk_clock_settime(&t);
if (error)
return (set_errno(error));
return (0);
}
int
clock_gettime(clockid_t clock, timespec_t *tp)
{
timespec_t t;
clock_backend_t *backend;
int error;
if ((backend = CLOCK_BACKEND(clock)) == NULL)
return (set_errno(EINVAL));
error = backend->clk_clock_gettime(&t);
if (error)
return (set_errno(error));
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyout(&t, tp, sizeof (timespec_t)) != 0)
return (set_errno(EFAULT));
} else {
timespec32_t t32;
if (TIMESPEC_OVERFLOW(&t))
return (set_errno(EOVERFLOW));
TIMESPEC_TO_TIMESPEC32(&t32, &t);
if (copyout(&t32, tp, sizeof (timespec32_t)) != 0)
return (set_errno(EFAULT));
}
return (0);
}
int
clock_getres(clockid_t clock, timespec_t *tp)
{
timespec_t t;
clock_backend_t *backend;
int error;
if (tp == NULL)
return (0);
if ((backend = CLOCK_BACKEND(clock)) == NULL)
return (set_errno(EINVAL));
error = backend->clk_clock_getres(&t);
if (error)
return (set_errno(error));
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyout(&t, tp, sizeof (timespec_t)) != 0)
return (set_errno(EFAULT));
} else {
timespec32_t t32;
if (TIMESPEC_OVERFLOW(&t))
return (set_errno(EOVERFLOW));
TIMESPEC_TO_TIMESPEC32(&t32, &t);
if (copyout(&t32, tp, sizeof (timespec32_t)) != 0)
return (set_errno(EFAULT));
}
return (0);
}
void
timer_signal(sigqueue_t *sigq)
{
itimer_t *it = (itimer_t *)sigq->sq_backptr;
mutex_enter(&it->it_mutex);
ASSERT(it->it_pending > 0);
it->it_overrun = it->it_pending - 1;
it->it_pending = 0;
mutex_exit(&it->it_mutex);
}
static void
timer_fire(itimer_t *it)
{
proc_t *p = NULL;
int proc_lock_held;
if (it->it_flags & IT_SIGNAL) {
p = it->it_proc;
proc_lock_held = 1;
mutex_enter(&p->p_lock);
} else {
proc_lock_held = 0;
}
mutex_enter(&it->it_mutex);
if (it->it_pending > 0) {
if (it->it_pending < INT_MAX)
it->it_pending++;
mutex_exit(&it->it_mutex);
} else {
if (it->it_flags & IT_PORT) {
it->it_pending = 1;
port_send_event((port_kevent_t *)it->it_portev);
mutex_exit(&it->it_mutex);
} else if (it->it_flags & IT_SIGNAL) {
it->it_pending = 1;
mutex_exit(&it->it_mutex);
sigaddqa(p, NULL, it->it_sigq);
} else {
mutex_exit(&it->it_mutex);
}
}
if (proc_lock_held)
mutex_exit(&p->p_lock);
}
static boolean_t
timer_get_id(proc_t *p, timer_t *id)
{
itimer_t **itp = NULL, **itp_new;
uint_t target_sz;
uint_t i;
ASSERT(MUTEX_HELD(&p->p_lock));
if (p->p_itimer == NULL) {
ASSERT0(p->p_itimer_sz);
target_sz = _TIMER_ALLOC_INIT;
mutex_exit(&p->p_lock);
itp_new = kmem_zalloc(target_sz * sizeof (itimer_t *),
KM_SLEEP);
mutex_enter(&p->p_lock);
if (p->p_itimer == NULL) {
p->p_itimer = itp_new;
p->p_itimer_sz = target_sz;
i = 0;
goto done;
}
kmem_free(itp_new, target_sz * sizeof (itimer_t *));
}
retry:
for (i = 0; i < p->p_itimer_sz; i++) {
if (p->p_itimer[i] == NULL) {
goto done;
}
}
target_sz = p->p_itimer_sz * 2;
if (target_sz > timer_max || target_sz > INT_MAX ||
target_sz < p->p_itimer_sz) {
return (B_FALSE);
}
mutex_exit(&p->p_lock);
itp_new = kmem_zalloc(target_sz * sizeof (itimer_t *), KM_SLEEP);
mutex_enter(&p->p_lock);
if (target_sz <= p->p_itimer_sz) {
kmem_free(itp_new, target_sz * sizeof (itimer_t *));
goto retry;
}
ASSERT3P(p->p_itimer, !=, NULL);
bcopy(p->p_itimer, itp_new, p->p_itimer_sz * sizeof (itimer_t *));
kmem_free(p->p_itimer, p->p_itimer_sz * sizeof (itimer_t *));
i = p->p_itimer_sz;
p->p_itimer = itp_new;
p->p_itimer_sz = target_sz;
done:
ASSERT3U(i, <=, INT_MAX);
*id = (timer_t)i;
return (B_TRUE);
}
int
timer_create(clockid_t clock, struct sigevent *evp, timer_t *tid)
{
struct sigevent ev;
proc_t *p = curproc;
clock_backend_t *backend;
itimer_t *it;
sigqueue_t *sigq;
cred_t *cr = CRED();
int error = 0;
timer_t i;
port_notify_t tim_pnevp;
port_kevent_t *pkevp = NULL;
if ((backend = CLOCK_BACKEND(clock)) == NULL)
return (set_errno(EINVAL));
if (evp != NULL) {
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyin(evp, &ev, sizeof (struct oldsigevent)))
return (set_errno(EFAULT));
if (ev.sigev_notify == SIGEV_PORT ||
ev.sigev_notify == SIGEV_THREAD) {
if (copyin(ev.sigev_value.sival_ptr, &tim_pnevp,
sizeof (port_notify_t)))
return (set_errno(EFAULT));
}
#ifdef _SYSCALL32_IMPL
} else {
struct sigevent32 ev32;
port_notify32_t tim_pnevp32;
if (copyin(evp, &ev32, sizeof (struct oldsigevent32)))
return (set_errno(EFAULT));
ev.sigev_notify = ev32.sigev_notify;
ev.sigev_signo = ev32.sigev_signo;
ev.sigev_value.sival_int = ev32.sigev_value.sival_int;
if (ev.sigev_notify == SIGEV_PORT ||
ev.sigev_notify == SIGEV_THREAD) {
if (copyin((void *)(uintptr_t)
ev32.sigev_value.sival_ptr,
(void *)&tim_pnevp32,
sizeof (port_notify32_t)))
return (set_errno(EFAULT));
tim_pnevp.portnfy_port =
tim_pnevp32.portnfy_port;
tim_pnevp.portnfy_user =
(void *)(uintptr_t)tim_pnevp32.portnfy_user;
}
#endif
}
switch (ev.sigev_notify) {
case SIGEV_NONE:
break;
case SIGEV_SIGNAL:
if (ev.sigev_signo < 1 || ev.sigev_signo >= NSIG)
return (set_errno(EINVAL));
break;
case SIGEV_THREAD:
case SIGEV_PORT:
break;
default:
return (set_errno(EINVAL));
}
} else {
ev = backend->clk_default;
}
sigq = kmem_zalloc(sizeof (sigqueue_t), KM_SLEEP);
it = kmem_cache_alloc(clock_timer_cache, KM_SLEEP);
bzero(it, sizeof (*it));
mutex_init(&it->it_mutex, NULL, MUTEX_DEFAULT, NULL);
mutex_enter(&p->p_lock);
if (!timer_get_id(p, &i)) {
mutex_exit(&p->p_lock);
kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
return (set_errno(EAGAIN));
}
ASSERT(i < p->p_itimer_sz && p->p_itimer[i] == NULL);
sigq->sq_info.si_signo = ev.sigev_signo;
if (evp == NULL)
sigq->sq_info.si_value.sival_int = i;
else
sigq->sq_info.si_value = ev.sigev_value;
sigq->sq_info.si_code = SI_TIMER;
sigq->sq_info.si_pid = p->p_pid;
sigq->sq_info.si_ctid = PRCTID(p);
sigq->sq_info.si_zoneid = getzoneid();
sigq->sq_info.si_uid = crgetruid(cr);
sigq->sq_func = timer_signal;
sigq->sq_next = NULL;
sigq->sq_backptr = it;
it->it_sigq = sigq;
it->it_backend = backend;
it->it_lock = ITLK_LOCKED;
if (ev.sigev_notify == SIGEV_THREAD ||
ev.sigev_notify == SIGEV_PORT) {
int port;
it->it_flags |= IT_PORT;
port = tim_pnevp.portnfy_port;
error = port_associate_ksource(port, PORT_SOURCE_TIMER,
(port_source_t **)&it->it_portsrc, timer_close_port,
(void *)it, NULL);
if (error) {
mutex_exit(&p->p_lock);
kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
return (set_errno(error));
}
error = port_alloc_event(port, PORT_ALLOC_SCACHED,
PORT_SOURCE_TIMER, &pkevp);
if (error) {
(void) port_dissociate_ksource(port, PORT_SOURCE_TIMER,
(port_source_t *)it->it_portsrc);
mutex_exit(&p->p_lock);
kmem_cache_free(clock_timer_cache, it);
kmem_free(sigq, sizeof (sigqueue_t));
return (set_errno(error));
}
port_init_event(pkevp, i, tim_pnevp.portnfy_user,
timer_port_callback, it);
it->it_portev = pkevp;
it->it_portfd = port;
} else {
if (ev.sigev_notify == SIGEV_SIGNAL)
it->it_flags |= IT_SIGNAL;
}
p->p_itimer[i] = it;
mutex_exit(&p->p_lock);
if ((error = backend->clk_timer_create(it, timer_fire)) != 0)
goto err;
it->it_lwp = ttolwp(curthread);
it->it_proc = p;
if (copyout(&i, tid, sizeof (timer_t)) != 0) {
error = EFAULT;
goto err;
}
timer_release(p, it);
return (0);
err:
ASSERT(!(it->it_lock & ITLK_REMOVE));
timer_delete_grabbed(p, i, it);
return (set_errno(error));
}
int
timer_gettime(timer_t tid, itimerspec_t *val)
{
proc_t *p = curproc;
itimer_t *it;
itimerspec_t when;
int error;
if ((it = timer_grab(p, tid)) == NULL)
return (set_errno(EINVAL));
error = it->it_backend->clk_timer_gettime(it, &when);
timer_release(p, it);
if (error == 0) {
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyout(&when, val, sizeof (itimerspec_t)))
error = EFAULT;
} else {
if (ITIMERSPEC_OVERFLOW(&when))
error = EOVERFLOW;
else {
itimerspec32_t w32;
ITIMERSPEC_TO_ITIMERSPEC32(&w32, &when)
if (copyout(&w32, val, sizeof (itimerspec32_t)))
error = EFAULT;
}
}
}
return (error ? set_errno(error) : 0);
}
int
timer_settime(timer_t tid, int flags, itimerspec_t *val, itimerspec_t *oval)
{
itimerspec_t when;
itimer_t *it;
proc_t *p = curproc;
int error;
if (oval != NULL) {
if ((error = timer_gettime(tid, oval)) != 0)
return (error);
}
if (get_udatamodel() == DATAMODEL_NATIVE) {
if (copyin(val, &when, sizeof (itimerspec_t)))
return (set_errno(EFAULT));
} else {
itimerspec32_t w32;
if (copyin(val, &w32, sizeof (itimerspec32_t)))
return (set_errno(EFAULT));
ITIMERSPEC32_TO_ITIMERSPEC(&when, &w32);
}
if (itimerspecfix(&when.it_value) ||
(itimerspecfix(&when.it_interval) &&
timerspecisset(&when.it_value))) {
return (set_errno(EINVAL));
}
if ((it = timer_grab(p, tid)) == NULL)
return (set_errno(EINVAL));
error = it->it_backend->clk_timer_settime(it, flags, &when);
timer_release(p, it);
return (error ? set_errno(error) : 0);
}
int
timer_delete(timer_t tid)
{
proc_t *p = curproc;
itimer_t *it;
if ((it = timer_grab(p, tid)) == NULL)
return (set_errno(EINVAL));
timer_delete_grabbed(p, tid, it);
return (0);
}
int
timer_getoverrun(timer_t tid)
{
int overrun;
proc_t *p = curproc;
itimer_t *it;
if ((it = timer_grab(p, tid)) == NULL)
return (set_errno(EINVAL));
mutex_enter(&p->p_lock);
overrun = it->it_overrun;
mutex_exit(&p->p_lock);
timer_release(p, it);
return (overrun);
}
void
timer_lwpexit(void)
{
uint_t i;
proc_t *p = curproc;
klwp_t *lwp = ttolwp(curthread);
itimer_t *it, **itp;
ASSERT(MUTEX_HELD(&p->p_lock));
if ((itp = p->p_itimer) == NULL)
return;
for (i = 0; i < p->p_itimer_sz; i++) {
if ((it = itp[i]) == NULL)
continue;
timer_lock(p, it);
if ((it->it_lock & ITLK_REMOVE) || it->it_lwp != lwp) {
timer_unlock(p, it);
continue;
}
it->it_lwp = NULL;
timer_unlock(p, it);
}
}
void
timer_lwpbind()
{
uint_t i;
proc_t *p = curproc;
klwp_t *lwp = ttolwp(curthread);
itimer_t *it, **itp;
ASSERT(MUTEX_HELD(&p->p_lock));
if ((itp = p->p_itimer) == NULL)
return;
for (i = 0; i < p->p_itimer_sz; i++) {
if ((it = itp[i]) == NULL)
continue;
timer_lock(p, it);
if (!(it->it_lock & ITLK_REMOVE) && it->it_lwp == lwp) {
mutex_exit(&p->p_lock);
it->it_backend->clk_timer_lwpbind(it);
mutex_enter(&p->p_lock);
}
timer_unlock(p, it);
}
}
void
timer_exit(void)
{
uint_t i;
proc_t *p = curproc;
ASSERT(p->p_itimer != NULL);
ASSERT(p->p_itimer_sz != 0);
for (i = 0; i < p->p_itimer_sz; i++) {
(void) timer_delete((timer_t)i);
}
kmem_free(p->p_itimer, p->p_itimer_sz * sizeof (itimer_t *));
p->p_itimer = NULL;
p->p_itimer_sz = 0;
}
static int
timer_port_callback(void *arg, int *events, pid_t pid, int flag, void *evp)
{
itimer_t *it = arg;
mutex_enter(&it->it_mutex);
if (curproc != it->it_proc) {
mutex_exit(&it->it_mutex);
return (EACCES);
}
*events = it->it_pending;
it->it_pending = 0;
mutex_exit(&it->it_mutex);
return (0);
}
static void
timer_close_port(void *arg, int port, pid_t pid, int lastclose)
{
proc_t *p = curproc;
timer_t tid;
itimer_t *it;
for (tid = 0; tid < timer_max; tid++) {
if ((it = timer_grab(p, tid)) == NULL)
continue;
if (it->it_portev) {
mutex_enter(&it->it_mutex);
if (it->it_portfd == port) {
port_kevent_t *pev;
pev = (port_kevent_t *)it->it_portev;
it->it_portev = NULL;
it->it_flags &= ~IT_PORT;
mutex_exit(&it->it_mutex);
(void) port_remove_done_event(pev);
port_free_event(pev);
} else {
mutex_exit(&it->it_mutex);
}
}
timer_release(p, it);
}
}