aboutsummaryrefslogtreecommitdiff
path: root/android/WALT/app/src/main/jni/sync_clock.c
blob: e16b759e79d32f6378b9c09cecc73ba5e4defe0b (plain)
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
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sync_clock.h"

#include <asm/byteorder.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/usbdevice_fs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>


#ifdef __ANDROID__
    #include <android/log.h>
    #define  LOGD(...)  __android_log_print(ANDROID_LOG_VERBOSE, "ClockSyncNative", __VA_ARGS__)
#else
    #define  LOGD(...)  printf(__VA_ARGS__)
#endif


// How many times to repeat the 1..9 digit sequence it's a tradeoff between
// precision and how long it takes.
// TODO: investigate better combination of constants for repeats and wait times
const int kSyncRepeats = 7;
const int kMillion = 1000000;


/**
uptimeMicros() - returns microseconds elapsed since boot.
Same time as Android's SystemClock.uptimeMillis() but in microseconds.

Adapted from Android:
platform/system/core/libutils/Timers.cpp
platform/system/core/include/utils/Timers.h

See:
http://developer.android.com/reference/android/os/SystemClock.html
https://android.googlesource.com/platform/system/core/+/master/libutils/Timers.cpp
*/
int64_t uptimeMicros() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ((int64_t)ts.tv_sec) * kMillion + ts.tv_nsec / 1000;
}


// Sleeps us microseconds
int microsleep(int us) {
    struct timespec ts;
    ts.tv_sec = us / kMillion;
    us %= kMillion;
    ts.tv_nsec = us*1000;
    return nanosleep(&ts, NULL);
}


// *********************** Generic USB functions *******************************

static int send_char_async(int fd, int endpoint, char msg, char * label) {
    // TODO: Do we really need a buffer longer than 1 char here?
    char buffer[256] = {0};
    buffer[0] = msg;
    int length = 1;

    // TODO: free() the memory used for URBs.
    // Circular buffer of URBs? Cleanup at the end of clock sync?
    // Several may be used simultaneously, no signal when done.
    struct usbdevfs_urb *urb = calloc(1, sizeof(struct usbdevfs_urb));
    memset(urb, 0, sizeof(struct usbdevfs_urb));

    int res;
    urb->status = -1;
    urb->buffer = buffer;
    urb->buffer_length = length;
    urb->endpoint = endpoint;
    urb->type = USBDEVFS_URB_TYPE_BULK;
    urb->usercontext = label; // This is hackish
    do {
        res = ioctl(fd, USBDEVFS_SUBMITURB, urb);
    } while((res < 0) && (errno == EINTR));
    return res;
}


// Send or read using USBDEVFS_BULK. Allows to set a timeout.
static int bulk_talk(int fd, int endpoint, char * buffer, int length) {
    // Set some reasonable timeout. 20ms is plenty time for most transfers but
    // short enough to fail quickly if all transfers and retries fail with
    // timeout.
    const int kTimeoutMs = 20;
    struct usbdevfs_bulktransfer ctrl;
    // TODO: need to limit request size to avoid EINVAL

    ctrl.ep = endpoint;
    ctrl.len = length;
    ctrl.data = buffer;
    ctrl.timeout = kTimeoutMs;
    int ret = ioctl(fd, USBDEVFS_BULK, &ctrl);
    return ret;
}


/*******************************************************************************
* Clock sync specific stuff below.
* Most data is stored in the clock_connection struct variable.
*/

// Send a single character to the remote in a blocking mode
int send_cmd(struct clock_connection *clk, char cmd) {
    return bulk_talk(clk->fd, clk->endpoint_out, &cmd, 1);
}

// Schedule a single character to be sent to the remote - async.
int send_async(struct clock_connection *clk, char cmd) {
    return send_char_async(clk->fd, clk->endpoint_out, cmd, NULL);
}


int bulk_read(struct clock_connection *clk) {
    memset(clk->buffer, 0, sizeof(clk->buffer));
    int ret = bulk_talk(clk->fd, clk->endpoint_in, clk->buffer, sizeof(clk->buffer));
    return ret;
}

// microseconds elapsed since clk->t_base
int micros(struct clock_connection *clk) {
    return uptimeMicros() - clk->t_base;
}

// Clear all incoming data that's already waiting somewhere in kernel buffers
// and discard it.
void flush_incoming(struct clock_connection *clk) {
    // When bulk_read times out errno = ETIMEDOUT=110, retval =-1
    // should we check for this?

    while(bulk_read(clk) >= 0) {
        // TODO: fail nicely if waiting too long to avoid hangs
    }
}

// Ask the remote to send its timestamps
// for the digits previously sent to it.
void read_remote_timestamps(struct clock_connection *clk, int * times_remote) {
    int i;
    int t_remote;
    // Go over the digits [1, 2 ... 9]
    for (i = 0; i < 9; i++) {
        char digit = i + '1';
        send_cmd(clk, CMD_SYNC_READOUT);
        bulk_read(clk);
        if (clk->buffer[0] != digit) {
            LOGD("Error, bad reply for R%d: %s", i+1, clk->buffer);
        }
        // The reply string looks like digit + space + timestamp
        // Offset by 2 to ignore the digit and the space
        t_remote = atoi(clk->buffer + 2);
        times_remote[i] = t_remote;
    }
}


// Preliminary rough sync with a single message - CMD_SYNC_ZERO = 'Z'.
// This is not strictly necessary but greatly simplifies debugging
// by removing the need to look at very long numbers.
void zero_remote(struct clock_connection *clk) {
    flush_incoming(clk);
    clk->t_base = uptimeMicros();
    send_cmd(clk, CMD_SYNC_ZERO);
    bulk_read(clk); // TODO, make sure we got 'z'
    clk->maxE = micros(clk);
    clk->minE = 0;

    LOGD("Sent a 'Z', reply '%c' in %d us\n", clk->buffer[0], clk->maxE);
}



void improve_minE(struct clock_connection *clk) {
    int times_local_sent[9] = {0};
    int times_remote_received[9] = {0};

    // Set sleep time as 1/kSleepTimeDivider of the current bounds interval,
    // but never less or more than k(Min/Max)SleepUs. All pretty random
    // numbers that could use some tuning and may behave differently on
    // different devices.
    const int kMaxSleepUs = 700;
    const int kMinSleepUs = 70;
    const int kSleepTimeDivider = 10;
    int minE = clk->minE;
    int sleep_time = (clk->maxE - minE) / kSleepTimeDivider;
    if(sleep_time > kMaxSleepUs) sleep_time = kMaxSleepUs;
    if(sleep_time < kMinSleepUs) sleep_time = kMinSleepUs;

    flush_incoming(clk);
    // Send digits to remote side
    int i;
    for (i = 0; i < 9; i++) {
        char c = i + '1';
        times_local_sent[i] = micros(clk);
        send_async(clk, c);
        microsleep(sleep_time);
    }

    // Read out receive times from the other side
    read_remote_timestamps(clk, times_remote_received);

    // Do stats
    for (i = 0; i < 9; i++) {
        int tls = times_local_sent[i];
        int trr = times_remote_received[i];

        int dt;

        // Look at outgoing digits
        dt = tls - trr;
        if (tls != 0 && trr != 0 && dt > minE) {
            minE = dt;
        }

    }

    clk->minE = minE;

    LOGD("E is between %d and %d us, sleep_time=%d\n", clk->minE, clk->maxE, sleep_time);
}

void improve_maxE(struct clock_connection *clk) {
    int times_remote_sent[9] = {0};
    int times_local_received[9] = {0};

    // Tell the remote to send us digits with delays
    // TODO: try tuning / configuring the delay time on remote side
    send_async(clk, CMD_SYNC_SEND);

    // Read and timestamp the incoming digits, they may arrive out of order.
    // TODO: Try he same with USBDEVFS_REAPURB, it might be faster
    int i;
    for (i = 0; i < 9; ++i) {
        int retval = bulk_read(clk);
        // TODO: deal with retval = (bytes returned) > 1. shouldn't happen.
        // Can it happen on some devices?
        int t_local = micros(clk);
        int digit = atoi(clk->buffer);
        if (digit <=0 || digit > 9) {
            LOGD("Error, bad incoming digit: %s\n", clk->buffer);
        }
        times_local_received[digit-1] = t_local;
    }

    // Flush whatever came after the digits. As of this writing, it's usually
    // a single linefeed character.
    flush_incoming(clk);
    // Read out the remote timestamps of when the digits were sent
    read_remote_timestamps(clk, times_remote_sent);

    // Do stats
    int maxE = clk->maxE;
    for (i = 0; i < 9; i++) {
        int trs = times_remote_sent[i];
        int tlr = times_local_received[i];
        int dt = tlr - trs;
        if (tlr != 0 && trs != 0 && dt < maxE) {
            maxE = dt;
        }
    }

    clk->maxE = maxE;

    LOGD("E is between %d and %d us\n", clk->minE, clk->maxE);
}


void improve_bounds(struct clock_connection *clk) {
    improve_minE(clk);
    improve_maxE(clk);
}

// get minE and maxE again after some time to check for clock drift
void update_bounds(struct clock_connection *clk) {
    // Reset the bounds to some unrealistically large numbers
    int i;
    clk->minE = -1e7;
    clk->maxE =  1e7;
    // Talk to remote to get bounds on minE and maxE
    for (i=0; i < kSyncRepeats; i++) {
        improve_bounds(clk);
    }
}

void sync_clocks(struct clock_connection *clk) {
    // Send CMD_SYNC_ZERO to remote for rough initial sync
    zero_remote(clk);

    int rep;
    for (rep=0; rep < kSyncRepeats; rep++) {
        improve_bounds(clk);
    }

    // Shift the base time to set minE = 0
    clk->t_base += clk->minE;
    clk->maxE -= clk->minE;
    clk->minE = 0;
    LOGD("Base time shifted for zero minE\n");
}