Author: dchagin
Date: Sun Feb 26 09:48:18 2017
New Revision: 314295
URL: https://svnweb.freebsd.org/changeset/base/314295

Log:
  Implement timerfd family syscalls.
  
  MFC after:    1 month

Modified:
  head/sys/amd64/linux/linux_dummy.c
  head/sys/amd64/linux32/linux32_dummy.c
  head/sys/compat/linux/linux_event.c
  head/sys/compat/linux/linux_event.h
  head/sys/compat/linux/linux_time.c
  head/sys/compat/linux/linux_timer.h
  head/sys/i386/linux/linux_dummy.c
  head/sys/sys/file.h

Modified: head/sys/amd64/linux/linux_dummy.c
==============================================================================
--- head/sys/amd64/linux/linux_dummy.c  Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/amd64/linux/linux_dummy.c  Sun Feb 26 09:48:18 2017        
(r314295)
@@ -103,10 +103,6 @@ DUMMY(vmsplice);
 DUMMY(move_pages);
 /* linux 2.6.22: */
 DUMMY(signalfd);
-DUMMY(timerfd_create);
-/* linux 2.6.25: */
-DUMMY(timerfd_settime);
-DUMMY(timerfd_gettime);
 /* linux 2.6.27: */
 DUMMY(signalfd4);
 DUMMY(inotify_init1);

Modified: head/sys/amd64/linux32/linux32_dummy.c
==============================================================================
--- head/sys/amd64/linux32/linux32_dummy.c      Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/amd64/linux32/linux32_dummy.c      Sun Feb 26 09:48:18 2017        
(r314295)
@@ -103,10 +103,6 @@ DUMMY(move_pages);
 DUMMY(getcpu);
 /* linux 2.6.22: */
 DUMMY(signalfd);
-DUMMY(timerfd_create);
-/* linux 2.6.25: */
-DUMMY(timerfd_settime);
-DUMMY(timerfd_gettime);
 /* linux 2.6.27: */
 DUMMY(signalfd4);
 DUMMY(inotify_init1);

Modified: head/sys/compat/linux/linux_event.c
==============================================================================
--- head/sys/compat/linux/linux_event.c Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/compat/linux/linux_event.c Sun Feb 26 09:48:18 2017        
(r314295)
@@ -37,6 +37,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/limits.h>
 #include <sys/lock.h>
 #include <sys/mutex.h>
+#include <sys/callout.h>
 #include <sys/capsicum.h>
 #include <sys/types.h>
 #include <sys/user.h>
@@ -63,6 +64,7 @@ __FBSDID("$FreeBSD$");
 #include <compat/linux/linux_emul.h>
 #include <compat/linux/linux_event.h>
 #include <compat/linux/linux_file.h>
+#include <compat/linux/linux_timer.h>
 #include <compat/linux/linux_util.h>
 
 /*
@@ -161,6 +163,41 @@ static struct filterops eventfd_wfiltops
        .f_event = filt_eventfdwrite
 };
 
+/* timerfd */
+typedef uint64_t       timerfd_t;
+
+static fo_rdwr_t       timerfd_read;
+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 = invfo_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 eventfd {
        eventfd_t       efd_count;
        uint32_t        efd_flags;
@@ -168,7 +205,19 @@ struct eventfd {
        struct mtx      efd_lock;
 };
 
+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 int     eventfd_create(struct thread *td, uint32_t initval, int flags);
+static void    linux_timerfd_expire(void *);
+static void    linux_timerfd_curval(struct timerfd *, struct itimerspec *);
 
 
 static void
@@ -901,3 +950,360 @@ eventfd_fill_kinfo(struct file *fp, stru
        kif->kf_type = KF_TYPE_UNKNOWN;
        return (0);
 }
+
+int
+linux_timerfd_create(struct thread *td, struct linux_timerfd_create_args *args)
+{
+       struct filedesc *fdp;
+       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;
+
+       fdp = td->td_proc->p_fd;
+       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);
+
+       mtx_lock(&tfd->tfd_lock);
+       callout_drain(&tfd->tfd_callout);
+       mtx_unlock(&tfd->tfd_lock);
+
+       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);
+}
+
+/*ARGSUSED*/
+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);
+}
+
+/*ARGSUSED*/
+static int
+filt_timerfdread(struct knote *kn, long hint)
+{
+       struct timerfd *tfd = kn->kn_hook;
+
+       return (tfd->tfd_count > 0);
+}
+
+/*ARGSUSED*/
+static int
+timerfd_stat(struct file *fp, struct stat *st, struct ucred *active_cred,
+    struct thread *td)
+{
+
+       return (ENXIO);
+}
+
+/*ARGSUSED*/
+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);
+               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;
+               }
+       }
+}
+
+int
+linux_timerfd_gettime(struct thread *td, struct linux_timerfd_gettime_args 
*args)
+{
+       cap_rights_t rights;
+       struct l_itimerspec lots;
+       struct itimerspec ots;
+       struct timerfd *tfd;
+       struct file *fp;
+       int error;
+
+       error = fget(td, args->fd, cap_rights_init(&rights, CAP_READ), &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);
+
+       error = native_to_linux_itimerspec(&lots, &ots);
+       if (error == 0)
+               error = copyout(&lots, args->old_value, sizeof(lots));
+
+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;
+       struct timespec cts, ts;
+       cap_rights_t rights;
+       struct timerfd *tfd;
+       struct timeval tv;
+       struct file *fp;
+       int error;
+
+       if ((args->flags & ~LINUX_TFD_SETTIME_FLAGS) != 0)
+               return (EINVAL);
+
+       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);
+
+       error = fget(td, args->fd, cap_rights_init(&rights, CAP_WRITE), &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 (args->old_value != NULL)
+               linux_timerfd_curval(tfd, &ots);
+
+       tfd->tfd_time = nts;
+       if (timespecisset(&nts.it_value)) {
+               linux_timerfd_clocktime(tfd, &cts);
+               ts = nts.it_value;
+               if ((args->flags & LINUX_TFD_TIMER_ABSTIME) == 0) {
+                       timespecadd(&tfd->tfd_time.it_value, &cts);
+               } else {
+                       timespecsub(&ts, &cts);
+               }
+               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);
+
+       if (args->old_value != NULL) {
+               error = native_to_linux_itimerspec(&lots, &ots);
+               if (error == 0)
+                       error = copyout(&lots, args->old_value, sizeof(lots));
+       }
+
+out:
+       fdrop(fp, td);
+       return (error);
+}
+
+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);
+               else
+                       /* single shot timer */
+                       timespecclear(&tfd->tfd_time.it_value);
+               if (timespecisset(&tfd->tfd_time.it_value)) {
+                       ts = tfd->tfd_time.it_value;
+                       timespecsub(&ts, &cts);
+                       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)) {
+               ts = tfd->tfd_time.it_value;
+               timespecsub(&ts, &cts);
+               TIMESPEC_TO_TIMEVAL(&tv, &ts);
+               callout_reset(&tfd->tfd_callout, tvtohz(&tv),
+                   linux_timerfd_expire, tfd);
+       }
+}

Modified: head/sys/compat/linux/linux_event.h
==============================================================================
--- head/sys/compat/linux/linux_event.h Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/compat/linux/linux_event.h Sun Feb 26 09:48:18 2017        
(r314295)
@@ -57,4 +57,15 @@
 
 #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_ */

Modified: head/sys/compat/linux/linux_time.c
==============================================================================
--- head/sys/compat/linux/linux_time.c  Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/compat/linux/linux_time.c  Sun Feb 26 09:48:18 2017        
(r314295)
@@ -154,6 +154,28 @@ linux_to_native_timespec(struct timespec
 }
 
 int
+native_to_linux_itimerspec(struct l_itimerspec *ltp, struct itimerspec *ntp)
+{
+       int error;
+
+       error = native_to_linux_timespec(&ltp->it_interval, &ntp->it_interval);
+       if (error == 0)
+               error = native_to_linux_timespec(&ltp->it_value, 
&ntp->it_interval);
+       return (error);
+}
+
+int
+linux_to_native_itimerspec(struct itimerspec *ntp, struct l_itimerspec *ltp)
+{
+       int error;
+
+       error = linux_to_native_timespec(&ntp->it_interval, &ltp->it_interval);
+       if (error == 0)
+               error = linux_to_native_timespec(&ntp->it_value, 
&ltp->it_value);
+       return (error);
+}
+
+int
 linux_to_native_clockid(clockid_t *n, clockid_t l)
 {
 

Modified: head/sys/compat/linux/linux_timer.h
==============================================================================
--- head/sys/compat/linux/linux_timer.h Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/compat/linux/linux_timer.h Sun Feb 26 09:48:18 2017        
(r314295)
@@ -116,5 +116,9 @@ int native_to_linux_timespec(struct l_ti
 int linux_to_native_timespec(struct timespec *,
                                     struct l_timespec *);
 int linux_to_native_clockid(clockid_t *, clockid_t);
+int native_to_linux_itimerspec(struct l_itimerspec *,
+                                    struct itimerspec *);
+int linux_to_native_itimerspec(struct itimerspec *,
+                                    struct l_itimerspec *);
 
 #endif /* _LINUX_TIMER_H */

Modified: head/sys/i386/linux/linux_dummy.c
==============================================================================
--- head/sys/i386/linux/linux_dummy.c   Sun Feb 26 09:42:34 2017        
(r314294)
+++ head/sys/i386/linux/linux_dummy.c   Sun Feb 26 09:48:18 2017        
(r314295)
@@ -99,10 +99,6 @@ DUMMY(move_pages);
 DUMMY(getcpu);
 /* linux 2.6.22: */
 DUMMY(signalfd);
-DUMMY(timerfd_create);
-/* linux 2.6.25: */
-DUMMY(timerfd_settime);
-DUMMY(timerfd_gettime);
 /* linux 2.6.27: */
 DUMMY(signalfd4);
 DUMMY(inotify_init1);

Modified: head/sys/sys/file.h
==============================================================================
--- head/sys/sys/file.h Sun Feb 26 09:42:34 2017        (r314294)
+++ head/sys/sys/file.h Sun Feb 26 09:48:18 2017        (r314295)
@@ -67,6 +67,7 @@ struct vnode;
 #define        DTYPE_DEV       11      /* Device specific fd type */
 #define        DTYPE_PROCDESC  12      /* process descriptor */
 #define        DTYPE_LINUXEFD  13      /* emulation eventfd type */
+#define        DTYPE_LINUXTFD  14      /* emulation timerfd type */
 
 #ifdef _KERNEL
 
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to