Implement timerfd on top of #alarm (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Fri, 8 Apr 2016 16:38:47 +0000 (12:38 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Fri, 15 Apr 2016 14:29:13 +0000 (10:29 -0400)
It mostly matches.  It is a little unfortunate that read() expects
host-endian format for the u64.

Rebuild glibc.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Makefile
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/Versions
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/bits/timerfd.h [new file with mode: 0644]
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/sys/timerfd.h [new file with mode: 0644]
tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/timerfd.c [new file with mode: 0644]

index 7bb5608..fb7c1a5 100644 (file)
@@ -77,6 +77,12 @@ sysdep_routines += eventfd
 endif
 sysdep_headers += sys/eventfd.h bits/eventfd.h
 
+# Timerfd, implemented in glibc
+ifeq ($(subdir),stdlib)
+sysdep_routines += timerfd
+endif
+sysdep_headers += sys/timerfd.h bits/timerfd.h
+
 # time.h, override for struct timespec.  This overrides time/time.h from glibc,
 # installed as usr/inc/time.h.
 #
index 28c657e..b0e8e32 100644 (file)
@@ -63,6 +63,10 @@ libc {
     eventfd_read;
     eventfd_write;
 
+    timerfd_create;
+    timerfd_settime;
+    timerfd_gettime;
+
     # Weak symbols in parlib-compat.c
     __vcoreid;
     __vcore_context;
diff --git a/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/bits/timerfd.h b/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/bits/timerfd.h
new file mode 100644 (file)
index 0000000..4957b59
--- /dev/null
@@ -0,0 +1,29 @@
+/* Copyright (C) 2008-2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef        _SYS_TIMERFD_H
+# error "Never use <bits/timerfd.h> directly; include <sys/timerfd.h> instead."
+#endif
+
+/* Bits to be set in the FLAGS parameter of `timerfd_create'.  */
+enum
+  {
+    TFD_CLOEXEC = 02000000,
+#define TFD_CLOEXEC TFD_CLOEXEC
+    TFD_NONBLOCK = 00004000
+#define TFD_NONBLOCK TFD_NONBLOCK
+  };
diff --git a/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/sys/timerfd.h b/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/sys/timerfd.h
new file mode 100644 (file)
index 0000000..c28f311
--- /dev/null
@@ -0,0 +1,52 @@
+/* Copyright (C) 2008-2014 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#ifndef        _SYS_TIMERFD_H
+#define        _SYS_TIMERFD_H  1
+
+#include <time.h>
+
+/* Get the platform-dependent flags.  */
+#include <bits/timerfd.h>
+
+
+/* Bits to be set in the FLAGS parameter of `timerfd_settime'.  */
+enum
+  {
+    TFD_TIMER_ABSTIME = 1 << 0
+#define TFD_TIMER_ABSTIME TFD_TIMER_ABSTIME
+  };
+
+
+__BEGIN_DECLS
+
+/* Return file descriptor for new interval timer source.  */
+extern int timerfd_create (clockid_t __clock_id, int __flags) __THROW;
+
+/* Set next expiration time of interval timer source UFD to UTMR.  If
+   FLAGS has the TFD_TIMER_ABSTIME flag set the timeout value is
+   absolute.  Optionally return the old expiration time in OTMR.  */
+extern int timerfd_settime (int __ufd, int __flags,
+                           const struct itimerspec *__utmr,
+                           struct itimerspec *__otmr) __THROW;
+
+/* Return the next expiration time of UFD.  */
+extern int timerfd_gettime (int __ufd, struct itimerspec *__otmr) __THROW;
+
+__END_DECLS
+
+#endif /* sys/timerfd.h */
diff --git a/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/timerfd.c b/tools/compilers/gcc-glibc/glibc-2.19-akaros/sysdeps/akaros/timerfd.c
new file mode 100644 (file)
index 0000000..b467a36
--- /dev/null
@@ -0,0 +1,208 @@
+/* Copyright (c) 2016 Google Inc.
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details.
+ *
+ * Implementation of glibc's timerfd interface on top of #alarm.
+ *
+ * Like sockets, timerfd is really an alarm directory under the hood, but the
+ * user gets a single FD that they will read and epoll on.  This FD will be for
+ * the 'count' file.  Other operations will require opening other FDs, given the
+ * 'count' fd.  This is basically the Rock lookup problem. */
+
+#include <sys/timerfd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syscall.h>
+#include <string.h>
+#include <parlib/timing.h>
+#include <time.h>
+#include <sys/plan9_helpers.h>
+
+int timerfd_create(int clockid, int flags)
+{
+       int ctlfd, countfd, ret;
+       char id[20];
+       char path[MAX_PATH_LEN];
+       int count_oflags = O_RDWR;
+
+       ctlfd = open("#alarm/clone", O_RDWR);
+       if (ctlfd < 0)
+               return -1;
+       /* TODO: if we want to support clocks like CLOCK_REALTIME, do it here,
+        * either at attach time (with a .spec), or with a ctl message. */
+       /* fd2path doesn't work on cloned files, so open the count manually. */
+       ret = read(ctlfd, id, sizeof(id) - 1);
+       if (ret <= 0)
+               return -1;
+       id[ret] = 0;
+       snprintf(path, sizeof(path), "#alarm/a%s/count", id);
+       count_oflags |= (flags & TFD_NONBLOCK ? O_NONBLOCK : 0);
+       count_oflags |= (flags & TFD_CLOEXEC ? O_CLOEXEC : 0);
+       countfd = open(path, count_oflags);
+       close(ctlfd);
+       return countfd;
+}
+
+static int set_period(int periodfd, uint64_t period)
+{
+       return write_hex_to_fd(periodfd, period);
+}
+
+static int set_timer(int timerfd, uint64_t abs_ticks)
+{
+       return write_hex_to_fd(timerfd, abs_ticks);
+}
+
+/* Helper: adds normal timespecs */
+static void add_timespecs(struct timespec *sum, const struct timespec *x,
+                          const struct timespec *y)
+{
+       bool plus_one = FALSE;
+
+       sum->tv_nsec = x->tv_nsec + y->tv_nsec;
+       /* Overflow detection */
+       if (sum->tv_nsec / 1000000000) {
+               sum->tv_nsec -= 1000000000;
+               plus_one = TRUE;
+       }
+       sum->tv_sec = x->tv_sec + y->tv_sec + (plus_one ? 1 : 0);
+}
+
+/* Helper: subtracts normal timespecs */
+static void sub_timespecs(struct timespec *diff, const struct timespec *minuend,
+                          const struct timespec *subtrahend)
+{
+       unsigned long borrow_amt = 0;
+
+       if (minuend->tv_nsec < subtrahend->tv_nsec)
+               borrow_amt = 1000000000;
+       diff->tv_nsec = borrow_amt + minuend->tv_nsec - subtrahend->tv_nsec;
+       diff->tv_sec = minuend->tv_sec - subtrahend->tv_sec - (borrow_amt ? 1 : 0);
+}
+
+static uint64_t timespec2tsc(const struct timespec *ts)
+{
+       return nsec2tsc(ts->tv_sec * 1000000000ULL + ts->tv_nsec);
+}
+
+static void tsc2timespec(uint64_t tsc, struct timespec *ts)
+{
+       uint64_t nsec = tsc2nsec(tsc);
+
+       ts->tv_sec = nsec / 1000000000;
+       ts->tv_nsec = nsec % 1000000000;
+}
+
+static int __timerfd_gettime(int timerfd, int periodfd,
+                             struct itimerspec *curr_value)
+{
+       char buf[20];
+       uint64_t timer_tsc, now_tsc, period_tsc;
+
+       if (read(periodfd, buf, sizeof(buf) <= 0))
+               return -1;
+       period_tsc = strtoul(buf, 0, 0);
+       tsc2timespec(period_tsc, &curr_value->it_interval);
+       if (read(timerfd, buf, sizeof(buf) <= 0))
+               return -1;
+       timer_tsc = strtoul(buf, 0, 0);
+       /* If 0 (disabled), we'll return 0 for 'it_value'.  o/w we need to return
+        * the relative time. */
+       if (timer_tsc) {
+               now_tsc = read_tsc();
+               if (timer_tsc > now_tsc) {
+                       timer_tsc -= now_tsc;
+               } else {
+                       /* it's possible that timer_tsc is in the past, and that we lost the
+                        * race.  The alarm fired since we looked at it, and it might be
+                        * disabled.  It might have fired multiple times too. */
+                       if (!period_tsc) {
+                               /* if there was no period and the alarm fired, then it should be
+                                * disabled.  This is racy, if there are other people setting
+                                * the timer. */
+                               timer_tsc = 0;
+                       } else {
+                               while (timer_tsc < now_tsc)
+                                       timer_tsc += period_tsc;
+                       }
+               }
+       }
+       tsc2timespec(timer_tsc, &curr_value->it_value);
+       return 0;
+}
+
+int timerfd_settime(int fd, int flags,
+                    const struct itimerspec *new_value,
+                    struct itimerspec *old_value)
+{
+       int timerfd, periodfd;
+       int ret;
+       uint64_t period;
+       struct timespec now_timespec = {0};
+       struct timespec rel_timespec;
+
+       timerfd = get_sibling_fd(fd, "timer");
+       if (timerfd < 0)
+               return -1;
+       periodfd = get_sibling_fd(fd, "period");
+       if (periodfd < 0) {
+               close(timerfd);
+               return -1;
+       }
+       if (old_value) {
+               if (__timerfd_gettime(timerfd, periodfd, old_value)) {
+                       ret = -1;
+                       goto out;
+               }
+       }
+       if (!new_value->it_value.tv_sec && !new_value->it_value.tv_nsec) {
+               ret = set_timer(timerfd, 0);
+               goto out;
+       }
+       period = timespec2tsc(&new_value->it_interval);
+       ret = set_period(periodfd, period);
+       if (ret < 0)
+               goto out;
+       /* So the caller is asking for timespecs in wall-clock time (depending on
+        * the clock, actually, (TODO)), and the kernel expects TSC ticks from boot.
+        * If !ABSTIME, then it's just relative to now.  If it is ABSTIME, then they
+        * are asking in terms of real-world time, which means ABS - NOW to get the
+        * rel time, then convert to tsc ticks. */
+       if (flags & TFD_TIMER_ABSTIME) {
+               ret = clock_gettime(CLOCK_MONOTONIC, &now_timespec);
+               if (ret < 0)
+                       goto out;
+               sub_timespecs(&rel_timespec, &new_value->it_value, &now_timespec);
+       } else {
+               rel_timespec = new_value->it_value;
+       }
+       ret = set_timer(timerfd, timespec2tsc(&rel_timespec) + read_tsc());
+       /* fall-through */
+out:
+       close(timerfd);
+       close(periodfd);
+       return ret;
+}
+
+int timerfd_gettime(int fd, struct itimerspec *curr_value)
+{
+       int timerfd, periodfd;
+       int ret;
+
+       timerfd = get_sibling_fd(fd, "timer");
+       if (timerfd < 0)
+               return -1;
+       periodfd = get_sibling_fd(fd, "period");
+       if (periodfd < 0) {
+               close(timerfd);
+               return -1;
+       }
+       ret = __timerfd_gettime(timerfd, periodfd, curr_value);
+       close(timerfd);
+       close(periodfd);
+       return ret;
+}