Home | History | Annotate | Download | only in jni
      1 # Clock Synchronization
      2 
      3 How it works
      4 
      5 ## Step 1 - rough sync
      6 
      7         T0 = current_time()
      8         Tell the remote to zero clock.
      9         Wait for confirmation from remote
     10         maxE = current_time() - T0
     11         All further local time is measured from T0
     12 
     13 
     14 After this step we are sure that the remote clock lags behind the local clock by
     15 some value E. And we know that E >= 0 because remote was zeroed *after* we
     16 zeroed the local time (recored T0). And also E<= maxE. So 0 = minE < E < maxE.
     17 
     18 
     19 ## Step 2 - find better lower bound - `minE`
     20 
     21 Send some messages from local to remote, note the time right before sending the
     22 message (denote it as `t_local`) and have the remote reply with his timestamps
     23 of when it received the messages according to his clock that lags by unknown
     24 positive value `E` behind the local clock, denote it by `t_remote`.
     25 
     26 
     27         t_remote = t_local - E + travel_time
     28         E = t_local - t_remote + travel_time > t_local - t_remote
     29         since travel_time > 0
     30         E > t_local - t_remote
     31 
     32         set minE to max(minE, t_local - t_remote)
     33         Repeat
     34 
     35 We need to first send a bunch of messages with some random small delays, and
     36 only after that get the remote timestamps for all of them. This helps deal with
     37 unwanted buffering and delay added by the kernel of hardware in the outgoing
     38 direction.
     39 
     40 ## Step 3 - find better upper bound `maxE`
     41 
     42 Same idea, but in the opposite direction. Remote device sends us messages and
     43 then the timestamps according to his clock of when they were sent. We record the
     44 local timestamps when we receive them.
     45 
     46     t_local = t_remote + E + travel_time
     47     E = t_local - t_remote - travel time < t_local - t_remote
     48     set maxE = min(maxE, t_local - t_remote)
     49     Repeat
     50 
     51 ## Comparison with NTP
     52 
     53 NTP measures the mean travel_time (latency) and assumes it to be symmetric - the
     54 same in both directions. If the symmetry assumption is broken, there is no way
     55 to detect this. Both, systematic asymmetry in latency and clock difference would
     56 result in exactly the same observations -
     57 [explanation here](http://cs.stackexchange.com/questions/103/clock-synchronization-in-a-network-with-asymmetric-delays).
     58 
     59 In our case the latency can be relatively small compared to network, but is
     60 likely to be asymmetric due to the asymmetric nature of USB. The algorithm
     61 described above guarantees that the clock difference is within the measured
     62 bounds `minE < E < maxE`, though the resulting interval `deltaE = maxE - minE`
     63 can be fairly large compared to synchronization accuracy of NTP on a network
     64 with good latency symmetry.
     65 
     66 Observed values for `deltaE`
     67  - Linux desktop machine (HP Z420), USB2 port: ~100us
     68  - Same Linux machine, USB3 port: ~800us
     69  - Nexus 5 ~100us
     70  - Nexus 7 (both the old and the newer model) ~300us
     71  - Samsung Galaxy S3 ~150us
     72 
     73 
     74 
     75 ## Implementation notes
     76 
     77 General
     78  - All times in this C code are recored in microseconds, unless otherwise
     79    specified.
     80  - The timestamped messages are all single byte.
     81 
     82 USB communication
     83  - USB hierarchy recap: USB device has several interfaces. Each interface has
     84    several endpoints. Endpoints are directional IN = data going into the host,
     85    OUT = data going out of the host. To get data from the device via an IN
     86    endpoint, we must query it.
     87  - There are two types of endpoints - BULK and INTERRUPT. For our case it's not
     88    really important. Currently all the comms are done via a BULK interface
     89    exposed when you compile Teensy code in "Serial".
     90  - All the comms are done using the Linux API declared in linux/usbdevice_fs.h
     91  - The C code can be compiled for both Android JNI and Linux.
     92  - The C code is partially based on the code of libusbhost from the Android OS
     93    core code, but does not use that library because it's an overkill for our
     94    needs.
     95 
     96 ## There are two ways of communicating via usbdevice_fs
     97 
     98         // Async way
     99         ioctl(fd, USBDEVFS_SUBMITURB, urb);
    100         // followed by
    101         ioctl(fd, USBDEVFS_REAPURB, &urb); // Blocks until there is a URB to read.
    102 
    103         // Sync way
    104         struct usbdevfs_bulktransfer  ctrl;
    105         ctrl.ep = endpoint;
    106         ctrl.len = length;
    107         ctrl.data = buffer;
    108         ctrl.timeout = timeout; // [milliseconds] Will timeout if there is nothing to read
    109         int ret = ioctl(fd, USBDEVFS_BULK, &ctrl);
    110 
    111 
    112 
    113