Stop using snprintf in write_hex_to_fd (XCC)
[akaros.git] / tools / compilers / gcc-glibc / glibc-2.19-akaros / sysdeps / akaros / timerfd.c
1 /* Copyright (c) 2016 Google Inc.
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Implementation of glibc's timerfd interface on top of #alarm.
6  *
7  * Like sockets, timerfd is really an alarm directory under the hood, but the
8  * user gets a single FD that they will read and epoll on.  This FD will be for
9  * the 'count' file.  Other operations will require opening other FDs, given the
10  * 'count' fd.  This is basically the Rock lookup problem. */
11
12 #include <sys/timerfd.h>
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <fcntl.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <unistd.h>
19 #include <syscall.h>
20 #include <string.h>
21 #include <parlib/timing.h>
22 #include <time.h>
23 #include <sys/plan9_helpers.h>
24
25 int timerfd_create(int clockid, int flags)
26 {
27         int ctlfd, countfd, ret;
28         char id[20];
29         char path[MAX_PATH_LEN];
30         int count_oflags = O_RDWR;
31
32         ctlfd = open("#alarm/clone", O_RDWR);
33         if (ctlfd < 0)
34                 return -1;
35         /* TODO: if we want to support clocks like CLOCK_REALTIME, do it here,
36          * either at attach time (with a .spec), or with a ctl message. */
37         /* fd2path doesn't work on cloned files, so open the count manually. */
38         ret = read(ctlfd, id, sizeof(id) - 1);
39         if (ret <= 0)
40                 return -1;
41         id[ret] = 0;
42         snprintf(path, sizeof(path), "#alarm/a%s/count", id);
43         count_oflags |= (flags & TFD_NONBLOCK ? O_NONBLOCK : 0);
44         count_oflags |= (flags & TFD_CLOEXEC ? O_CLOEXEC : 0);
45         countfd = open(path, count_oflags);
46         close(ctlfd);
47         return countfd;
48 }
49
50 static int set_period(int periodfd, uint64_t period)
51 {
52         return write_hex_to_fd(periodfd, period);
53 }
54
55 static int set_timer(int timerfd, uint64_t abs_ticks)
56 {
57         return write_hex_to_fd(timerfd, abs_ticks);
58 }
59
60 static uint64_t timespec2tsc(const struct timespec *ts)
61 {
62         return nsec2tsc(ts->tv_sec * 1000000000ULL + ts->tv_nsec);
63 }
64
65 static void tsc2timespec(uint64_t tsc, struct timespec *ts)
66 {
67         uint64_t nsec = tsc2nsec(tsc);
68
69         ts->tv_sec = nsec / 1000000000;
70         ts->tv_nsec = nsec % 1000000000;
71 }
72
73 static int __timerfd_gettime(int timerfd, int periodfd,
74                              struct itimerspec *curr_value)
75 {
76         char buf[20];
77         uint64_t timer_tsc, now_tsc, period_tsc;
78
79         if (read(periodfd, buf, sizeof(buf) <= 0))
80                 return -1;
81         period_tsc = strtoul(buf, 0, 0);
82         tsc2timespec(period_tsc, &curr_value->it_interval);
83         if (read(timerfd, buf, sizeof(buf) <= 0))
84                 return -1;
85         timer_tsc = strtoul(buf, 0, 0);
86         /* If 0 (disabled), we'll return 0 for 'it_value'.  o/w we need to return
87          * the relative time. */
88         if (timer_tsc) {
89                 now_tsc = read_tsc();
90                 if (timer_tsc > now_tsc) {
91                         timer_tsc -= now_tsc;
92                 } else {
93                         /* it's possible that timer_tsc is in the past, and that we lost the
94                          * race.  The alarm fired since we looked at it, and it might be
95                          * disabled.  It might have fired multiple times too. */
96                         if (!period_tsc) {
97                                 /* if there was no period and the alarm fired, then it should be
98                                  * disabled.  This is racy, if there are other people setting
99                                  * the timer. */
100                                 timer_tsc = 0;
101                         } else {
102                                 while (timer_tsc < now_tsc)
103                                         timer_tsc += period_tsc;
104                         }
105                 }
106         }
107         tsc2timespec(timer_tsc, &curr_value->it_value);
108         return 0;
109 }
110
111 int timerfd_settime(int fd, int flags,
112                     const struct itimerspec *new_value,
113                     struct itimerspec *old_value)
114 {
115         int timerfd, periodfd;
116         int ret;
117         uint64_t period;
118         struct timespec now_timespec = {0};
119         struct timespec rel_timespec;
120
121         timerfd = get_sibling_fd(fd, "timer");
122         if (timerfd < 0)
123                 return -1;
124         periodfd = get_sibling_fd(fd, "period");
125         if (periodfd < 0) {
126                 close(timerfd);
127                 return -1;
128         }
129         if (old_value) {
130                 if (__timerfd_gettime(timerfd, periodfd, old_value)) {
131                         ret = -1;
132                         goto out;
133                 }
134         }
135         if (!new_value->it_value.tv_sec && !new_value->it_value.tv_nsec) {
136                 ret = set_timer(timerfd, 0);
137                 goto out;
138         }
139         period = timespec2tsc(&new_value->it_interval);
140         ret = set_period(periodfd, period);
141         if (ret < 0)
142                 goto out;
143         /* So the caller is asking for timespecs in wall-clock time (depending on
144          * the clock, actually, (TODO)), and the kernel expects TSC ticks from boot.
145          * If !ABSTIME, then it's just relative to now.  If it is ABSTIME, then they
146          * are asking in terms of real-world time, which means ABS - NOW to get the
147          * rel time, then convert to tsc ticks. */
148         if (flags & TFD_TIMER_ABSTIME) {
149                 ret = clock_gettime(CLOCK_MONOTONIC, &now_timespec);
150                 if (ret < 0)
151                         goto out;
152                 subtract_timespecs(&rel_timespec, &new_value->it_value, &now_timespec);
153         } else {
154                 rel_timespec = new_value->it_value;
155         }
156         ret = set_timer(timerfd, timespec2tsc(&rel_timespec) + read_tsc());
157         /* fall-through */
158 out:
159         close(timerfd);
160         close(periodfd);
161         return ret;
162 }
163
164 int timerfd_gettime(int fd, struct itimerspec *curr_value)
165 {
166         int timerfd, periodfd;
167         int ret;
168
169         timerfd = get_sibling_fd(fd, "timer");
170         if (timerfd < 0)
171                 return -1;
172         periodfd = get_sibling_fd(fd, "period");
173         if (periodfd < 0) {
174                 close(timerfd);
175                 return -1;
176         }
177         ret = __timerfd_gettime(timerfd, periodfd, curr_value);
178         close(timerfd);
179         close(periodfd);
180         return ret;
181 }