aboutsummaryrefslogtreecommitdiff
path: root/android/WALT/app/src/main/jni/sync_clock.c
diff options
context:
space:
mode:
authorAndrew Lehmer <alehmer@google.com>2017-04-26 14:58:59 -0700
committerAndrew Lehmer <alehmer@google.com>2017-04-26 14:58:59 -0700
commite76dcf96b0c451e46cddfa695de8feeb92533937 (patch)
treeed9a45d409f988f517e6c3f3a685cbf81ac45a5a /android/WALT/app/src/main/jni/sync_clock.c
parentbcf013dda8ffac9fd76937be6441b44bb9f3586f (diff)
downloadwalt-e76dcf96b0c451e46cddfa695de8feeb92533937.tar.gz
Import google/walt
Cloned from https://github.com/google/walt.git without modification. Bug: 36896528 Test: N/A
Diffstat (limited to 'android/WALT/app/src/main/jni/sync_clock.c')
-rw-r--r--android/WALT/app/src/main/jni/sync_clock.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/android/WALT/app/src/main/jni/sync_clock.c b/android/WALT/app/src/main/jni/sync_clock.c
new file mode 100644
index 0000000..1591f88
--- /dev/null
+++ b/android/WALT/app/src/main/jni/sync_clock.c
@@ -0,0 +1,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 = {0};
+ 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 = {0};
+ ts.tv_sec = us / kMillion;
+ us %= kMillion;
+ ts.tv_nsec = us*1000;
+ 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 = {0};
+ // 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");
+}
+
+