timerfd: Move implementation from linux compat to sys/kern

Move the timerfd impelemntation from linux compat code to sys/kern. Use
it to implement the new system calls for timerfd. Add a hook to kern_tc
to allow timerfd to know when the system time has stepped. Add kqueue
support to timerfd. Adjust a few names to be less Linux centric.

RelNotes: YES
Reviewed by: markj (on irc), imp, kib (with reservations), jhb (slack)
Differential Revision: https://reviews.freebsd.org/D38459
This commit is contained in:
Jake Freeland 2023-08-23 22:39:54 -06:00 committed by Warner Losh
parent f8bc606aad
commit af93fea710
23 changed files with 1004 additions and 430 deletions

View File

@ -421,6 +421,9 @@ FBSD_1.7 {
kqueuex;
membarrier;
swapoff;
timerfd_create;
timerfd_gettime;
timerfd_settime;
};
FBSDprivate_1.0 {

View File

@ -661,6 +661,7 @@
#define AUE_AIO_WRITEV 43267 /* FreeBSD-specific. */
#define AUE_AIO_READV 43268 /* FreeBSD-specific. */
#define AUE_FSPACECTL 43269 /* FreeBSD-specific. */
#define AUE_TIMERFD 43270 /* FreeBSD/Linux. */
/*
* Darwin BSM uses a number of AUE_O_* definitions, which are aliased to the

View File

@ -684,6 +684,16 @@ struct freebsd32_aio_writev_args {
struct freebsd32_aio_readv_args {
char aiocbp_l_[PADL_(struct aiocb32 *)]; struct aiocb32 * aiocbp; char aiocbp_r_[PADR_(struct aiocb32 *)];
};
struct freebsd32_timerfd_gettime_args {
char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
char curr_value_l_[PADL_(struct itimerspec32 *)]; struct itimerspec32 * curr_value; char curr_value_r_[PADR_(struct itimerspec32 *)];
};
struct freebsd32_timerfd_settime_args {
char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
char flags_l_[PADL_(int)]; int flags; char flags_r_[PADR_(int)];
char new_value_l_[PADL_(const struct itimerspec32 *)]; const struct itimerspec32 * new_value; char new_value_r_[PADR_(const struct itimerspec32 *)];
char old_value_l_[PADL_(struct itimerspec32 *)]; struct itimerspec32 * old_value; char old_value_r_[PADR_(struct itimerspec32 *)];
};
int freebsd32_wait4(struct thread *, struct freebsd32_wait4_args *);
int freebsd32_ptrace(struct thread *, struct freebsd32_ptrace_args *);
int freebsd32_recvmsg(struct thread *, struct freebsd32_recvmsg_args *);
@ -799,6 +809,8 @@ int freebsd32_cpuset_setdomain(struct thread *, struct freebsd32_cpuset_setdomai
int freebsd32___sysctlbyname(struct thread *, struct freebsd32___sysctlbyname_args *);
int freebsd32_aio_writev(struct thread *, struct freebsd32_aio_writev_args *);
int freebsd32_aio_readv(struct thread *, struct freebsd32_aio_readv_args *);
int freebsd32_timerfd_gettime(struct thread *, struct freebsd32_timerfd_gettime_args *);
int freebsd32_timerfd_settime(struct thread *, struct freebsd32_timerfd_settime_args *);
#ifdef COMPAT_43
@ -1292,6 +1304,8 @@ int freebsd11_freebsd32_fstatat(struct thread *, struct freebsd11_freebsd32_fsta
#define FREEBSD32_SYS_AUE_freebsd32___sysctlbyname AUE_SYSCTL
#define FREEBSD32_SYS_AUE_freebsd32_aio_writev AUE_AIO_WRITEV
#define FREEBSD32_SYS_AUE_freebsd32_aio_readv AUE_AIO_READV
#define FREEBSD32_SYS_AUE_freebsd32_timerfd_gettime AUE_TIMERFD
#define FREEBSD32_SYS_AUE_freebsd32_timerfd_settime AUE_TIMERFD
#undef PAD_
#undef PADL_

View File

@ -502,4 +502,7 @@
#define FREEBSD32_SYS_swapoff 582
#define FREEBSD32_SYS_kqueuex 583
#define FREEBSD32_SYS_membarrier 584
#define FREEBSD32_SYS_MAXSYSCALL 585
#define FREEBSD32_SYS_timerfd_create 585
#define FREEBSD32_SYS_freebsd32_timerfd_gettime 586
#define FREEBSD32_SYS_freebsd32_timerfd_settime 587
#define FREEBSD32_SYS_MAXSYSCALL 588

View File

@ -590,4 +590,7 @@ const char *freebsd32_syscallnames[] = {
"swapoff", /* 582 = swapoff */
"kqueuex", /* 583 = kqueuex */
"membarrier", /* 584 = membarrier */
"timerfd_create", /* 585 = timerfd_create */
"freebsd32_timerfd_gettime", /* 586 = freebsd32_timerfd_gettime */
"freebsd32_timerfd_settime", /* 587 = freebsd32_timerfd_settime */
};

View File

@ -646,4 +646,7 @@ struct sysent freebsd32_sysent[] = {
{ .sy_narg = AS(swapoff_args), .sy_call = (sy_call_t *)sys_swapoff, .sy_auevent = AUE_SWAPOFF, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 582 = swapoff */
{ .sy_narg = AS(kqueuex_args), .sy_call = (sy_call_t *)sys_kqueuex, .sy_auevent = AUE_KQUEUE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 583 = kqueuex */
{ .sy_narg = AS(membarrier_args), .sy_call = (sy_call_t *)sys_membarrier, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = membarrier */
{ .sy_narg = AS(timerfd_create_args), .sy_call = (sy_call_t *)sys_timerfd_create, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 585 = timerfd_create */
{ .sy_narg = AS(freebsd32_timerfd_gettime_args), .sy_call = (sy_call_t *)freebsd32_timerfd_gettime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 586 = freebsd32_timerfd_gettime */
{ .sy_narg = AS(freebsd32_timerfd_settime_args), .sy_call = (sy_call_t *)freebsd32_timerfd_settime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 587 = freebsd32_timerfd_settime */
};

View File

@ -3336,6 +3336,32 @@ systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
*n_args = 3;
break;
}
/* timerfd_create */
case 585: {
struct timerfd_create_args *p = params;
iarg[a++] = p->clockid; /* int */
iarg[a++] = p->flags; /* int */
*n_args = 2;
break;
}
/* freebsd32_timerfd_gettime */
case 586: {
struct freebsd32_timerfd_gettime_args *p = params;
iarg[a++] = p->fd; /* int */
uarg[a++] = (intptr_t)p->curr_value; /* struct itimerspec32 * */
*n_args = 2;
break;
}
/* freebsd32_timerfd_settime */
case 587: {
struct freebsd32_timerfd_settime_args *p = params;
iarg[a++] = p->fd; /* int */
iarg[a++] = p->flags; /* int */
uarg[a++] = (intptr_t)p->new_value; /* const struct itimerspec32 * */
uarg[a++] = (intptr_t)p->old_value; /* struct itimerspec32 * */
*n_args = 4;
break;
}
default:
*n_args = 0;
break;
@ -9005,6 +9031,51 @@ systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
break;
};
break;
/* timerfd_create */
case 585:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "int";
break;
default:
break;
};
break;
/* freebsd32_timerfd_gettime */
case 586:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "userland struct itimerspec32 *";
break;
default:
break;
};
break;
/* freebsd32_timerfd_settime */
case 587:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "int";
break;
case 2:
p = "userland const struct itimerspec32 *";
break;
case 3:
p = "userland struct itimerspec32 *";
break;
default:
break;
};
break;
default:
break;
};
@ -10873,6 +10944,21 @@ systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* timerfd_create */
case 585:
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* freebsd32_timerfd_gettime */
case 586:
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* freebsd32_timerfd_settime */
case 587:
if (ndx == 0 || ndx == 1)
p = "int";
break;
default:
break;
};

View File

@ -44,6 +44,7 @@
#include <sys/specialfd.h>
#include <sys/sx.h>
#include <sys/syscallsubr.h>
#include <sys/timerfd.h>
#include <sys/timespec.h>
#include <sys/user.h>
@ -99,55 +100,6 @@ struct epoll_copyout_args {
int error;
};
/* timerfd */
typedef uint64_t timerfd_t;
static fo_rdwr_t timerfd_read;
static fo_ioctl_t timerfd_ioctl;
static fo_poll_t timerfd_poll;
static fo_kqfilter_t timerfd_kqfilter;
static fo_stat_t timerfd_stat;
static fo_close_t timerfd_close;
static fo_fill_kinfo_t timerfd_fill_kinfo;
static struct fileops timerfdops = {
.fo_read = timerfd_read,
.fo_write = invfo_rdwr,
.fo_truncate = invfo_truncate,
.fo_ioctl = timerfd_ioctl,
.fo_poll = timerfd_poll,
.fo_kqfilter = timerfd_kqfilter,
.fo_stat = timerfd_stat,
.fo_close = timerfd_close,
.fo_chmod = invfo_chmod,
.fo_chown = invfo_chown,
.fo_sendfile = invfo_sendfile,
.fo_fill_kinfo = timerfd_fill_kinfo,
.fo_flags = DFLAG_PASSABLE
};
static void filt_timerfddetach(struct knote *kn);
static int filt_timerfdread(struct knote *kn, long hint);
static struct filterops timerfd_rfiltops = {
.f_isfd = 1,
.f_detach = filt_timerfddetach,
.f_event = filt_timerfdread
};
struct timerfd {
clockid_t tfd_clockid;
struct itimerspec tfd_time;
struct callout tfd_callout;
timerfd_t tfd_count;
bool tfd_canceled;
struct selinfo tfd_sel;
struct mtx tfd_lock;
};
static void linux_timerfd_expire(void *);
static void linux_timerfd_curval(struct timerfd *, struct itimerspec *);
static int
epoll_create_common(struct thread *td, int flags)
{
@ -658,255 +610,14 @@ linux_eventfd2(struct thread *td, struct linux_eventfd2_args *args)
int
linux_timerfd_create(struct thread *td, struct linux_timerfd_create_args *args)
{
struct timerfd *tfd;
struct file *fp;
clockid_t clockid;
int fflags, fd, error;
if ((args->flags & ~LINUX_TFD_CREATE_FLAGS) != 0)
return (EINVAL);
int error;
error = linux_to_native_clockid(&clockid, args->clockid);
if (error != 0)
return (error);
if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC)
return (EINVAL);
fflags = 0;
if ((args->flags & LINUX_TFD_CLOEXEC) != 0)
fflags |= O_CLOEXEC;
error = falloc(td, &fp, &fd, fflags);
if (error != 0)
return (error);
tfd = malloc(sizeof(*tfd), M_EPOLL, M_WAITOK | M_ZERO);
tfd->tfd_clockid = clockid;
mtx_init(&tfd->tfd_lock, "timerfd", NULL, MTX_DEF);
callout_init_mtx(&tfd->tfd_callout, &tfd->tfd_lock, 0);
knlist_init_mtx(&tfd->tfd_sel.si_note, &tfd->tfd_lock);
fflags = FREAD;
if ((args->flags & LINUX_O_NONBLOCK) != 0)
fflags |= FNONBLOCK;
finit(fp, fflags, DTYPE_LINUXTFD, tfd, &timerfdops);
fdrop(fp, td);
td->td_retval[0] = fd;
return (error);
}
static int
timerfd_close(struct file *fp, struct thread *td)
{
struct timerfd *tfd;
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL)
return (EINVAL);
timespecclear(&tfd->tfd_time.it_value);
timespecclear(&tfd->tfd_time.it_interval);
callout_drain(&tfd->tfd_callout);
seldrain(&tfd->tfd_sel);
knlist_destroy(&tfd->tfd_sel.si_note);
fp->f_ops = &badfileops;
mtx_destroy(&tfd->tfd_lock);
free(tfd, M_EPOLL);
return (0);
}
static int
timerfd_read(struct file *fp, struct uio *uio, struct ucred *active_cred,
int flags, struct thread *td)
{
struct timerfd *tfd;
timerfd_t count;
int error;
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL)
return (EINVAL);
if (uio->uio_resid < sizeof(timerfd_t))
return (EINVAL);
error = 0;
mtx_lock(&tfd->tfd_lock);
retry:
if (tfd->tfd_canceled) {
tfd->tfd_count = 0;
mtx_unlock(&tfd->tfd_lock);
return (ECANCELED);
}
if (tfd->tfd_count == 0) {
if ((fp->f_flag & FNONBLOCK) != 0) {
mtx_unlock(&tfd->tfd_lock);
return (EAGAIN);
}
error = mtx_sleep(&tfd->tfd_count, &tfd->tfd_lock, PCATCH, "ltfdrd", 0);
if (error == 0)
goto retry;
}
if (error == 0) {
count = tfd->tfd_count;
tfd->tfd_count = 0;
mtx_unlock(&tfd->tfd_lock);
error = uiomove(&count, sizeof(timerfd_t), uio);
} else
mtx_unlock(&tfd->tfd_lock);
return (error);
}
static int
timerfd_poll(struct file *fp, int events, struct ucred *active_cred,
struct thread *td)
{
struct timerfd *tfd;
int revents = 0;
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL)
return (POLLERR);
mtx_lock(&tfd->tfd_lock);
if ((events & (POLLIN|POLLRDNORM)) && tfd->tfd_count > 0)
revents |= events & (POLLIN|POLLRDNORM);
if (revents == 0)
selrecord(td, &tfd->tfd_sel);
mtx_unlock(&tfd->tfd_lock);
return (revents);
}
static int
timerfd_kqfilter(struct file *fp, struct knote *kn)
{
struct timerfd *tfd;
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL)
return (EINVAL);
if (kn->kn_filter == EVFILT_READ)
kn->kn_fop = &timerfd_rfiltops;
else
return (EINVAL);
kn->kn_hook = tfd;
knlist_add(&tfd->tfd_sel.si_note, kn, 0);
return (0);
}
static void
filt_timerfddetach(struct knote *kn)
{
struct timerfd *tfd = kn->kn_hook;
mtx_lock(&tfd->tfd_lock);
knlist_remove(&tfd->tfd_sel.si_note, kn, 1);
mtx_unlock(&tfd->tfd_lock);
}
static int
filt_timerfdread(struct knote *kn, long hint)
{
struct timerfd *tfd = kn->kn_hook;
return (tfd->tfd_count > 0);
}
static int
timerfd_ioctl(struct file *fp, u_long cmd, void *data,
struct ucred *active_cred, struct thread *td)
{
if (fp->f_data == NULL || fp->f_type != DTYPE_LINUXTFD)
return (EINVAL);
switch (cmd) {
case FIONBIO:
case FIOASYNC:
return (0);
}
return (ENOTTY);
}
static int
timerfd_stat(struct file *fp, struct stat *st, struct ucred *active_cred)
{
return (ENXIO);
}
static int
timerfd_fill_kinfo(struct file *fp, struct kinfo_file *kif, struct filedesc *fdp)
{
kif->kf_type = KF_TYPE_UNKNOWN;
return (0);
}
static void
linux_timerfd_clocktime(struct timerfd *tfd, struct timespec *ts)
{
if (tfd->tfd_clockid == CLOCK_REALTIME)
getnanotime(ts);
else /* CLOCK_MONOTONIC */
getnanouptime(ts);
}
static void
linux_timerfd_curval(struct timerfd *tfd, struct itimerspec *ots)
{
struct timespec cts;
linux_timerfd_clocktime(tfd, &cts);
*ots = tfd->tfd_time;
if (ots->it_value.tv_sec != 0 || ots->it_value.tv_nsec != 0) {
timespecsub(&ots->it_value, &cts, &ots->it_value);
if (ots->it_value.tv_sec < 0 ||
(ots->it_value.tv_sec == 0 &&
ots->it_value.tv_nsec == 0)) {
ots->it_value.tv_sec = 0;
ots->it_value.tv_nsec = 1;
}
}
}
static int
linux_timerfd_gettime_common(struct thread *td, int fd, struct itimerspec *ots)
{
struct timerfd *tfd;
struct file *fp;
int error;
error = fget(td, fd, &cap_read_rights, &fp);
if (error != 0)
return (error);
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) {
error = EINVAL;
goto out;
}
mtx_lock(&tfd->tfd_lock);
linux_timerfd_curval(tfd, ots);
mtx_unlock(&tfd->tfd_lock);
out:
fdrop(fp, td);
return (error);
return (kern_timerfd_create(td, clockid, args->flags));
}
int
@ -916,12 +627,40 @@ linux_timerfd_gettime(struct thread *td, struct linux_timerfd_gettime_args *args
struct itimerspec ots;
int error;
error = linux_timerfd_gettime_common(td, args->fd, &ots);
error = kern_timerfd_gettime(td, args->fd, &ots);
if (error != 0)
return (error);
error = native_to_linux_itimerspec(&lots, &ots);
if (error == 0)
error = copyout(&lots, args->old_value, sizeof(lots));
return (error);
}
int
linux_timerfd_settime(struct thread *td, struct linux_timerfd_settime_args *args)
{
struct l_itimerspec lots;
struct itimerspec nts, ots;
int error;
error = copyin(args->new_value, &lots, sizeof(lots));
if (error != 0)
return (error);
error = linux_to_native_itimerspec(&nts, &lots);
if (error != 0)
return (error);
if (args->old_value == NULL)
error = kern_timerfd_settime(td, args->fd, args->flags, &nts, NULL);
else
error = kern_timerfd_settime(td, args->fd, args->flags, &nts, &ots);
if (error == 0 && args->old_value != NULL) {
error = native_to_linux_itimerspec(&lots, &ots);
if (error == 0)
error = copyout(&lots, args->old_value, sizeof(lots));
}
return (error);
}
@ -933,100 +672,22 @@ linux_timerfd_gettime64(struct thread *td, struct linux_timerfd_gettime64_args *
struct itimerspec ots;
int error;
error = linux_timerfd_gettime_common(td, args->fd, &ots);
error = kern_timerfd_gettime(td, args->fd, &ots);
if (error != 0)
return (error);
error = native_to_linux_itimerspec64(&lots, &ots);
if (error == 0)
error = copyout(&lots, args->old_value, sizeof(lots));
return (error);
}
#endif
static int
linux_timerfd_settime_common(struct thread *td, int fd, int flags,
struct itimerspec *nts, struct itimerspec *oval)
{
struct timespec cts, ts;
struct timerfd *tfd;
struct timeval tv;
struct file *fp;
int error;
if ((flags & ~LINUX_TFD_SETTIME_FLAGS) != 0)
return (EINVAL);
error = fget(td, fd, &cap_write_rights, &fp);
if (error != 0)
return (error);
tfd = fp->f_data;
if (fp->f_type != DTYPE_LINUXTFD || tfd == NULL) {
error = EINVAL;
goto out;
}
mtx_lock(&tfd->tfd_lock);
if (!timespecisset(&nts->it_value))
timespecclear(&nts->it_interval);
if (oval != NULL)
linux_timerfd_curval(tfd, oval);
bcopy(nts, &tfd->tfd_time, sizeof(*nts));
tfd->tfd_count = 0;
if (timespecisset(&nts->it_value)) {
linux_timerfd_clocktime(tfd, &cts);
ts = nts->it_value;
if ((flags & LINUX_TFD_TIMER_ABSTIME) == 0) {
timespecadd(&tfd->tfd_time.it_value, &cts,
&tfd->tfd_time.it_value);
} else {
timespecsub(&ts, &cts, &ts);
}
TIMESPEC_TO_TIMEVAL(&tv, &ts);
callout_reset(&tfd->tfd_callout, tvtohz(&tv),
linux_timerfd_expire, tfd);
tfd->tfd_canceled = false;
} else {
tfd->tfd_canceled = true;
callout_stop(&tfd->tfd_callout);
}
mtx_unlock(&tfd->tfd_lock);
out:
fdrop(fp, td);
return (error);
}
int
linux_timerfd_settime(struct thread *td, struct linux_timerfd_settime_args *args)
{
struct l_itimerspec lots;
struct itimerspec nts, ots, *pots;
int error;
error = copyin(args->new_value, &lots, sizeof(lots));
if (error != 0)
return (error);
error = linux_to_native_itimerspec(&nts, &lots);
if (error != 0)
return (error);
pots = (args->old_value != NULL ? &ots : NULL);
error = linux_timerfd_settime_common(td, args->fd, args->flags,
&nts, pots);
if (error == 0 && args->old_value != NULL) {
error = native_to_linux_itimerspec(&lots, &ots);
if (error == 0)
error = copyout(&lots, args->old_value, sizeof(lots));
}
return (error);
}
#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32))
int
linux_timerfd_settime64(struct thread *td, struct linux_timerfd_settime64_args *args)
{
struct l_itimerspec64 lots;
struct itimerspec nts, ots, *pots;
struct itimerspec nts, ots;
int error;
error = copyin(args->new_value, &lots, sizeof(lots));
@ -1035,50 +696,16 @@ linux_timerfd_settime64(struct thread *td, struct linux_timerfd_settime64_args *
error = linux_to_native_itimerspec64(&nts, &lots);
if (error != 0)
return (error);
pots = (args->old_value != NULL ? &ots : NULL);
error = linux_timerfd_settime_common(td, args->fd, args->flags,
&nts, pots);
if (args->old_value == NULL)
error = kern_timerfd_settime(td, args->fd, args->flags, &nts, NULL);
else
error = kern_timerfd_settime(td, args->fd, args->flags, &nts, &ots);
if (error == 0 && args->old_value != NULL) {
error = native_to_linux_itimerspec64(&lots, &ots);
if (error == 0)
error = copyout(&lots, args->old_value, sizeof(lots));
}
return (error);
}
#endif
static void
linux_timerfd_expire(void *arg)
{
struct timespec cts, ts;
struct timeval tv;
struct timerfd *tfd;
tfd = (struct timerfd *)arg;
linux_timerfd_clocktime(tfd, &cts);
if (timespeccmp(&cts, &tfd->tfd_time.it_value, >=)) {
if (timespecisset(&tfd->tfd_time.it_interval))
timespecadd(&tfd->tfd_time.it_value,
&tfd->tfd_time.it_interval,
&tfd->tfd_time.it_value);
else
/* single shot timer */
timespecclear(&tfd->tfd_time.it_value);
if (timespecisset(&tfd->tfd_time.it_value)) {
timespecsub(&tfd->tfd_time.it_value, &cts, &ts);
TIMESPEC_TO_TIMEVAL(&tv, &ts);
callout_reset(&tfd->tfd_callout, tvtohz(&tv),
linux_timerfd_expire, tfd);
}
tfd->tfd_count++;
KNOTE_LOCKED(&tfd->tfd_sel.si_note, 0);
selwakeup(&tfd->tfd_sel);
wakeup(&tfd->tfd_count);
} else if (timespecisset(&tfd->tfd_time.it_value)) {
timespecsub(&tfd->tfd_time.it_value, &cts, &ts);
TIMESPEC_TO_TIMEVAL(&tv, &ts);
callout_reset(&tfd->tfd_callout, tvtohz(&tv),
linux_timerfd_expire, tfd);
}
}

View File

@ -54,15 +54,4 @@
#define LINUX_EFD_SEMAPHORE (1 << 0)
#define LINUX_TFD_TIMER_ABSTIME (1 << 0)
#define LINUX_TFD_TIMER_CANCEL_ON_SET (1 << 1)
#define LINUX_TFD_CLOEXEC LINUX_O_CLOEXEC
#define LINUX_TFD_NONBLOCK LINUX_O_NONBLOCK
#define LINUX_TFD_SHARED_FCNTL_FLAGS (LINUX_TFD_CLOEXEC \
|LINUX_TFD_NONBLOCK)
#define LINUX_TFD_CREATE_FLAGS LINUX_TFD_SHARED_FCNTL_FLAGS
#define LINUX_TFD_SETTIME_FLAGS (LINUX_TFD_TIMER_ABSTIME \
|LINUX_TFD_TIMER_CANCEL_ON_SET)
#endif /* !_LINUX_EVENT_H_ */

View File

@ -3908,6 +3908,7 @@ kern/sys_pipe.c standard
kern/sys_procdesc.c standard
kern/sys_process.c standard
kern/sys_socket.c standard
kern/sys_timerfd.c standard
kern/syscalls.c standard
kern/sysv_ipc.c standard
kern/sysv_msg.c optional sysvmsg

View File

@ -645,4 +645,7 @@ struct sysent sysent[] = {
{ .sy_narg = AS(swapoff_args), .sy_call = (sy_call_t *)sys_swapoff, .sy_auevent = AUE_SWAPOFF, .sy_flags = 0, .sy_thrcnt = SY_THR_STATIC }, /* 582 = swapoff */
{ .sy_narg = AS(kqueuex_args), .sy_call = (sy_call_t *)sys_kqueuex, .sy_auevent = AUE_KQUEUE, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 583 = kqueuex */
{ .sy_narg = AS(membarrier_args), .sy_call = (sy_call_t *)sys_membarrier, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = membarrier */
{ .sy_narg = AS(timerfd_create_args), .sy_call = (sy_call_t *)sys_timerfd_create, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 585 = timerfd_create */
{ .sy_narg = AS(timerfd_gettime_args), .sy_call = (sy_call_t *)sys_timerfd_gettime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 586 = timerfd_gettime */
{ .sy_narg = AS(timerfd_settime_args), .sy_call = (sy_call_t *)sys_timerfd_settime, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 587 = timerfd_settime */
};

View File

@ -5001,8 +5001,8 @@ file_type_to_name(short type)
return ("proc");
case DTYPE_EVENTFD:
return ("eventfd");
case DTYPE_LINUXTFD:
return ("ltimer");
case DTYPE_TIMERFD:
return ("timerfd");
default:
return ("unkn");
}

View File

@ -34,6 +34,7 @@
#include <sys/systm.h>
#include <sys/timeffc.h>
#include <sys/timepps.h>
#include <sys/timerfd.h>
#include <sys/timetc.h>
#include <sys/timex.h>
#include <sys/vdso.h>
@ -1305,6 +1306,7 @@ tc_setclock(struct timespec *ts)
/* Avoid rtc_generation == 0, since td_rtcgen == 0 is special. */
atomic_add_rel_int(&rtc_generation, 2);
timerfd_jumped();
sleepq_chains_remove_matching(sleeping_on_old_rtc);
if (timestepwarnings) {
nanotime(&taft);

632
sys/kern/sys_timerfd.c Normal file
View File

@ -0,0 +1,632 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2014 Dmitry Chagin <dchagin@FreeBSD.org>
* Copyright (c) 2023 Jake Freeland <jfree@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/callout.h>
#include <sys/fcntl.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/filio.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mount.h>
#include <sys/mutex.h>
#include <sys/poll.h>
#include <sys/proc.h>
#include <sys/queue.h>
#include <sys/selinfo.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/sysent.h>
#include <sys/sysproto.h>
#include <sys/timerfd.h>
#include <sys/timespec.h>
#include <sys/uio.h>
#include <sys/user.h>
#include <security/audit/audit.h>
#ifdef COMPAT_FREEBSD32
#include <compat/freebsd32/freebsd32.h>
#include <compat/freebsd32/freebsd32_proto.h>
#endif
static MALLOC_DEFINE(M_TIMERFD, "timerfd", "timerfd structures");
static LIST_HEAD(, timerfd) timerfd_head;
static struct unrhdr64 tfdino_unr;
#define TFD_NOJUMP 0 /* Realtime clock has not jumped. */
#define TFD_READ 1 /* Jumped, tfd has been read since. */
#define TFD_ZREAD 2 /* Jumped backwards, CANCEL_ON_SET=false. */
#define TFD_CANCELED 4 /* Jumped, CANCEL_ON_SET=true. */
#define TFD_JUMPED (TFD_ZREAD | TFD_CANCELED)
struct timerfd {
/* User specified. */
struct itimerspec tfd_time; /* tfd timer */
clockid_t tfd_clockid; /* timing base */
int tfd_flags; /* creation flags */
int tfd_timflags; /* timer flags */
/* Used internally. */
timerfd_t tfd_count; /* expiration count since last read */
bool tfd_expired; /* true upon initial expiration */
struct mtx tfd_lock; /* mtx lock */
struct callout tfd_callout; /* expiration notification */
struct selinfo tfd_sel; /* I/O alerts */
struct timespec tfd_boottim; /* cached boottime */
int tfd_jumped; /* timer jump status */
LIST_ENTRY(timerfd) entry; /* entry in list */
/* For stat(2). */
ino_t tfd_ino; /* inode number */
struct timespec tfd_atim; /* time of last read */
struct timespec tfd_mtim; /* time of last settime */
struct timespec tfd_birthtim; /* creation time */
};
static void
timerfd_init(void *data)
{
new_unrhdr64(&tfdino_unr, 1);
}
SYSINIT(timerfd, SI_SUB_VFS, SI_ORDER_ANY, timerfd_init, NULL);
static inline void
timerfd_getboottime(struct timespec *ts)
{
struct timeval tv;
getboottime(&tv);
TIMEVAL_TO_TIMESPEC(&tv, ts);
}
/*
* Call when a discontinuous jump has occured in CLOCK_REALTIME and
* update timerfd's cached boottime. A jump can be triggered using
* functions like clock_settime(2) or settimeofday(2).
*
* Timer is marked TFD_CANCELED if TFD_TIMER_CANCEL_ON_SET is set
* and the realtime clock jumps.
* Timer is marked TFD_ZREAD if TFD_TIMER_CANCEL_ON_SET is not set,
* but the realtime clock jumps backwards.
*/
void
timerfd_jumped(void)
{
struct timerfd *tfd;
struct timespec boottime, diff;
timerfd_getboottime(&boottime);
LIST_FOREACH(tfd, &timerfd_head, entry) {
mtx_lock(&tfd->tfd_lock);
if (tfd->tfd_clockid != CLOCK_REALTIME ||
(tfd->tfd_timflags & TFD_TIMER_ABSTIME) == 0 ||
timespeccmp(&boottime, &tfd->tfd_boottim, ==)) {
mtx_unlock(&tfd->tfd_lock);
continue;
}
if (callout_active(&tfd->tfd_callout)) {
if ((tfd->tfd_timflags & TFD_TIMER_CANCEL_ON_SET) != 0)
tfd->tfd_jumped = TFD_CANCELED;
else if (timespeccmp(&boottime, &tfd->tfd_boottim, <))
tfd->tfd_jumped = TFD_ZREAD;
/*
* Do not reschedule callout when
* inside interval time loop.
*/
if (!tfd->tfd_expired) {
timespecsub(&boottime,
&tfd->tfd_boottim, &diff);
timespecsub(&tfd->tfd_time.it_value,
&diff, &tfd->tfd_time.it_value);
if (callout_stop(&tfd->tfd_callout) == 1) {
callout_schedule_sbt(&tfd->tfd_callout,
tstosbt(tfd->tfd_time.it_value),
0, C_ABSOLUTE);
}
}
}
tfd->tfd_boottim = boottime;
mtx_unlock(&tfd->tfd_lock);
}
}
static int
timerfd_read(struct file *fp, struct uio *uio, struct ucred *active_cred,
int flags, struct thread *td)
{
struct timerfd *tfd = fp->f_data;
timerfd_t count;
int error = 0;
if (uio->uio_resid < sizeof(timerfd_t))
return (EINVAL);
mtx_lock(&tfd->tfd_lock);
retry:
getnanotime(&tfd->tfd_atim);
if ((tfd->tfd_jumped & TFD_JUMPED) != 0) {
if (tfd->tfd_jumped == TFD_CANCELED)
error = ECANCELED;
tfd->tfd_jumped = TFD_READ;
tfd->tfd_count = 0;
mtx_unlock(&tfd->tfd_lock);
return (error);
} else {
tfd->tfd_jumped = TFD_NOJUMP;
}
if (tfd->tfd_count == 0) {
if ((fp->f_flag & FNONBLOCK) != 0) {
mtx_unlock(&tfd->tfd_lock);
return (EAGAIN);
}
td->td_rtcgen = atomic_load_acq_int(&rtc_generation);
error = mtx_sleep(&tfd->tfd_count, &tfd->tfd_lock,
PCATCH, "tfdrd", 0);
if (error == 0) {
goto retry;
} else {
mtx_unlock(&tfd->tfd_lock);
return (error);
}
}
count = tfd->tfd_count;
tfd->tfd_count = 0;
mtx_unlock(&tfd->tfd_lock);
error = uiomove(&count, sizeof(timerfd_t), uio);
return (error);
}
static int
timerfd_ioctl(struct file *fp, u_long cmd, void *data,
struct ucred *active_cred, struct thread *td)
{
switch (cmd) {
case FIOASYNC:
if (*(int *)data != 0)
atomic_set_int(&fp->f_flag, FASYNC);
else
atomic_clear_int(&fp->f_flag, FASYNC);
return (0);
case FIONBIO:
if (*(int *)data != 0)
atomic_set_int(&fp->f_flag, FNONBLOCK);
else
atomic_clear_int(&fp->f_flag, FNONBLOCK);
return (0);
}
return (ENOTTY);
}
static int
timerfd_poll(struct file *fp, int events, struct ucred *active_cred,
struct thread *td)
{
struct timerfd *tfd = fp->f_data;
int revents = 0;
mtx_lock(&tfd->tfd_lock);
if ((events & (POLLIN | POLLRDNORM)) != 0 &&
tfd->tfd_count > 0 && tfd->tfd_jumped != TFD_READ)
revents |= events & (POLLIN | POLLRDNORM);
if (revents == 0)
selrecord(td, &tfd->tfd_sel);
mtx_unlock(&tfd->tfd_lock);
return (revents);
}
static void
filt_timerfddetach(struct knote *kn)
{
struct timerfd *tfd = kn->kn_hook;
mtx_lock(&tfd->tfd_lock);
knlist_remove(&tfd->tfd_sel.si_note, kn, 1);
mtx_unlock(&tfd->tfd_lock);
}
static int
filt_timerfdread(struct knote *kn, long hint)
{
struct timerfd *tfd = kn->kn_hook;
return (tfd->tfd_count > 0);
}
static struct filterops timerfd_rfiltops = {
.f_isfd = 1,
.f_detach = filt_timerfddetach,
.f_event = filt_timerfdread,
};
static int
timerfd_kqfilter(struct file *fp, struct knote *kn)
{
struct timerfd *tfd = fp->f_data;
if (kn->kn_filter != EVFILT_READ)
return (EINVAL);
kn->kn_fop = &timerfd_rfiltops;
kn->kn_hook = tfd;
knlist_add(&tfd->tfd_sel.si_note, kn, 0);
return (0);
}
static int
timerfd_stat(struct file *fp, struct stat *sb, struct ucred *active_cred)
{
struct timerfd *tfd = fp->f_data;
bzero(sb, sizeof(*sb));
sb->st_nlink = fp->f_count - 1;
sb->st_uid = fp->f_cred->cr_uid;
sb->st_gid = fp->f_cred->cr_gid;
sb->st_blksize = PAGE_SIZE;
mtx_lock(&tfd->tfd_lock);
sb->st_ino = tfd->tfd_ino;
sb->st_atim = tfd->tfd_atim;
sb->st_mtim = tfd->tfd_mtim;
sb->st_birthtim = tfd->tfd_birthtim;
mtx_unlock(&tfd->tfd_lock);
return (0);
}
static int
timerfd_close(struct file *fp, struct thread *td)
{
struct timerfd *tfd = fp->f_data;
callout_drain(&tfd->tfd_callout);
seldrain(&tfd->tfd_sel);
knlist_destroy(&tfd->tfd_sel.si_note);
mtx_destroy(&tfd->tfd_lock);
LIST_REMOVE(tfd, entry);
free(tfd, M_TIMERFD);
fp->f_ops = &badfileops;
return (0);
}
static int
timerfd_fill_kinfo(struct file *fp, struct kinfo_file *kif,
struct filedesc *fdp)
{
struct timerfd *tfd = fp->f_data;
kif->kf_type = KF_TYPE_TIMERFD;
mtx_lock(&tfd->tfd_lock);
kif->kf_un.kf_timerfd.kf_timerfd_clockid = tfd->tfd_clockid;
kif->kf_un.kf_timerfd.kf_timerfd_flags = tfd->tfd_flags;
kif->kf_un.kf_timerfd.kf_timerfd_addr = (uintptr_t)tfd;
mtx_unlock(&tfd->tfd_lock);
return (0);
}
static struct fileops timerfdops = {
.fo_read = timerfd_read,
.fo_write = invfo_rdwr,
.fo_truncate = invfo_truncate,
.fo_ioctl = timerfd_ioctl,
.fo_poll = timerfd_poll,
.fo_kqfilter = timerfd_kqfilter,
.fo_stat = timerfd_stat,
.fo_close = timerfd_close,
.fo_chmod = invfo_chmod,
.fo_chown = invfo_chown,
.fo_sendfile = invfo_sendfile,
.fo_fill_kinfo = timerfd_fill_kinfo,
.fo_flags = DFLAG_PASSABLE,
};
static void
timerfd_curval(struct timerfd *tfd, struct itimerspec *old_value)
{
struct timespec curr_value;
*old_value = tfd->tfd_time;
if (timespecisset(&tfd->tfd_time.it_value)) {
nanouptime(&curr_value);
timespecsub(&tfd->tfd_time.it_value, &curr_value,
&old_value->it_value);
}
}
static void
timerfd_expire(void *arg)
{
struct timerfd *tfd = (struct timerfd *)arg;
struct timespec uptime;
++tfd->tfd_count;
tfd->tfd_expired = true;
if (timespecisset(&tfd->tfd_time.it_interval)) {
/* Count missed events. */
nanouptime(&uptime);
if (timespeccmp(&uptime, &tfd->tfd_time.it_value, >)) {
timespecsub(&uptime, &tfd->tfd_time.it_value, &uptime);
tfd->tfd_count += tstosbt(uptime) /
tstosbt(tfd->tfd_time.it_interval);
}
timespecadd(&tfd->tfd_time.it_value,
&tfd->tfd_time.it_interval, &tfd->tfd_time.it_value);
callout_schedule_sbt(&tfd->tfd_callout,
tstosbt(tfd->tfd_time.it_value),
0, C_ABSOLUTE);
} else {
/* Single shot timer. */
callout_deactivate(&tfd->tfd_callout);
timespecclear(&tfd->tfd_time.it_value);
}
wakeup(&tfd->tfd_count);
selwakeup(&tfd->tfd_sel);
KNOTE_LOCKED(&tfd->tfd_sel.si_note, 0);
}
int
kern_timerfd_create(struct thread *td, int clockid, int flags)
{
struct file *fp;
struct timerfd *tfd;
int error, fd, fflags = 0;
AUDIT_ARG_VALUE(clockid);
AUDIT_ARG_FFLAGS(flags);
if (clockid != CLOCK_REALTIME && clockid != CLOCK_MONOTONIC)
return (EINVAL);
if ((flags & ~(TFD_CLOEXEC | TFD_NONBLOCK)) != 0)
return (EINVAL);
if ((flags & TFD_CLOEXEC) != 0)
fflags |= O_CLOEXEC;
tfd = malloc(sizeof(*tfd), M_TIMERFD, M_WAITOK | M_ZERO);
if (tfd == NULL)
return (ENOMEM);
tfd->tfd_clockid = (clockid_t)clockid;
tfd->tfd_flags = flags;
tfd->tfd_ino = alloc_unr64(&tfdino_unr);
mtx_init(&tfd->tfd_lock, "timerfd", NULL, MTX_DEF);
callout_init_mtx(&tfd->tfd_callout, &tfd->tfd_lock, 0);
knlist_init_mtx(&tfd->tfd_sel.si_note, &tfd->tfd_lock);
timerfd_getboottime(&tfd->tfd_boottim);
getnanotime(&tfd->tfd_birthtim);
LIST_INSERT_HEAD(&timerfd_head, tfd, entry);
error = falloc(td, &fp, &fd, fflags);
if (error != 0)
return (error);
fflags = FREAD;
if ((flags & TFD_NONBLOCK) != 0)
fflags |= FNONBLOCK;
finit(fp, fflags, DTYPE_TIMERFD, tfd, &timerfdops);
fdrop(fp, td);
td->td_retval[0] = fd;
return (0);
}
int
kern_timerfd_gettime(struct thread *td, int fd, struct itimerspec *curr_value)
{
struct file *fp;
struct timerfd *tfd;
int error;
error = fget(td, fd, &cap_write_rights, &fp);
if (error != 0)
return (error);
tfd = fp->f_data;
if (tfd == NULL || fp->f_type != DTYPE_TIMERFD) {
fdrop(fp, td);
return (EINVAL);
}
mtx_lock(&tfd->tfd_lock);
timerfd_curval(tfd, curr_value);
mtx_unlock(&tfd->tfd_lock);
fdrop(fp, td);
return (0);
}
int
kern_timerfd_settime(struct thread *td, int fd, int flags,
const struct itimerspec *new_value, struct itimerspec *old_value)
{
struct file *fp;
struct timerfd *tfd;
struct timespec ts;
int error = 0;
if ((flags & ~(TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET)) != 0)
return (EINVAL);
if (!timespecvalid_interval(&new_value->it_value) ||
!timespecvalid_interval(&new_value->it_interval))
return (EINVAL);
error = fget(td, fd, &cap_write_rights, &fp);
if (error != 0)
return (error);
tfd = fp->f_data;
if (tfd == NULL || fp->f_type != DTYPE_TIMERFD) {
fdrop(fp, td);
return (EINVAL);
}
mtx_lock(&tfd->tfd_lock);
getnanotime(&tfd->tfd_mtim);
tfd->tfd_timflags = flags;
/* Store old itimerspec, if applicable. */
if (old_value != NULL)
timerfd_curval(tfd, old_value);
/* Set new expiration. */
tfd->tfd_time = *new_value;
if (timespecisset(&tfd->tfd_time.it_value)) {
if ((flags & TFD_TIMER_ABSTIME) == 0) {
nanouptime(&ts);
timespecadd(&tfd->tfd_time.it_value, &ts,
&tfd->tfd_time.it_value);
} else if (tfd->tfd_clockid == CLOCK_REALTIME) {
/* ECANCELED if unread jump is pending. */
if (tfd->tfd_jumped == TFD_CANCELED)
error = ECANCELED;
/* Convert from CLOCK_REALTIME to CLOCK_BOOTTIME. */
timespecsub(&tfd->tfd_time.it_value, &tfd->tfd_boottim,
&tfd->tfd_time.it_value);
}
callout_reset_sbt(&tfd->tfd_callout,
tstosbt(tfd->tfd_time.it_value),
0, timerfd_expire, tfd, C_ABSOLUTE);
} else {
callout_stop(&tfd->tfd_callout);
}
tfd->tfd_count = 0;
tfd->tfd_expired = false;
tfd->tfd_jumped = TFD_NOJUMP;
mtx_unlock(&tfd->tfd_lock);
fdrop(fp, td);
return (error);
}
int
sys_timerfd_create(struct thread *td, struct timerfd_create_args *uap)
{
return (kern_timerfd_create(td, uap->clockid, uap->flags));
}
int
sys_timerfd_gettime(struct thread *td, struct timerfd_gettime_args *uap)
{
struct itimerspec curr_value;
int error;
error = kern_timerfd_gettime(td, uap->fd, &curr_value);
if (error == 0)
error = copyout(&curr_value, uap->curr_value,
sizeof(curr_value));
return (error);
}
int
sys_timerfd_settime(struct thread *td, struct timerfd_settime_args *uap)
{
struct itimerspec new_value, old_value;
int error;
error = copyin(uap->new_value, &new_value, sizeof(new_value));
if (error != 0)
return (error);
if (uap->old_value == NULL) {
error = kern_timerfd_settime(td, uap->fd, uap->flags,
&new_value, NULL);
} else {
error = kern_timerfd_settime(td, uap->fd, uap->flags,
&new_value, &old_value);
if (error == 0)
error = copyout(&old_value, uap->old_value,
sizeof(old_value));
}
return (error);
}
#ifdef COMPAT_FREEBSD32
int
freebsd32_timerfd_gettime(struct thread *td,
struct freebsd32_timerfd_gettime_args *uap)
{
struct itimerspec curr_value;
struct itimerspec32 curr_value32;
int error;
error = kern_timerfd_gettime(td, uap->fd, &curr_value);
if (error == 0) {
CP(curr_value, curr_value32, it_value.tv_sec);
CP(curr_value, curr_value32, it_value.tv_nsec);
CP(curr_value, curr_value32, it_interval.tv_sec);
CP(curr_value, curr_value32, it_interval.tv_nsec);
error = copyout(&curr_value32, uap->curr_value,
sizeof(curr_value32));
}
return (error);
}
int
freebsd32_timerfd_settime(struct thread *td,
struct freebsd32_timerfd_settime_args *uap)
{
struct itimerspec new_value, old_value;
struct itimerspec32 new_value32, old_value32;
int error;
error = copyin(uap->new_value, &new_value32, sizeof(new_value32));
if (error != 0)
return (error);
CP(new_value32, new_value, it_value.tv_sec);
CP(new_value32, new_value, it_value.tv_nsec);
CP(new_value32, new_value, it_interval.tv_sec);
CP(new_value32, new_value, it_interval.tv_nsec);
if (uap->old_value == NULL) {
error = kern_timerfd_settime(td, uap->fd, uap->flags,
&new_value, NULL);
} else {
error = kern_timerfd_settime(td, uap->fd, uap->flags,
&new_value, &old_value);
if (error == 0) {
CP(old_value, old_value32, it_value.tv_sec);
CP(old_value, old_value32, it_value.tv_nsec);
CP(old_value, old_value32, it_interval.tv_sec);
CP(old_value, old_value32, it_interval.tv_nsec);
error = copyout(&old_value32, uap->old_value,
sizeof(old_value32));
}
}
return (error);
}
#endif

View File

@ -590,4 +590,7 @@ const char *syscallnames[] = {
"swapoff", /* 582 = swapoff */
"kqueuex", /* 583 = kqueuex */
"membarrier", /* 584 = membarrier */
"timerfd_create", /* 585 = timerfd_create */
"timerfd_gettime", /* 586 = timerfd_gettime */
"timerfd_settime", /* 587 = timerfd_settime */
};

View File

@ -3318,6 +3318,26 @@
int cpu_id
);
}
585 AUE_TIMERFD STD|CAPENABLED {
int timerfd_create(
int clockid,
int flags
);
}
586 AUE_TIMERFD STD|CAPENABLED {
int timerfd_gettime(
int fd,
_Out_ _Contains_long_timet_ struct itimerspec *curr_value
);
}
587 AUE_TIMERFD STD|CAPENABLED {
int timerfd_settime(
int fd,
int flags,
_In_ _Contains_long_timet_ const struct itimerspec *new_value,
_Out_opt_ _Contains_long_timet_ struct itimerspec *old_value
);
}
; vim: syntax=off

View File

@ -3432,6 +3432,32 @@ systrace_args(int sysnum, void *params, uint64_t *uarg, int *n_args)
*n_args = 3;
break;
}
/* timerfd_create */
case 585: {
struct timerfd_create_args *p = params;
iarg[a++] = p->clockid; /* int */
iarg[a++] = p->flags; /* int */
*n_args = 2;
break;
}
/* timerfd_gettime */
case 586: {
struct timerfd_gettime_args *p = params;
iarg[a++] = p->fd; /* int */
uarg[a++] = (intptr_t)p->curr_value; /* struct itimerspec * */
*n_args = 2;
break;
}
/* timerfd_settime */
case 587: {
struct timerfd_settime_args *p = params;
iarg[a++] = p->fd; /* int */
iarg[a++] = p->flags; /* int */
uarg[a++] = (intptr_t)p->new_value; /* const struct itimerspec * */
uarg[a++] = (intptr_t)p->old_value; /* struct itimerspec * */
*n_args = 4;
break;
}
default:
*n_args = 0;
break;
@ -9175,6 +9201,51 @@ systrace_entry_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
break;
};
break;
/* timerfd_create */
case 585:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "int";
break;
default:
break;
};
break;
/* timerfd_gettime */
case 586:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "userland struct itimerspec *";
break;
default:
break;
};
break;
/* timerfd_settime */
case 587:
switch (ndx) {
case 0:
p = "int";
break;
case 1:
p = "int";
break;
case 2:
p = "userland const struct itimerspec *";
break;
case 3:
p = "userland struct itimerspec *";
break;
default:
break;
};
break;
default:
break;
};
@ -11138,6 +11209,21 @@ systrace_return_setargdesc(int sysnum, int ndx, char *desc, size_t descsz)
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* timerfd_create */
case 585:
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* timerfd_gettime */
case 586:
if (ndx == 0 || ndx == 1)
p = "int";
break;
/* timerfd_settime */
case 587:
if (ndx == 0 || ndx == 1)
p = "int";
break;
default:
break;
};

View File

@ -69,7 +69,7 @@ struct nameidata;
#define DTYPE_DEV 11 /* Device specific fd type */
#define DTYPE_PROCDESC 12 /* process descriptor */
#define DTYPE_EVENTFD 13 /* eventfd */
#define DTYPE_LINUXTFD 14 /* emulation timerfd type */
#define DTYPE_TIMERFD 14 /* timerfd */
#ifdef _KERNEL

View File

@ -521,4 +521,7 @@
#define SYS_swapoff 582
#define SYS_kqueuex 583
#define SYS_membarrier 584
#define SYS_MAXSYSCALL 585
#define SYS_timerfd_create 585
#define SYS_timerfd_gettime 586
#define SYS_timerfd_settime 587
#define SYS_MAXSYSCALL 588

View File

@ -426,4 +426,7 @@ MIASM = \
sched_getcpu.o \
swapoff.o \
kqueuex.o \
membarrier.o
membarrier.o \
timerfd_create.o \
timerfd_gettime.o \
timerfd_settime.o

View File

@ -1862,6 +1862,20 @@ struct membarrier_args {
char flags_l_[PADL_(unsigned)]; unsigned flags; char flags_r_[PADR_(unsigned)];
char cpu_id_l_[PADL_(int)]; int cpu_id; char cpu_id_r_[PADR_(int)];
};
struct timerfd_create_args {
char clockid_l_[PADL_(int)]; int clockid; char clockid_r_[PADR_(int)];
char flags_l_[PADL_(int)]; int flags; char flags_r_[PADR_(int)];
};
struct timerfd_gettime_args {
char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
char curr_value_l_[PADL_(struct itimerspec *)]; struct itimerspec * curr_value; char curr_value_r_[PADR_(struct itimerspec *)];
};
struct timerfd_settime_args {
char fd_l_[PADL_(int)]; int fd; char fd_r_[PADR_(int)];
char flags_l_[PADL_(int)]; int flags; char flags_r_[PADR_(int)];
char new_value_l_[PADL_(const struct itimerspec *)]; const struct itimerspec * new_value; char new_value_r_[PADR_(const struct itimerspec *)];
char old_value_l_[PADL_(struct itimerspec *)]; struct itimerspec * old_value; char old_value_r_[PADR_(struct itimerspec *)];
};
int sys_exit(struct thread *, struct exit_args *);
int sys_fork(struct thread *, struct fork_args *);
int sys_read(struct thread *, struct read_args *);
@ -2259,6 +2273,9 @@ int sys_sched_getcpu(struct thread *, struct sched_getcpu_args *);
int sys_swapoff(struct thread *, struct swapoff_args *);
int sys_kqueuex(struct thread *, struct kqueuex_args *);
int sys_membarrier(struct thread *, struct membarrier_args *);
int sys_timerfd_create(struct thread *, struct timerfd_create_args *);
int sys_timerfd_gettime(struct thread *, struct timerfd_gettime_args *);
int sys_timerfd_settime(struct thread *, struct timerfd_settime_args *);
#ifdef COMPAT_43
@ -3230,6 +3247,9 @@ int freebsd13_swapoff(struct thread *, struct freebsd13_swapoff_args *);
#define SYS_AUE_swapoff AUE_SWAPOFF
#define SYS_AUE_kqueuex AUE_KQUEUE
#define SYS_AUE_membarrier AUE_NULL
#define SYS_AUE_timerfd_create AUE_TIMERFD
#define SYS_AUE_timerfd_gettime AUE_TIMERFD
#define SYS_AUE_timerfd_settime AUE_TIMERFD
#undef PAD_
#undef PADL_

66
sys/sys/timerfd.h Normal file
View File

@ -0,0 +1,66 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2023 Jake Freeland <jfree@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _SYS_TIMERFD_H_
#define _SYS_TIMERFD_H_
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/proc.h>
#include <sys/timespec.h>
typedef uint64_t timerfd_t;
/* Creation flags. */
#define TFD_NONBLOCK O_NONBLOCK
#define TFD_CLOEXEC O_CLOEXEC
/* Timer flags. */
#define TFD_TIMER_ABSTIME 0x01
#define TFD_TIMER_CANCEL_ON_SET 0x02
#ifndef _KERNEL
__BEGIN_DECLS
int timerfd_create(int clockid, int flags);
int timerfd_gettime(int fd, struct itimerspec *curr_value);
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,
struct itimerspec *old_value);
__END_DECLS
#else /* _KERNEL */
int kern_timerfd_create(struct thread *td, int clockid, int flags);
int kern_timerfd_gettime(struct thread *td, int fd,
struct itimerspec *curr_value);
int kern_timerfd_settime(struct thread *td, int fd, int flags,
const struct itimerspec *new_value, struct itimerspec *old_value);
void timerfd_jumped(void);
#endif /* !_KERNEL */
#endif /* !_SYS_TIMERFD_H_ */

View File

@ -264,6 +264,7 @@ struct user {
#define KF_TYPE_PROCDESC 11
#define KF_TYPE_DEV 12
#define KF_TYPE_EVENTFD 13
#define KF_TYPE_TIMERFD 14
#define KF_TYPE_UNKNOWN 255
#define KF_VTYPE_VNON 0
@ -445,6 +446,11 @@ struct kinfo_file {
uint32_t kf_eventfd_spareint[3];
uint64_t kf_eventfd_addr;
} kf_eventfd;
struct {
uint32_t kf_timerfd_clockid;
uint32_t kf_timerfd_flags;
uint64_t kf_timerfd_addr;
} kf_timerfd;
struct {
uint64_t kf_kqueue_addr;
int32_t kf_kqueue_count;