diff --git a/lib/libc/sys/Symbol.map b/lib/libc/sys/Symbol.map --- a/lib/libc/sys/Symbol.map +++ b/lib/libc/sys/Symbol.map @@ -421,6 +421,9 @@ fspacectl; kqueuex; swapoff; + timerfd_create; + timerfd_gettime; + timerfd_settime; }; FBSDprivate_1.0 { diff --git a/sys/bsm/audit_kevents.h b/sys/bsm/audit_kevents.h --- a/sys/bsm/audit_kevents.h +++ b/sys/bsm/audit_kevents.h @@ -663,6 +663,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 diff --git a/sys/compat/freebsd32/freebsd32_proto.h b/sys/compat/freebsd32/freebsd32_proto.h --- a/sys/compat/freebsd32/freebsd32_proto.h +++ b/sys/compat/freebsd32/freebsd32_proto.h @@ -685,6 +685,16 @@ 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 *); @@ -800,6 +810,8 @@ 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 @@ -1293,6 +1305,8 @@ #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_ diff --git a/sys/compat/freebsd32/freebsd32_syscall.h b/sys/compat/freebsd32/freebsd32_syscall.h --- a/sys/compat/freebsd32/freebsd32_syscall.h +++ b/sys/compat/freebsd32/freebsd32_syscall.h @@ -502,4 +502,7 @@ #define FREEBSD32_SYS_sched_getcpu 581 #define FREEBSD32_SYS_swapoff 582 #define FREEBSD32_SYS_kqueuex 583 -#define FREEBSD32_SYS_MAXSYSCALL 584 +#define FREEBSD32_SYS_timerfd_create 584 +#define FREEBSD32_SYS_freebsd32_timerfd_gettime 585 +#define FREEBSD32_SYS_freebsd32_timerfd_settime 586 +#define FREEBSD32_SYS_MAXSYSCALL 587 diff --git a/sys/compat/freebsd32/freebsd32_syscalls.c b/sys/compat/freebsd32/freebsd32_syscalls.c --- a/sys/compat/freebsd32/freebsd32_syscalls.c +++ b/sys/compat/freebsd32/freebsd32_syscalls.c @@ -590,4 +590,7 @@ "sched_getcpu", /* 581 = sched_getcpu */ "swapoff", /* 582 = swapoff */ "kqueuex", /* 583 = kqueuex */ + "timerfd_create", /* 584 = timerfd_create */ + "freebsd32_timerfd_gettime", /* 585 = freebsd32_timerfd_gettime */ + "freebsd32_timerfd_settime", /* 586 = freebsd32_timerfd_settime */ }; diff --git a/sys/compat/freebsd32/freebsd32_sysent.c b/sys/compat/freebsd32/freebsd32_sysent.c --- a/sys/compat/freebsd32/freebsd32_sysent.c +++ b/sys/compat/freebsd32/freebsd32_sysent.c @@ -646,4 +646,7 @@ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_sched_getcpu, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 581 = sched_getcpu */ { .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(timerfd_create_args), .sy_call = (sy_call_t *)sys_timerfd_create, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = 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 }, /* 585 = 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 }, /* 586 = freebsd32_timerfd_settime */ }; diff --git a/sys/compat/freebsd32/freebsd32_systrace_args.c b/sys/compat/freebsd32/freebsd32_systrace_args.c --- a/sys/compat/freebsd32/freebsd32_systrace_args.c +++ b/sys/compat/freebsd32/freebsd32_systrace_args.c @@ -3328,6 +3328,32 @@ *n_args = 1; break; } + /* timerfd_create */ + case 584: { + struct timerfd_create_args *p = params; + iarg[a++] = p->clockid; /* int */ + iarg[a++] = p->flags; /* int */ + *n_args = 2; + break; + } + /* freebsd32_timerfd_gettime */ + case 585: { + 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 586: { + 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; @@ -8981,6 +9007,51 @@ break; }; break; + /* timerfd_create */ + case 584: + switch (ndx) { + case 0: + p = "int"; + break; + case 1: + p = "int"; + break; + default: + break; + }; + break; + /* freebsd32_timerfd_gettime */ + case 585: + switch (ndx) { + case 0: + p = "int"; + break; + case 1: + p = "userland struct itimerspec32 *"; + break; + default: + break; + }; + break; + /* freebsd32_timerfd_settime */ + case 586: + 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; }; @@ -10844,6 +10915,21 @@ if (ndx == 0 || ndx == 1) p = "int"; break; + /* timerfd_create */ + case 584: + if (ndx == 0 || ndx == 1) + p = "int"; + break; + /* freebsd32_timerfd_gettime */ + case 585: + if (ndx == 0 || ndx == 1) + p = "int"; + break; + /* freebsd32_timerfd_settime */ + case 586: + if (ndx == 0 || ndx == 1) + p = "int"; + break; default: break; }; diff --git a/sys/compat/linux/linux_event.h b/sys/compat/linux/linux_event.h --- a/sys/compat/linux/linux_event.h +++ b/sys/compat/linux/linux_event.h @@ -56,15 +56,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_ */ diff --git a/sys/compat/linux/linux_event.c b/sys/compat/linux/linux_event.c --- a/sys/compat/linux/linux_event.c +++ b/sys/compat/linux/linux_event.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -102,55 +103,6 @@ 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) { @@ -661,255 +613,14 @@ 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); - - 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); + error = linux_to_native_clockid(&clockid, args->clockid); 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 @@ -919,84 +630,14 @@ 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); -} - -#if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) -int -linux_timerfd_gettime64(struct thread *td, struct linux_timerfd_gettime64_args *args) -{ - struct l_itimerspec64 lots; - struct itimerspec ots; - int error; - error = linux_timerfd_gettime_common(td, args->fd, &ots); - if (error != 0) - return (error); - error = native_to_linux_itimerspec64(&lots, &ots); + error = native_to_linux_itimerspec(&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); } @@ -1004,7 +645,7 @@ linux_timerfd_settime(struct thread *td, struct linux_timerfd_settime_args *args) { struct l_itimerspec lots; - struct itimerspec nts, ots, *pots; + struct itimerspec nts, ots; int error; error = copyin(args->new_value, &lots, sizeof(lots)); @@ -1013,23 +654,43 @@ 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 (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); } #if defined(__i386__) || (defined(__amd64__) && defined(COMPAT_LINUX32)) +int +linux_timerfd_gettime64(struct thread *td, struct linux_timerfd_gettime64_args *args) +{ + struct l_itimerspec64 lots; + struct itimerspec ots; + int error; + + 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); +} + 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)); @@ -1038,50 +699,16 @@ 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); - } -} diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -3913,6 +3913,7 @@ 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 diff --git a/sys/kern/init_sysent.c b/sys/kern/init_sysent.c --- a/sys/kern/init_sysent.c +++ b/sys/kern/init_sysent.c @@ -645,4 +645,7 @@ { .sy_narg = 0, .sy_call = (sy_call_t *)sys_sched_getcpu, .sy_auevent = AUE_NULL, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 581 = sched_getcpu */ { .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(timerfd_create_args), .sy_call = (sy_call_t *)sys_timerfd_create, .sy_auevent = AUE_TIMERFD, .sy_flags = SYF_CAPENABLED, .sy_thrcnt = SY_THR_STATIC }, /* 584 = 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 }, /* 585 = 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 }, /* 586 = timerfd_settime */ }; diff --git a/sys/kern/kern_descrip.c b/sys/kern/kern_descrip.c --- a/sys/kern/kern_descrip.c +++ b/sys/kern/kern_descrip.c @@ -4939,8 +4939,8 @@ return ("proc"); case DTYPE_EVENTFD: return ("eventfd"); - case DTYPE_LINUXTFD: - return ("ltimer"); + case DTYPE_TIMERFD: + return ("timerfd"); default: return ("unkn"); } diff --git a/sys/kern/kern_tc.c b/sys/kern/kern_tc.c --- a/sys/kern/kern_tc.c +++ b/sys/kern/kern_tc.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -1307,6 +1308,7 @@ /* 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); diff --git a/sys/kern/sys_timerfd.c b/sys/kern/sys_timerfd.c new file mode 100644 --- /dev/null +++ b/sys/kern/sys_timerfd.c @@ -0,0 +1,631 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2014 Dmitry Chagin + * Copyright (c) 2023 Jake Freeland + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef COMPAT_FREEBSD32 +#include +#include +#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 diff --git a/sys/kern/syscalls.c b/sys/kern/syscalls.c --- a/sys/kern/syscalls.c +++ b/sys/kern/syscalls.c @@ -590,4 +590,7 @@ "sched_getcpu", /* 581 = sched_getcpu */ "swapoff", /* 582 = swapoff */ "kqueuex", /* 583 = kqueuex */ + "timerfd_create", /* 584 = timerfd_create */ + "timerfd_gettime", /* 585 = timerfd_gettime */ + "timerfd_settime", /* 586 = timerfd_settime */ }; diff --git a/sys/kern/syscalls.master b/sys/kern/syscalls.master --- a/sys/kern/syscalls.master +++ b/sys/kern/syscalls.master @@ -3313,5 +3313,25 @@ u_int flags ); } +584 AUE_TIMERFD STD|CAPENABLED { + int timerfd_create( + int clockid, + int flags + ); + } +585 AUE_TIMERFD STD|CAPENABLED { + int timerfd_gettime( + int fd, + _Out_ _Contains_long_timet_ struct itimerspec *curr_value + ); + } +586 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 diff --git a/sys/kern/systrace_args.c b/sys/kern/systrace_args.c --- a/sys/kern/systrace_args.c +++ b/sys/kern/systrace_args.c @@ -3424,6 +3424,32 @@ *n_args = 1; break; } + /* timerfd_create */ + case 584: { + struct timerfd_create_args *p = params; + iarg[a++] = p->clockid; /* int */ + iarg[a++] = p->flags; /* int */ + *n_args = 2; + break; + } + /* timerfd_gettime */ + case 585: { + 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 586: { + 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; @@ -9151,6 +9177,51 @@ break; }; break; + /* timerfd_create */ + case 584: + switch (ndx) { + case 0: + p = "int"; + break; + case 1: + p = "int"; + break; + default: + break; + }; + break; + /* timerfd_gettime */ + case 585: + switch (ndx) { + case 0: + p = "int"; + break; + case 1: + p = "userland struct itimerspec *"; + break; + default: + break; + }; + break; + /* timerfd_settime */ + case 586: + 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; }; @@ -11109,6 +11180,21 @@ if (ndx == 0 || ndx == 1) p = "int"; break; + /* timerfd_create */ + case 584: + if (ndx == 0 || ndx == 1) + p = "int"; + break; + /* timerfd_gettime */ + case 585: + if (ndx == 0 || ndx == 1) + p = "int"; + break; + /* timerfd_settime */ + case 586: + if (ndx == 0 || ndx == 1) + p = "int"; + break; default: break; }; diff --git a/sys/sys/file.h b/sys/sys/file.h --- a/sys/sys/file.h +++ b/sys/sys/file.h @@ -70,7 +70,7 @@ #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 diff --git a/sys/sys/syscall.h b/sys/sys/syscall.h --- a/sys/sys/syscall.h +++ b/sys/sys/syscall.h @@ -521,4 +521,7 @@ #define SYS_sched_getcpu 581 #define SYS_swapoff 582 #define SYS_kqueuex 583 -#define SYS_MAXSYSCALL 584 +#define SYS_timerfd_create 584 +#define SYS_timerfd_gettime 585 +#define SYS_timerfd_settime 586 +#define SYS_MAXSYSCALL 587 diff --git a/sys/sys/syscall.mk b/sys/sys/syscall.mk --- a/sys/sys/syscall.mk +++ b/sys/sys/syscall.mk @@ -426,4 +426,7 @@ fspacectl.o \ sched_getcpu.o \ swapoff.o \ - kqueuex.o + kqueuex.o \ + timerfd_create.o \ + timerfd_gettime.o \ + timerfd_settime.o diff --git a/sys/sys/sysproto.h b/sys/sys/sysproto.h --- a/sys/sys/sysproto.h +++ b/sys/sys/sysproto.h @@ -1858,6 +1858,20 @@ struct kqueuex_args { char flags_l_[PADL_(u_int)]; u_int flags; char flags_r_[PADR_(u_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 *); @@ -2254,6 +2268,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_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 @@ -3224,6 +3241,9 @@ #define SYS_AUE_sched_getcpu AUE_NULL #define SYS_AUE_swapoff AUE_SWAPOFF #define SYS_AUE_kqueuex AUE_KQUEUE +#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_ diff --git a/sys/sys/timerfd.h b/sys/sys/timerfd.h new file mode 100644 --- /dev/null +++ b/sys/sys/timerfd.h @@ -0,0 +1,66 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Jake Freeland + * + * 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 +#include +#include +#include + +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_ */ diff --git a/sys/sys/user.h b/sys/sys/user.h --- a/sys/sys/user.h +++ b/sys/sys/user.h @@ -265,6 +265,7 @@ #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 @@ -446,6 +447,11 @@ 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;