-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlibiphb.c
More file actions
227 lines (198 loc) · 5.3 KB
/
Copy pathlibiphb.c
File metadata and controls
227 lines (198 loc) · 5.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
@brief Self-contained timerfd implementation of libiphb.
@file libiphb.c
Each iphb handle owns a private timerfd instead of talking to a heartbeat
server over a socket. See README.md for the rationale, the ABI-compatibility
notes, and the CAP_WAKE_ALARM requirement for suspend-waking timers.
This file is part of libiphb-timerfd and is distributed under the GNU Lesser
General Public License version 2.1 (see COPYING).
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <poll.h>
#include <stdint.h>
#include <sys/timerfd.h>
#include "libiphb.h"
#ifndef CLOCK_BOOTTIME_ALARM
# define CLOCK_BOOTTIME_ALARM 9
#endif
#ifndef CLOCK_BOOTTIME
# define CLOCK_BOOTTIME 7
#endif
/** Allocated structure for an iphb handle. */
struct _iphb_t {
int fd; /*!< timerfd used for the wakeup */
int alarm; /*!< non-zero if fd is an alarm clock (wakes from suspend) */
};
#define HB_INST(x) ((struct _iphb_t *)(x))
/* Drain any pending expirations from the timerfd. The fd is non-blocking, so
* this returns once there is nothing left to read. Returns the number of bytes
* consumed, or -1 on a real error. */
static int
iphb_drain(int fd)
{
int total = 0;
for (;;) {
uint64_t exp = 0;
ssize_t rc = read(fd, &exp, sizeof exp);
if (rc > 0) {
total += (int)rc;
continue;
}
if (rc == 0)
break;
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
if (errno == EINTR)
continue;
return -1;
}
return total;
}
/* Arm (or, when seconds == 0, disarm) the timerfd to fire "seconds" from now,
* relative to the timerfd's own clock. */
static int
iphb_arm(int fd, unsigned seconds)
{
struct itimerspec its;
memset(&its, 0, sizeof its);
its.it_value.tv_sec = (time_t)seconds;
its.it_value.tv_nsec = 0;
/* One-shot: it_interval stays zero. */
return timerfd_settime(fd, 0, &its, NULL);
}
iphb_t
iphb_open(int *dummy)
{
struct _iphb_t *self = calloc(1, sizeof *self);
if (!self) {
errno = ENOMEM;
return 0;
}
/* Prefer an alarm clock so wakeups can end (auto)suspend. This needs
* CAP_WAKE_ALARM; unprivileged callers transparently fall back to a plain
* boottime timer that only fires while the device is awake. */
self->fd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK | TFD_CLOEXEC);
self->alarm = 1;
if (self->fd == -1) {
self->fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK | TFD_CLOEXEC);
self->alarm = 0;
}
if (self->fd == -1) {
int saved = errno;
free(self);
errno = saved;
return 0;
}
if (dummy)
*dummy = 30; /* for compatibility with the old API */
return self;
}
int
iphb_get_fd(iphb_t iphbh)
{
if (!iphbh) {
errno = EINVAL;
return -1;
}
return HB_INST(iphbh)->fd;
}
time_t
iphb_wait2(iphb_t iphbh, unsigned mintime, unsigned maxtime, int must_wait,
int resume)
{
(void)resume; /* clock type is fixed at open time; see iphb_open */
if (!iphbh || mintime > maxtime) {
errno = EINVAL;
return (time_t)-1;
}
struct _iphb_t *self = HB_INST(iphbh);
/* Clear any wakeup that fired for a previous request. */
if (iphb_drain(self->fd) == -1)
return (time_t)-1;
/* mintime == maxtime == 0 cancels the pending wakeup (matches the old
* server semantics used by callers to stop waiting). */
if (maxtime == 0) {
if (iphb_arm(self->fd, 0) == -1)
return (time_t)-1;
return 0;
}
/* Wake at the far end of the [mintime, maxtime] window: it honours the
* mandatory minimum wait and maximises the chance of coalescing with other
* wakeups / staying suspended as long as allowed. */
if (iphb_arm(self->fd, maxtime) == -1)
return (time_t)-1;
if (!must_wait)
return 0;
/* Blocking variant: wait here until the timer fires. */
struct timespec beg = { 0, 0 };
clock_gettime(CLOCK_MONOTONIC, &beg);
for (;;) {
struct pollfd pfd = { .fd = self->fd, .events = POLLIN };
int rc = poll(&pfd, 1, -1);
if (rc > 0) {
(void)iphb_drain(self->fd);
struct timespec end = { 0, 0 };
clock_gettime(CLOCK_MONOTONIC, &end);
return (time_t)(end.tv_sec - beg.tv_sec);
}
if (rc == -1 && errno != EINTR)
return (time_t)-1;
}
}
time_t
iphb_wait(iphb_t iphbh, unsigned short mintime, unsigned short maxtime,
int must_wait)
{
return iphb_wait2(iphbh, mintime, maxtime, must_wait, 1);
}
int
iphb_I_woke_up(iphb_t iphbh)
{
if (!iphbh) {
errno = EINVAL;
return -1;
}
struct _iphb_t *self = HB_INST(iphbh);
int drained = iphb_drain(self->fd);
/* Woken by some other means: cancel the outstanding wakeup. */
(void)iphb_arm(self->fd, 0);
return drained;
}
int
iphb_discard_wakeups(iphb_t iphbh)
{
if (!iphbh) {
errno = EINVAL;
return -1;
}
return iphb_drain(HB_INST(iphbh)->fd);
}
int
iphb_get_stats(iphb_t iphbh, struct iphb_stats *stats)
{
if (!iphbh || !stats) {
errno = EINVAL;
return -1;
}
/* There is no central server to aggregate statistics any more. Report a
* single local client so callers that sanity-check the struct keep working. */
stats->clients = 1;
stats->waiting = 0;
stats->next_hb = 0;
return 0;
}
iphb_t
iphb_close(iphb_t iphbh)
{
if (iphbh) {
struct _iphb_t *self = HB_INST(iphbh);
if (self->fd != -1)
close(self->fd);
free(self);
}
return 0;
}