| /* 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); |
| } |
| |
| 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; |
| subtract_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; |
| } |