├── README.md ├── clk_sync_observer ├── README.md ├── clk_sync_observer.c ├── clxync_obsv.h ├── clxync_obsv_sql.c ├── orloj_init_db.sql ├── refclk.c ├── refclk.h ├── refclk_sql.c └── txtpipe2sql.c ├── notes └── requirements.md /README.md: -------------------------------------------------------------------------------- 1 | rt_clk_sync 2 | =========== 3 | 4 | Hard Real-Time world-wide clock synchronization utilizing new better algorithms than PTP and NTP uses. 5 | 6 | Objectives 7 | ---------- 8 | 9 | * "NTP without PLL" -- continue the ideas of [1], [2] 10 | * better stability (no feedback with obscured interoperation 11 | between digital quasi-VCO inside of OS which nobody 12 | understands, and the ad-hoc tuned PI-controller within ntpd in 13 | userspace 14 | * possibly better performance 15 | - provide means to tune system to particular process (computer 16 | clock) & measurement (network) noises by means of their 17 | absolute variances as well as temporal characteristics 18 | - according to control theory laws, optimally tuned estimator 19 | will perform same or better than any possible feedback system 20 | for the NTP task 21 | 22 | * establish better NTP-like userspace program <-> OS interface 23 | * provide more clear i/f based on current time offset & drift 24 | estimates input into OS's timekeeping core; 25 | no P, I controller parameters, no ad-hoc quasi-VCOs; 26 | the Linux' adjtimex() is an example of devil's evil 27 | * try Linux as the reference implementation 28 | * join efforts with PTPd guys, they probably need exactly the 29 | same 30 | 31 | * allow simultaneous locking to several NTP servers 32 | * AFAIK impossible with current ntpd client, correct me if wrong 33 | 34 | * implement optimal linear estimator for clock processes, allowing 35 | to run with no absolute master at all [3], [4], [5] 36 | 37 | In longer term: 38 | 39 | * adaptive estimation of both clock and network parameters 40 | (autotuning, or continuous adaptation) 41 | 42 | * revise/redesign application interface to allow e.g. delay/latency measurement between two hosts (us and some other) on demand or 43 | reliable reporting of current/min/max accuracy (delay/latency) which 44 | all other connected stations have 45 | 46 | * statistics/logging of estimated accuracy (for audits, revisions, etc.) 47 | 48 | The ultimate goal (megalomanic): 49 | 50 | * release the Internet-wide network of computers and their clocks, 51 | managed as a non-hierarhical cloud of clocks of very different 52 | stochastical properties (from GPSes and highly stable atomic 53 | standards to ordinary PC's crystals); 54 | apply clock ensembling algorithms to perform this large-scale 55 | time fusion 56 | 57 | To Do 58 | ----- 59 | 60 | ### Outline ### 61 | 62 | Test & prototype the idea in two different scenarios: 63 | * LAN, 1 NTP server 64 | * WAN, multiple NTP servers 65 | 66 | ### Tasks ### 67 | 68 | * write clk_sync_observer, preferably in Linux' user-space 69 | * should run besides classical, non-patched ntpd 70 | * should monitor bidirectional NTP network datagrams, parse them 71 | and record their timestamps wrt. non-disciplined local clock 72 | * record evolution of disciplined ("ntp") time wrt. local clock 73 | * provide means to perform/record simultaneous NTP queries to 74 | serveral NTP servers from fixed set 75 | 76 | * define reference PC or "PC-like" HW platform 77 | * Ethernet card 78 | * some reasonable, but not unrealistically good local clock 79 | (i.e. no undeterministic, unbeatable errors, but on the other 80 | hand no unfairly good oscillators, common consumer/industrial 81 | HW only) 82 | * a means to output OS' clock synchronous marks, at least 83 | (at worst) using GPIO 84 | 85 | * make a simple and portable FPGA & Rb clock reference measurement 86 | kit (for this purpose, 10ns is fairly good, so no fancy 87 | interpolation techniques from VZLU's TDC are needed) 88 | 89 | * perform measurements 90 | * WAN scenario 91 | * LAN scenario 92 | 93 | * evaluate acquired data 94 | * perform offline simulations of proposed algorithms 95 | * compare against bare NTP 96 | 97 | * patch Linux' timekeeping.c 98 | * wipe out PI/quasi-VCO, replace with offset&drift estimates 99 | - for compatibility reason, current PI/VCO mess may perhaps 100 | become an upper shell on top of the offset/drift i/f 101 | 102 | * write a NTP client-side userspace replacement, using algorithms 103 | and new OS i/f as described 104 | * first, only single-server mode 105 | * later, add multiple-servers fusion 106 | * (much later: adaptive filtering...) 107 | 108 | Resources 109 | --------- 110 | 111 | * https://github.com/jeremyfix/easykf (C++ Kalman filtering (Extended Kalman Filter, Unscented Kalman Filter) 112 | * https://github.com/TKJElectronics/KalmanFilter (Kalman filter used to calculate the angle, rate and bias from from the input of an accelerometer/magnetometer and a gyroscope) 113 | 114 | People & Credits 115 | ---------------- 116 | 117 | * Dave Mills (for NTP) 118 | * Thomas Gleixner (for current timekeeping.c) 119 | * Pavel Pisa (guidance to tglx's timekeeping.c) 120 | * Jan Pacner (ptpd port to QNX, started ntpd vs. ptpd comparisons) 121 | * Marek Peca (noPLL NTP _idefix_, clock ensembling algorithms) 122 | 123 | [1] J.Levine: Synchronizing computer clocks using Kalman filters. 124 | 43rd PTTI Meeting, 2011. 125 | 126 | [2] J.Ridoux, D.Veitch: Ten Microseconds Over LAN, for Free. 127 | IEEE ISPCS, 2007. 128 | 129 | [3] K.R.Brown: The Theory of the GPS Composite Clock. Proceedings 130 | of ION GPS-91, 1991. 131 | 132 | [4] C.A.Greenhall: A Review of Reduced Kalman Filters for Clock 133 | Ensembles. IEEE UFFC 2012. 134 | 135 | [5] M.Peca, V.Michalek, M.Vacek: Clock Composition by Wiener 136 | Filtering Illustrated on Two Atomic Clocks. EFTF 2013 137 | (accepted) 138 | -------------------------------------------------------------------------------- /clk_sync_observer/README.md: -------------------------------------------------------------------------------- 1 | compile (needs libpcap installed): 2 | 3 | `gcc -o clk_sync_observer -pedantic -lpcap ./clk_sync_observer.c` 4 | 5 | run: 6 | 7 | `./clk_sync_observer` 8 | 9 | _________________________________________________________________ 10 | Comments on timestamping, pcap, Linux & Zynq (Marek Peca, pecam1) 11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 12 | 13 | - capture (pcap) chosen to compare our algs against ntp, ntimed,...; reason: 14 | do not require patching of other's SW 15 | - suitable reference HW to compare with absolute time (Rb clock, GPS Rx): Zynq; 16 | reason: easy addition of simple timestamping HW counter, untouched by OS 17 | 18 | Observations: 19 | 20 | - Linux (3.14.2 for firsts tests) supports 2 APIs for net timestamping: 21 | a) ioctl SIOGCSTAMP(NS) (probably a legacy way) 22 | b) cmsg SCM_TIMESTAMP(NS) 23 | 24 | - HW timestamps are theoretically supported; fortunately, a concept of 25 | disciplined NIC oscillator by NIC driver has been deprecated, and moved to 26 | userspace. Therefore, only host SW timestamps, or raw (non-disciplined) 27 | HW timestamps are supported, HW clock *shall* be exposed as a clock source, 28 | and this may be disciplined from userspace (the good side is, it need not); 29 | 30 | However: 31 | -- whether there are NICs which support HW timestamping of generic UDP packet, 32 | is unknown to me; Zynq does not, its Gigabit Eth supports stamping of PTP 33 | stuff only; *and*, the NIC driver as of xlnx linux 4.1.x does some weird 34 | action to discipline NIC time (yes, silly control attempts...); 35 | 36 | - libpcap (1.7.3) seems to use ioctl SIOCGSTAMP(NS) only, HW tstamps supported; 37 | 38 | - both (a) ioctl & (b) cmsg resort to ktime_get_real() in case of SW timestamp; 39 | it seems it is not possible to choose other clock source in Linux: 40 | 41 | (a) inet_ioctl() calls: 42 | int sock_get_timestampns(...): 43 | (..) 44 | if (ts.tv_sec == 0) { 45 | sk->sk_stamp = ktime_get_real(); 46 | ts = ktime_to_timespec(sk->sk_stamp); 47 | } 48 | 49 | (b) __sock_recv_timestamp() calls: 50 | static inline void __net_timestamp(struct sk_buff *skb) 51 | { 52 | skb->tstamp = ktime_get_real(); 53 | } 54 | 55 | - Ntimed (Aug 2015 development client) in contrary uses only SCM_* cmsg API; 56 | 57 | - by an experiment, it has been confirmed, at least on reference Zynq, that 58 | nanosecond timestamps are equal among libpcap (using ioctl, SW tstamps), and 59 | Ntimed, using cmsgs 60 | 61 | Experiment 62 | 63 | ./clk_sync_observer -d eth0 -t host 64 | has been run on Zynq in parallel with Ntimed; 65 | "host" timestamps select the only working stamping, i.e. SW. 66 | (2015-08-07 pecam1) 67 | -------------------------------------------------------------------------------- /clk_sync_observer/clk_sync_observer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * capture NTP packets and extract contained timestamps 3 | * 4 | * Authors: 5 | * first code by Jan Pacner 6 | * idea by Marek Peca 7 | * supported by the wisdom of Pavel Pisa 8 | * 9 | * Date: 2013-05-23 14:48:07 CEST 10 | * License: 11 | * "THE BEER-WARE LICENSE" (Revision 42): 12 | * Jan Pacner wrote this file. As long as you retain this notice you 13 | * can do whatever you want with this stuff. If we meet some day and 14 | * you think this stuff is worth it, you can buy me a beer in return. 15 | */ 16 | 17 | // useful links 18 | // fxr.watson.org 19 | // http://dpdk.org/ (extremely fast packet processing on x86) 20 | // not implemented 21 | // support for any L3+ tunneling (including IPSec etc.) 22 | // support for fragmented packets (neither IPv4 nor IPv6) 23 | // NTP control digests checking 24 | // NTP message authentication code support 25 | // NTP port choice 26 | // NTP leap detection 27 | 28 | //#include // HACK for pcap missing u_* types (needed under Linux) 29 | //#define __USE_BSD // ...see the preceeding line 30 | #include 31 | #include 32 | 33 | #include // u_* types 34 | #include 35 | #include 36 | #include // open() 37 | #include // open() 38 | #include 39 | #include // in_addr in6_addr 40 | #include // inet_ntop() 41 | #include // sigaction() 42 | #include 43 | #include // strerror() 44 | #include 45 | 46 | #include "clxync_obsv.h" 47 | 48 | #define RING_BUF_SIZE 8192 /* for 1 packet */ 49 | #define READ_TIMEOUT 300 /* ms */ 50 | 51 | #define IP_VERSION_4 4 /* content of the version field in IP header */ 52 | #define IP_VERSION_6 6 /* - || - */ 53 | 54 | /* ethernet frame */ 55 | typedef struct { 56 | /* preamble and frame delimiter are not part of pcap frame */ 57 | uint8_t mac_addr_dst[6]; 58 | uint8_t mac_addr_src[6]; 59 | /* 802.1Q tag is removed by libpcap */ 60 | uint16_t len_or_ethertype; /* <1500 payload len 61 | >=1536 EtherType values 62 | rest is undefined */ 63 | /* checksum is removed by libpcap */ 64 | } __packed eth_hdr_t; 65 | 66 | /* IPv4 header (according to RFC 791), partially adopted from tutorial 67 | http://www.tcpdump.org/pcap.html and 68 | http://systhread.net/texts/200805lpcap1.php) */ 69 | typedef struct { 70 | uint8_t ver_hdrlen; /* 4b version; 4b header length (in multiples of 4B) */ 71 | #define IPv4_version(x) ((x) >> 4) /* should be IPPROTO_IP */ 72 | #define IPv4_hdrlen(x) (((x) & 0x0f) * 4) 73 | uint8_t dscp; /* differentiated services code point */ 74 | uint16_t totallen; /* len of fragment (header + data) in bytes */ 75 | uint16_t id; /* identification */ 76 | uint16_t flags_foff; /* flags & fragment offset field */ 77 | #define IPv4_DF 0x4000 /* dont fragment flag */ 78 | #define IPv4_FOF_MASK 0x1fff /* mask for fragmenting bits */ 79 | uint8_t ttl; 80 | uint8_t proto; /* protocol 81 | IPPROTO_IP (could be more than once, 82 | but we do not support IP in IP) 83 | IPPROTO_TCP 84 | IPPROTO_UDP */ 85 | uint16_t checksum; 86 | struct in_addr src; 87 | struct in_addr dst; 88 | } __packed ipv4_hdr_t; 89 | 90 | /* IPv6 header (according to RFC 2460) */ 91 | typedef struct { 92 | uint32_t ver_class_label; /* 4b version; 8b traffic class; 20b flow label */ 93 | #define IPv6_version(x) ((x) >> (8 + 20)) /* should be IPPROTO_IPV6 */ 94 | uint16_t payloadlen; /* len of the data after current header in bytes */ 95 | uint8_t nexthdr; /* same as IPv4 protocol field 96 | netinet/in.h: 97 | IPPROTO_NONE no next header 98 | IPPROTO_IPV6 ipv6 header (can be more than once) 99 | IPPROTO_FRAGMENT */ 100 | uint8_t hoplimit; 101 | struct in6_addr src; 102 | struct in6_addr dst; 103 | } __packed ipv6_hdr_t; 104 | 105 | /* UDP header (according to RFC 768) */ 106 | typedef struct { 107 | uint16_t src; /* port */ 108 | uint16_t dst; /* port */ 109 | uint16_t len; /* len of (header + data) in bytes */ 110 | uint16_t checksum; 111 | } __packed udp_hdr_t; 112 | 113 | /* ?? 114 | typedef struct { 115 | uint32_t key_id; 116 | uint8_t dgst[128]; 117 | } __packed ntp_ftr_t; 118 | */ 119 | 120 | struct global_vars_s { 121 | pcap_t *pcap_handle; 122 | } global_vars; 123 | 124 | struct args_s { 125 | char *d; // eth device 126 | FILE *o; // output 127 | char *tstamp_type; // prefered timestamp type 128 | int promisc; 129 | char buf[1024];// = {0}; 130 | int buf_end;// = 0; 131 | struct timeval sysclk; 132 | } args = { 133 | .tstamp_type = NULL, 134 | .promisc = 0, 135 | .buf_end = 0, 136 | }; 137 | 138 | char *ipaddr2str(char *s, const uint8_t *addr, const bool is_ipv6) { 139 | static char buf[INET6_ADDRSTRLEN+1]; 140 | if (s == NULL) 141 | s = buf; 142 | inet_ntop(is_ipv6 ? AF_INET6 : AF_INET, (void*)addr, s, INET6_ADDRSTRLEN); 143 | return s; 144 | } 145 | 146 | int ntp2txt(char *s, ntp_pkt_t *ntp) { 147 | uint8_t li, vn, mode; 148 | li = ntp->li_vn_mode >> 6; 149 | vn = (ntp->li_vn_mode >> 3) & 0x7; 150 | mode = (ntp->li_vn_mode >> 0) & 0x7; 151 | 152 | return 153 | sprintf(s, 154 | " li=%u vn=%u mode=%u stratum=%u poll=%u precision=%u\n" 155 | " root_delay=%u root_disp=%u ref_id=0x%08x\n" 156 | " ref_tstamp=%016llX org_tstamp=%016llX\n" 157 | " rec_tstamp=%016llX xmt_tstamp=%016llX\n", 158 | li, vn, mode, 159 | ntp->stratum, ntp->poll, ntp->precision, 160 | ntp->root_delay, ntp->root_disp, ntp->ref_id, 161 | ntp->ref_tstamp, ntp->org_tstamp, ntp->rec_tstamp, ntp->xmt_tstamp); 162 | } 163 | 164 | void output_ntp_packet(FILE *out, 165 | bool ipv6, 166 | const uint8_t *src_ip, uint16_t src_port, 167 | const uint8_t *dst_ip, uint16_t dst_port, 168 | const struct timeval *tstamp, /* actually is in nsec */ 169 | ntp_pkt_t *ntp) { 170 | static char a1[INET6_ADDRSTRLEN+1], a2[INET6_ADDRSTRLEN+1]; 171 | static char s[1024]; 172 | ntp2txt(s, ntp); 173 | fprintf(out, 174 | "t=%lu.%09lu\tsrc=%s[%d] dst=%s[%d]\n%s", 175 | tstamp->tv_sec, tstamp->tv_usec, 176 | ipaddr2str(a1, src_ip, ipv6), src_port, 177 | ipaddr2str(a2, dst_ip, ipv6), dst_port, 178 | s); 179 | } 180 | 181 | static inline uint8_t get_u8(uint8_t **p) { 182 | uint8_t u = **p; 183 | ++*p; 184 | return u; 185 | } 186 | 187 | static inline uint32_t get_u32be(uint8_t **p) { 188 | uint32_t u = get_u8(p); 189 | u <<= 8; u |= get_u8(p); 190 | u <<= 8; u |= get_u8(p); 191 | u <<= 8; u |= get_u8(p); 192 | return u; 193 | } 194 | 195 | static inline uint64_t get_u64be(uint8_t **p) { 196 | uint64_t u = get_u32be(p); 197 | u <<= 32; u |= get_u32be(p); 198 | return u; 199 | } 200 | 201 | void parse_ntp_packet(ntp_pkt_t *ntp, uint8_t *pkt) { 202 | ntp->li_vn_mode = get_u8(&pkt); 203 | ntp->stratum = get_u8(&pkt); 204 | ntp->poll = get_u8(&pkt); 205 | ntp->precision = get_u8(&pkt); 206 | ntp->root_delay = get_u32be(&pkt); 207 | ntp->root_disp = get_u32be(&pkt); 208 | ntp->ref_id = get_u32be(&pkt); 209 | ntp->ref_tstamp = get_u64be(&pkt); 210 | ntp->org_tstamp = get_u64be(&pkt); 211 | ntp->rec_tstamp = get_u64be(&pkt); 212 | ntp->xmt_tstamp = get_u64be(&pkt); 213 | } 214 | 215 | #define CHECK_PACKET_LEN \ 216 | do { if (packet > _packet + header->caplen) return; } while (0) 217 | 218 | /** remove packet headers (assume only IP) */ 219 | void handle_packet(uint8_t *_args, const struct pcap_pkthdr *header, 220 | const uint8_t *_packet) { 221 | struct args_s *args = (struct args_s *)_args; 222 | uint8_t *packet = (uint8_t *)_packet; 223 | uint8_t *tmp; 224 | 225 | /* pcap timestamp: header->ts */ 226 | 227 | /* jump over ethernet header */ 228 | packet += sizeof(eth_hdr_t); 229 | CHECK_PACKET_LEN; 230 | 231 | uint8_t 232 | *src = NULL, /* in_addr or in6_addr */ 233 | *dst = NULL; /* in_addr or in6_addr */ 234 | bool ipv6_found = false; 235 | 236 | /* jump over IP header(s) */ 237 | switch (IPv4_version(((ipv4_hdr_t *)packet)->ver_hdrlen)) { 238 | case IP_VERSION_4: 239 | /* do not support fragmented packets (but if fragmented, take the 240 | first fragment and assume, the message is not damaged) */ 241 | if (! (IPv4_DF || (! (IPv4_FOF_MASK & 242 | ntohs(((ipv4_hdr_t *)packet)->flags_foff)) )) ) 243 | return; 244 | 245 | /* NTP works only using UDP */ 246 | if (((ipv4_hdr_t *)packet)->proto != IPPROTO_UDP) return; 247 | 248 | tmp = packet; 249 | packet += IPv4_hdrlen(((ipv4_hdr_t *)packet)->ver_hdrlen); 250 | CHECK_PACKET_LEN; 251 | src = (uint8_t *)&((ipv4_hdr_t *)tmp)->src; 252 | dst = (uint8_t *)&((ipv4_hdr_t *)tmp)->dst; 253 | break; 254 | case IP_VERSION_6: 255 | /* jump over all chained IPv6 headers */ 256 | while (((ipv6_hdr_t *)packet)->nexthdr == IPPROTO_IPV6) { 257 | packet += sizeof(ipv6_hdr_t); 258 | CHECK_PACKET_LEN; 259 | } 260 | 261 | if (((ipv6_hdr_t *)packet)->nexthdr != IPPROTO_UDP) return; 262 | 263 | tmp = packet; 264 | packet += sizeof(ipv6_hdr_t); 265 | CHECK_PACKET_LEN; 266 | src = (uint8_t *)&((ipv6_hdr_t *)tmp)->src; 267 | dst = (uint8_t *)&((ipv6_hdr_t *)tmp)->dst; 268 | ipv6_found = true; 269 | break; 270 | default: 271 | return; 272 | } 273 | 274 | tmp = packet; 275 | udp_hdr_t *udp_hdr = (udp_hdr_t*)tmp; 276 | packet += sizeof(udp_hdr_t); /* jump over UDP header */ 277 | CHECK_PACKET_LEN; 278 | 279 | if (header->caplen - (packet - _packet) != sizeof(ntp_pkt_pkd_t)) 280 | /*FIXME: add broken/unknown packets error reporting */ 281 | return; 282 | ntp_pkt_t ntp; 283 | parse_ntp_packet(&ntp, packet); 284 | uint16_t src_port = htons(udp_hdr->src), dst_port = htons(udp_hdr->dst); 285 | output_ntp_packet(args->o, 286 | ipv6_found, src, src_port, dst, dst_port, 287 | &header->ts, &ntp); 288 | #ifdef SQL 289 | sql_output_ntp_packet(ipv6_found, src, src_port, dst, dst_port, 290 | &header->ts, &ntp); 291 | #endif 292 | } 293 | 294 | void print_pcap_warning(FILE *f, int rc) { 295 | switch (rc) { 296 | case PCAP_WARNING_PROMISC_NOTSUP: 297 | fprintf(f, "WARNING: promiscuous mode not supported\n"); 298 | break; 299 | case PCAP_WARNING_TSTAMP_TYPE_NOTSUP: 300 | fprintf(f, "WARNING: timestamp type not supported\n"); 301 | break; 302 | case PCAP_WARNING: 303 | fprintf(f, "WARNING: generic(?) pcap_activate() warning\n"); 304 | break; 305 | default: 306 | fprintf(f, "WARNING: unknown pcap_activate() warning\n"); 307 | break; 308 | } 309 | } 310 | 311 | int start_capture(struct args_s *args) { 312 | char errbuf[PCAP_ERRBUF_SIZE]; 313 | int rc = 0; 314 | errbuf[0] = '\0'; 315 | 316 | /* set up capture device and parameters */ 317 | pcap_t *pcap; 318 | global_vars.pcap_handle = pcap = pcap_create(args->d, errbuf); 319 | if (pcap == NULL) 320 | goto pcap_fatal; 321 | if (pcap_set_snaplen(pcap, RING_BUF_SIZE)) 322 | goto pcap_fatal; 323 | fprintf(stderr, "setting %spromiscuous mode @%s\n", 324 | args->promisc ? "" : "not-", args->d); 325 | if (pcap_set_promisc(pcap, args->promisc)) 326 | goto pcap_fatal; 327 | if (pcap_set_timeout(pcap, READ_TIMEOUT)) 328 | goto pcap_fatal; 329 | 330 | /* set timestamp type and resolution */ 331 | const char *tstamp_type_pref[] = { 332 | "adapter_unsynced", "adapter", "host", 333 | }; 334 | int *tstamp_types; 335 | rc = pcap_list_tstamp_types(pcap, &tstamp_types); 336 | if (rc == PCAP_ERROR) { 337 | fprintf(stderr, "ERR: %s \"%s\"\n", pcap_geterr(pcap), args->d); 338 | } 339 | else { 340 | int i, j, pref_idx = length(tstamp_type_pref), pref_type = PCAP_ERROR; 341 | fprintf(stderr, "%s supported pcap timestamp types: ", args->d); 342 | for (i = 0; i < rc; i++) { 343 | const char *name = pcap_tstamp_type_val_to_name(tstamp_types[i]); 344 | fprintf(stderr, "%s (%s)%s", name, 345 | pcap_tstamp_type_val_to_description(tstamp_types[i]), 346 | (i == rc-1) ? "\n" : ", "); 347 | for (j = 0; j < pref_idx; j++) { 348 | if (args->tstamp_type) { 349 | /* prefer user's choice */ 350 | if (strcmp(name, args->tstamp_type) == 0) { 351 | pref_idx = -1; 352 | pref_type = tstamp_types[i]; 353 | } 354 | } 355 | if (strcmp(name, tstamp_type_pref[j]) == 0) { 356 | pref_idx = j; 357 | pref_type = tstamp_types[i]; 358 | } 359 | } 360 | } 361 | if (pref_idx != length(tstamp_type_pref)) { 362 | fprintf(stderr, "setting timestamp type to \"%s\"\n", 363 | pcap_tstamp_type_val_to_name(pref_type)); 364 | if (pcap_set_tstamp_type(pcap, pref_type)) { 365 | fprintf(stderr, "ERR: %s \"%s\"\n", pcap_geterr(pcap), args->d); 366 | } 367 | } 368 | else { 369 | fprintf(stderr, "no prefered timestamp type found\n"); 370 | } 371 | pcap_free_tstamp_types(tstamp_types); 372 | } 373 | if (pcap_set_tstamp_precision(pcap, PCAP_TSTAMP_PRECISION_NANO)) 374 | goto pcap_fatal; 375 | 376 | /* activate capture device */ 377 | if ((rc = pcap_activate(pcap)) < 0) 378 | goto pcap_fatal; 379 | if (rc) 380 | print_pcap_warning(stderr, rc); 381 | 382 | /* IPv4, IPv6, UDP, port 123 383 | http://ethereal.cs.pu.edu.tw/lists/ethereal-users/200208/msg00039.html */ 384 | struct bpf_program filter; 385 | if (pcap_compile(pcap, &filter, 386 | "udp && (port 123)", 1, PCAP_NETMASK_UNKNOWN)) { 387 | fprintf(stderr, "ERR: %s \"%s\"\n", 388 | pcap_geterr(pcap), args->d); 389 | return EXIT_FAILURE; 390 | } 391 | 392 | /* man pcap-filter */ 393 | if (pcap_setfilter(pcap, &filter)) { 394 | fprintf(stderr, "ERR: %s \"%s\"\n", 395 | pcap_geterr(pcap), args->d); 396 | return EXIT_FAILURE; 397 | } 398 | 399 | rc = pcap_loop(pcap, -1, handle_packet, (void *)args); 400 | pcap_close(pcap); 401 | 402 | if (rc == -1) 403 | goto pcap_fatal; 404 | return EXIT_SUCCESS; 405 | 406 | pcap_fatal: 407 | fprintf(stderr, "ERR: pcap @%s: %s\n", 408 | args->d, pcap_geterr(pcap)); 409 | return EXIT_FAILURE; 410 | } 411 | 412 | /* sigaction handler */ 413 | void my_sa_handler(int x) { 414 | x = x; 415 | fprintf(stderr, "finishing packet capture\n"); 416 | pcap_breakloop(global_vars.pcap_handle); 417 | } 418 | 419 | int main(int argc, char *argv[]) { 420 | global_vars.pcap_handle = NULL; 421 | 422 | sigset_t sigblock; 423 | sigfillset(&sigblock); 424 | struct sigaction signew = { 425 | .sa_handler = my_sa_handler, 426 | //.sa_sigaction = NULL, /* may overlap with sa_handler => do not use both */ 427 | .sa_mask = sigblock, 428 | .sa_flags = 0, 429 | }; 430 | 431 | sigaction(SIGTERM, &signew, NULL); /* termination */ 432 | sigaction(SIGHUP, &signew, NULL); /* hangup */ 433 | sigaction(SIGINT, &signew, NULL); /* interrupt */ 434 | 435 | args.d = NULL; 436 | args.o = NULL; 437 | 438 | int opt; 439 | while ((opt = getopt(argc, argv, "+hd:o:t:")) != -1) { 440 | switch (opt) { 441 | case 'h': 442 | printf( 443 | #ifdef SQL 444 | "USAGE: %s [-h] [-d ] [-o ] [sql_db_file]\n" 445 | #else 446 | "USAGE: %s [-h] [-d ] [-o ]\n" 447 | #endif 448 | " -d ethernet device to watch on\n" 449 | " if none given, watch on all available devices\n" 450 | " -t timestamp_type\n" 451 | " try to select chosen pcap timestamp type, if possible\n" 452 | " -o output file\n" 453 | " if none given, use stdout\n", argv[0]); 454 | return EXIT_SUCCESS; 455 | case 'd': 456 | if (args.d == NULL) 457 | args.d = argv[optind -1]; 458 | else 459 | fprintf(stderr, "ERR: Argument -%c can be given only once!", (char)opt); 460 | break; 461 | case 't': 462 | args.tstamp_type = argv[optind-1]; 463 | break; 464 | case 'o': 465 | if (args.o == NULL) { 466 | int fildes; 467 | if ( 468 | // obtain file descriptor 469 | ((fildes = open(argv[optind -1], O_WRONLY | O_CREAT | O_EXCL, 470 | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP)) == -1) 471 | || 472 | // use the obtained file descriptor 473 | ((args.o = fdopen(fildes, "w")) == NULL) 474 | ) { 475 | fprintf(stderr, "ERR: Cannot open \"%s\" (%s).\n", 476 | argv[optind -1], strerror(errno)); 477 | return EXIT_FAILURE; 478 | } 479 | } 480 | else { 481 | fprintf(stderr, "ERR: Argument -%c can be given only once!", (char)opt); 482 | } 483 | break; 484 | default: 485 | break; 486 | } 487 | } 488 | 489 | /* optind points to next argument (after the current one) in argv */ 490 | #ifdef SQL 491 | if (optind != argc) { 492 | sql_init(argv[optind]); 493 | } 494 | else { 495 | sql_init(DEFAULT_SQL_DB); 496 | } 497 | sql_log("init"); 498 | #else 499 | fprintf(stderr, "Unknown argument \"%s\".\n", argv[optind]); 500 | return EXIT_FAILURE; 501 | #endif 502 | 503 | if (args.d == NULL) { 504 | fprintf(stderr, 505 | "WARN: On some platforms (e.g. Linux) the pcap device \"any\" " 506 | "produces\nmalformed packets. See -h for choosing a particular " 507 | "device.\n"); 508 | args.d = "any"; 509 | } 510 | if (args.o == NULL) args.o = stdout; 511 | 512 | fprintf(stderr, "Press Ctrl+C for exit.\n"); 513 | int ret = start_capture(&args); 514 | fclose(args.o); 515 | #ifdef SQL 516 | sql_close(); 517 | #endif 518 | return ret; 519 | } 520 | -------------------------------------------------------------------------------- /clk_sync_observer/clxync_obsv.h: -------------------------------------------------------------------------------- 1 | #define length(a) (sizeof(a)/sizeof((a)[0])) 2 | #define __packed __attribute__((packed)) 3 | 4 | #define NTP_PORT 123 5 | 6 | #define NTP_PACKET \ 7 | uint8_t li_vn_mode; /* 2b Leap Indicator, 3b Version Number, 3b Association Mode */\ 8 | uint8_t stratum;\ 9 | uint8_t poll; /* msg interval in log2 sec */\ 10 | uint8_t precision; /* precision of the system clock in log2 sec */\ 11 | uint32_t root_delay; /* total round-trip delay to the ref clock */\ 12 | uint32_t root_disp; /* root dispersion (total disp. to the ref clk) */\ 13 | uint32_t ref_id; /* reference ID (usually ASCII printable) */\ 14 | uint64_t ref_tstamp; /* reference timestamp (time when sys clk was last set/corrected) */\ 15 | uint64_t org_tstamp; /* origin timestamp */\ 16 | uint64_t rec_tstamp; /* receive timestamp */\ 17 | uint64_t xmt_tstamp; /* transmit timestamp */ 18 | 19 | typedef struct { 20 | NTP_PACKET 21 | } __packed ntp_pkt_pkd_t; 22 | 23 | typedef struct { 24 | NTP_PACKET 25 | } /* not packed */ ntp_pkt_t; 26 | 27 | char *ipaddr2str(char *s, const uint8_t *addr, const bool is_ipv6); 28 | 29 | #ifdef SQL 30 | #ifndef DEFAULT_SQL_DB 31 | #define DEFAULT_SQL_DB "/buben/cas/orloj.db" 32 | #endif 33 | #define LOG_CLK_ID CLOCK_MONOTONIC_RAW 34 | void sql_log(char *msg); 35 | void sql_output_ntp_packet(bool ipv6, 36 | const uint8_t *src_ip, uint16_t src_port, 37 | const uint8_t *dst_ip, uint16_t dst_port, 38 | const struct timeval *tstamp, /* actually in nsec */ 39 | ntp_pkt_t *ntp); 40 | void sql_init(char *db_fname); 41 | void sql_close(); 42 | #endif 43 | -------------------------------------------------------------------------------- /clk_sync_observer/clxync_obsv_sql.c: -------------------------------------------------------------------------------- 1 | #ifdef SQL 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include "clxync_obsv.h" 14 | 15 | static sqlite3 *db; 16 | static sqlite3_stmt *ins_log_exe, *ins_ntp_tx_exe, *ins_ntp_rx_exe; 17 | 18 | int sql_rc(int rc) { 19 | switch (rc) { 20 | case SQLITE_OK: 21 | case SQLITE_ROW: 22 | case SQLITE_DONE: 23 | case SQLITE_BUSY: 24 | return rc; 25 | default: 26 | fprintf(stderr, "sqlite error (%d): %s\n", rc, sqlite3_errmsg(db)); 27 | exit(rc); 28 | } 29 | } 30 | 31 | void sql_log(char *msg) { 32 | struct timespec ts; 33 | int i; 34 | clock_gettime(LOG_CLK_ID, &ts); 35 | if (msg[(i = strlen(msg)-1)] == '\n') 36 | msg[i] = '\0'; 37 | sql_rc(sqlite3_bind_int64(ins_log_exe, 1, ts.tv_sec)); 38 | sql_rc(sqlite3_bind_int64(ins_log_exe, 2, ts.tv_nsec)); 39 | sql_rc(sqlite3_bind_text(ins_log_exe, 3, msg, -1, SQLITE_STATIC)); 40 | while (sql_rc(sqlite3_step(ins_log_exe)) == SQLITE_BUSY) { 41 | fprintf(stderr, "db busy: wait & retry\n"); 42 | usleep(1000); 43 | } 44 | sql_rc(sqlite3_reset(ins_log_exe)); 45 | } 46 | 47 | /* * */ 48 | 49 | typedef struct { 50 | uint16_t src_port, dst_port; 51 | char src_addr_s[INET6_ADDRSTRLEN+1], dst_addr_s[INET6_ADDRSTRLEN+1]; 52 | uint64_t tx_tstamp; 53 | int64_t db_id; 54 | } ntp_info_t; 55 | 56 | #define PKT_BUF_LEN 256 /* must be 2^n */ 57 | static ntp_info_t pkt_buf[PKT_BUF_LEN]; 58 | static unsigned pkt_buf_idx = 0; 59 | 60 | static inline unsigned next_idx(unsigned idx) { 61 | ++idx; 62 | idx &= PKT_BUF_LEN-1; 63 | return idx; 64 | } 65 | 66 | static inline unsigned prev_idx(unsigned idx) { 67 | --idx; 68 | idx &= PKT_BUF_LEN-1; 69 | return idx; 70 | } 71 | 72 | int find_rx_packet(ntp_info_t *ntp_tx) { 73 | int i, idx = pkt_buf_idx; 74 | ntp_info_t *nfo; 75 | for (i = 0; i < PKT_BUF_LEN; i++) { 76 | nfo = pkt_buf + idx; 77 | if ((nfo->src_port == NTP_PORT) && 78 | (strcmp(nfo->src_addr_s, ntp_tx->dst_addr_s) == 0) && 79 | (strcmp(nfo->dst_addr_s, ntp_tx->src_addr_s) == 0) && 80 | (nfo->tx_tstamp == ntp_tx->tx_tstamp)) 81 | return idx; 82 | idx = prev_idx(idx); 83 | } 84 | return -1; 85 | } 86 | 87 | int find_tx_packet(ntp_info_t *ntp_rx) { 88 | int i, idx = pkt_buf_idx; 89 | ntp_info_t *nfo; 90 | for (i = 0; i < PKT_BUF_LEN; i++) { 91 | nfo = pkt_buf + idx; 92 | if ((nfo->dst_port == NTP_PORT) && 93 | (strcmp(nfo->src_addr_s, ntp_rx->dst_addr_s) == 0) && 94 | (strcmp(nfo->dst_addr_s, ntp_rx->src_addr_s) == 0) && 95 | (nfo->tx_tstamp == ntp_rx->tx_tstamp)) 96 | return idx; 97 | idx = prev_idx(idx); 98 | } 99 | return -1; 100 | } 101 | 102 | void put_packet(ntp_info_t *ntp) { 103 | int idx = next_idx(pkt_buf_idx); 104 | memcpy(pkt_buf + idx, ntp, sizeof(ntp_info_t)); 105 | } 106 | 107 | static inline uint32_t hi32(uint64_t w) { 108 | w >>= 32; 109 | return (uint32_t)w; 110 | } 111 | 112 | static inline uint32_t lo32(uint64_t w) { 113 | return (uint32_t)w; 114 | } 115 | 116 | void sql_output_ntp_packet(bool ipv6, 117 | const uint8_t *src_ip, uint16_t src_port, 118 | const uint8_t *dst_ip, uint16_t dst_port, 119 | const struct timeval *tstamp, /* actually in nsec */ 120 | ntp_pkt_t *ntp) { 121 | sqlite3_stmt *ins_exe; 122 | ntp_info_t nfo; 123 | int pair_idx; 124 | int (*find_packet)(ntp_info_t *ntp_rx); 125 | struct timespec ts; 126 | clock_gettime(LOG_CLK_ID, &ts); 127 | /* determine, whether we have got tx or rx packet; 128 | simply assume, that client's local port will never be equal to 123; 129 | in case of error, file it under rx 130 | */ 131 | if (dst_port == NTP_PORT) { 132 | /* tx pkt */ 133 | ins_exe = ins_ntp_tx_exe; 134 | find_packet = find_rx_packet; 135 | nfo.tx_tstamp = ntp->xmt_tstamp; 136 | } 137 | else { 138 | /* rx pkt */ 139 | ins_exe = ins_ntp_rx_exe; 140 | find_packet = find_tx_packet; 141 | nfo.tx_tstamp = ntp->org_tstamp; 142 | } 143 | nfo.src_port = src_port; 144 | nfo.dst_port = dst_port; 145 | ipaddr2str(nfo.src_addr_s, src_ip, ipv6); 146 | ipaddr2str(nfo.dst_addr_s, dst_ip, ipv6); 147 | /* find counterpart from other direction, if already in the buffer */ 148 | pair_idx = find_packet(&nfo); 149 | /* insert into db */ 150 | int i = 0; 151 | if (pair_idx != -1) { 152 | sql_rc(sqlite3_bind_int64(ins_exe, ++i, pkt_buf[pair_idx].db_id)); 153 | } 154 | else { 155 | sql_rc(sqlite3_bind_null(ins_exe, ++i)); 156 | } 157 | /* INSERT packet */ 158 | /* IP src/dst */ 159 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ipv6)); 160 | sql_rc(sqlite3_bind_text (ins_exe, ++i, nfo.src_addr_s, -1, SQLITE_STATIC)); 161 | sql_rc(sqlite3_bind_int (ins_exe, ++i, src_port)); 162 | sql_rc(sqlite3_bind_text (ins_exe, ++i, nfo.dst_addr_s, -1, SQLITE_STATIC)); 163 | sql_rc(sqlite3_bind_int (ins_exe, ++i, dst_port)); 164 | /* very coarse timestamp for SQL index/sort only */ 165 | sql_rc(sqlite3_bind_int64(ins_exe, ++i, ts.tv_sec)); 166 | sql_rc(sqlite3_bind_int64(ins_exe, ++i, ts.tv_nsec)); 167 | /* our (pcap) timestamp */ 168 | sql_rc(sqlite3_bind_int64(ins_exe, ++i, tstamp->tv_sec)); 169 | sql_rc(sqlite3_bind_int64(ins_exe, ++i, tstamp->tv_usec /*act.nsec*/)); 170 | /* NTP payload */ 171 | uint8_t 172 | li = ntp->li_vn_mode >> 6, 173 | vn = (ntp->li_vn_mode >> 3) & 0x7, 174 | mode = (ntp->li_vn_mode >> 0) & 0x7; 175 | sql_rc(sqlite3_bind_int (ins_exe, ++i, li)); 176 | sql_rc(sqlite3_bind_int (ins_exe, ++i, vn)); 177 | sql_rc(sqlite3_bind_int (ins_exe, ++i, mode)); 178 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ntp->poll)); 179 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ntp->precision)); 180 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ntp->root_delay)); 181 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ntp->root_disp)); 182 | sql_rc(sqlite3_bind_int (ins_exe, ++i, ntp->ref_id)); 183 | sql_rc(sqlite3_bind_int (ins_exe, ++i, hi32(ntp->ref_tstamp))); 184 | sql_rc(sqlite3_bind_int (ins_exe, ++i, lo32(ntp->ref_tstamp))); 185 | sql_rc(sqlite3_bind_int (ins_exe, ++i, hi32(ntp->org_tstamp))); 186 | sql_rc(sqlite3_bind_int (ins_exe, ++i, lo32(ntp->org_tstamp))); 187 | sql_rc(sqlite3_bind_int (ins_exe, ++i, hi32(ntp->rec_tstamp))); 188 | sql_rc(sqlite3_bind_int (ins_exe, ++i, lo32(ntp->rec_tstamp))); 189 | sql_rc(sqlite3_bind_int (ins_exe, ++i, hi32(ntp->xmt_tstamp))); 190 | sql_rc(sqlite3_bind_int (ins_exe, ++i, lo32(ntp->xmt_tstamp))); 191 | /* go fight for a lock! */ 192 | while (sql_rc(sqlite3_step(ins_exe)) == SQLITE_BUSY) { 193 | fprintf(stderr, "db busy: wait & retry\n"); 194 | usleep(1000); 195 | } 196 | nfo.db_id = sqlite3_last_insert_rowid(db); 197 | sql_rc(sqlite3_reset(ins_exe)); 198 | /* if not paired, put into the buffer */ 199 | if (pair_idx == -1) { 200 | put_packet(&nfo); 201 | } 202 | } 203 | 204 | void sql_init(char *db_fname) { 205 | const char 206 | ins_log_cmd[] = 207 | "INSERT INTO log VALUES (?,?,'clxync_obsv',?)", 208 | ins_ntp_tx_cmd[] = 209 | "INSERT INTO ntp_tx " 210 | "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", 211 | ins_ntp_rx_cmd[] = 212 | "INSERT INTO ntp_rx " 213 | "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"; 214 | sql_rc(sqlite3_open(db_fname, &db)); 215 | sql_rc(sqlite3_prepare(db, ins_log_cmd, -1, &ins_log_exe, NULL)); 216 | sql_rc(sqlite3_prepare(db, ins_ntp_tx_cmd, -1, &ins_ntp_tx_exe, NULL)); 217 | sql_rc(sqlite3_prepare(db, ins_ntp_rx_cmd, -1, &ins_ntp_rx_exe, NULL)); 218 | } 219 | 220 | void sql_close() { 221 | sql_rc(sqlite3_finalize(ins_log_exe)); 222 | sql_rc(sqlite3_finalize(ins_ntp_tx_exe)); 223 | sql_rc(sqlite3_finalize(ins_ntp_rx_exe)); 224 | sql_rc(sqlite3_close(db)); 225 | } 226 | 227 | #endif /*SQL*/ 228 | -------------------------------------------------------------------------------- /clk_sync_observer/orloj_init_db.sql: -------------------------------------------------------------------------------- 1 | create table log ( 2 | t_sec integer, t_nsec integer, head varchar, tail varchar 3 | ); 4 | create table gps_nmea ( 5 | t_sec integer, t_nsec integer, head varchar, tail varchar 6 | ); 7 | create table ntimed_trace ( 8 | t_sec integer, t_nsec integer, head varchar, tail varchar 9 | ); 10 | 11 | create table os_clk ( 12 | cnt_sec0 integer, 13 | cnt_tick0 integer, cnt_tick1 integer, cnt_tick2 integer, cnt_tick3 integer, 14 | raw_sec1 integer, raw_nsec1 integer, raw_nsec2 integer, raw_nsec3 integer, 15 | adj_sec1 integer, adj_nsec1 integer, adj_nsec2 integer, adj_nsec3 integer 16 | ); 17 | create table ref_clk ( 18 | raw_sec integer, raw_nsec integer, 19 | cnt_sec integer, cnt_tick integer 20 | ); 21 | 22 | create table ntp_rx ( 23 | ntp_tx_id integer, 24 | ipv6 boolean, 25 | src_addr varchar, 26 | src_port integer, 27 | dst_addr varchar, 28 | dst_port integer, 29 | raw_sec integer, 30 | raw_nsec intefer, 31 | pkt_tstamp_sec integer, 32 | pkt_tstamp_nsec integer, 33 | li integer, 34 | vn integer, 35 | mode integer, 36 | poll integer, 37 | prec integer, 38 | root_delay integer, 39 | root_disp integer, 40 | ref_id integer, 41 | ref_ts_hi integer, 42 | ref_ts_lo integer, 43 | org_ts_hi integer, 44 | org_ts_lo integer, 45 | rec_ts_hi integer, 46 | rec_ts_lo integer, 47 | xmt_ts_hi integer, 48 | xmt_ts_lo integer 49 | ); 50 | 51 | create table ntp_tx ( 52 | ntp_rx_id integer, 53 | ipv6 boolean, 54 | src_addr varchar, 55 | src_port integer, 56 | dst_addr varchar, 57 | dst_port integer, 58 | raw_sec integer, 59 | raw_nsec intefer, 60 | pkt_tstamp_sec integer, 61 | pkt_tstamp_nsec integer, 62 | li integer, 63 | vn integer, 64 | mode integer, 65 | poll integer, 66 | prec integer, 67 | root_delay integer, 68 | root_disp integer, 69 | ref_id integer, 70 | ref_ts_hi integer, 71 | ref_ts_lo integer, 72 | org_ts_hi integer, 73 | org_ts_lo integer, 74 | rec_ts_hi integer, 75 | rec_ts_lo integer, 76 | xmt_ts_hi integer, 77 | xmt_ts_lo integer 78 | ); 79 | 80 | create index ix_log1 on log(t_sec,t_nsec); 81 | create index ix_log2 on log(head,t_sec,t_nsec); 82 | 83 | create index ix_gps_nmea1 on gps_nmea(t_sec,t_nsec); 84 | create index ix_gps_nmea2 on gps_nmea(head,t_sec,t_nsec); 85 | 86 | create index ix_ntimed1 on ntimed_trace(t_sec,t_nsec); 87 | create index ix_ntimed2 on ntimed_trace(head,t_sec,t_nsec); 88 | 89 | create index ix_ref_clk1 on ref_clk(raw_sec,raw_nsec); 90 | 91 | create index ix_os_clk1 on os_clk(raw_sec1,raw_nsec1); 92 | 93 | create index ix_ntp_rx1 on ntp_rx(raw_sec,raw_nsec); 94 | create index ix_ntp_rx2 on ntp_rx(src_addr,raw_sec,raw_nsec); 95 | 96 | create index ix_ntp_tx1 on ntp_rx(raw_sec,raw_nsec); 97 | create index ix_ntp_tx2 on ntp_rx(dst_addr,raw_sec,raw_nsec); 98 | -------------------------------------------------------------------------------- /clk_sync_observer/refclk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "refclk.h" 11 | 12 | uint32_t *mm_ctrl, *tmem_cpu, *tmem_refclk; 13 | 14 | #define TSTAMP_CTRL_ADDR 0x43c00000 15 | #define TSTAMP_CTRL_LEN (4*4) 16 | #define TSTAMP_MEM0_ADDR 0x40000000 17 | #define TSTAMP_MEM1_ADDR 0x40010000 18 | #define TSTAMP_MEM_LEN (256) 19 | #define TSTAMP_MEM_BLEN (8*TSTAMP_MEM_LEN) 20 | 21 | #define F_CCLK 200000000 /*Hz*/ 22 | #define TTAG_MASK 0xff000000 23 | 24 | #define __mb() \ 25 | __asm__ __volatile__("dmb": : : "memory") 26 | 27 | #define __mb_smp() \ 28 | __asm__ __volatile__("dmb ish": : : "memory") 29 | 30 | static char *memdev = "/dev/mem"; 31 | static int mem_fd = -1; 32 | 33 | static int mem_open() { 34 | mem_fd = open(memdev, O_RDWR|O_SYNC); 35 | if (mem_fd < 0) { 36 | perror("open memory device"); 37 | return -1; 38 | } 39 | return 0; 40 | } 41 | 42 | void *mem_map(unsigned long mem_start, unsigned long mem_length) { 43 | unsigned long pagesize, mem_window_size; 44 | void *mm, *mem; 45 | //pagesize = getpagesize(); 46 | pagesize = sysconf(_SC_PAGESIZE); 47 | 48 | mem_window_size = 49 | ((mem_start & (pagesize-1)) + mem_length + pagesize-1) & ~(pagesize-1); 50 | mm = mmap(NULL, mem_window_size, PROT_WRITE|PROT_READ, 51 | MAP_SHARED, mem_fd, mem_start & ~(pagesize-1)); 52 | mem = mm + (mem_start & (pagesize-1)); 53 | if (mm == MAP_FAILED) { 54 | perror("mmap"); 55 | return NULL; 56 | } 57 | fprintf(stderr, "mmap 0x%lx -> %p\n", mem_start, mem); 58 | return mem; 59 | } 60 | 61 | static inline void tstamp_cpu(uint32_t tag) { 62 | mm_ctrl[0] = tag; 63 | __mb(); 64 | } 65 | 66 | static inline uint32_t tstamp_read(uint32_t *tmem, int offset, uint32_t *tag) { 67 | unsigned idx = offset & (TSTAMP_MEM_LEN-1); /* len must be 2^n */ 68 | /* 1 record = 2 32-bit words */ 69 | idx <<= 1; 70 | if (tag) 71 | *tag = tmem[idx+1]; 72 | return tmem[idx+0]; 73 | } 74 | 75 | static inline int tstamp_next_idx(int idx) { 76 | ++idx; 77 | idx &= TSTAMP_MEM_LEN-1; 78 | return idx; 79 | } 80 | 81 | int tstamp_init() { 82 | mem_open(); 83 | uint32_t *mm_sysctl = mem_map(0xf8000000, 0xb78); 84 | /* override FCLK1 freq. to periph clk (1GHz) / 5 = 200MHz */ 85 | mm_sysctl[0x180/4] = 0x00100500; 86 | munmap(mm_sysctl, 0xb78); 87 | /* map timestamp ring-buffers */ 88 | tmem_cpu = mem_map(TSTAMP_MEM0_ADDR, TSTAMP_MEM_LEN); 89 | tmem_refclk = mem_map(TSTAMP_MEM1_ADDR, TSTAMP_MEM_LEN); 90 | /* set up timestamping */ 91 | mm_ctrl = mem_map(TSTAMP_CTRL_ADDR, TSTAMP_CTRL_LEN); 92 | mm_ctrl[2] = F_CCLK-1; 93 | mm_ctrl[1] = TTAG_MASK; 94 | return 0; 95 | } 96 | 97 | void tstamp_close() { 98 | munmap(mm_ctrl, TSTAMP_CTRL_LEN); 99 | munmap(tmem_cpu, TSTAMP_MEM_LEN); 100 | munmap(tmem_refclk, TSTAMP_MEM_LEN); 101 | } 102 | 103 | /* * */ 104 | 105 | uint32_t old_sec[TSTAMP_MEM_LEN]; 106 | 107 | void ref_pps_init() { 108 | int i; 109 | for (i = 0; i < TSTAMP_MEM_LEN; i++) 110 | tstamp_read(tmem_refclk, i, old_sec + i); 111 | } 112 | 113 | int ref_pps_detect() { 114 | uint32_t sec; 115 | int i; 116 | for (i = 0; i < TSTAMP_MEM_LEN; i++) { 117 | tstamp_read(tmem_refclk, i, &sec); 118 | if (sec != old_sec[i]) 119 | return i; 120 | } 121 | return -1; 122 | } 123 | 124 | int ref_pps_get(int *idx, uint32_t *sec, uint32_t *tick) { 125 | *tick = tstamp_read(tmem_refclk, *idx, sec); 126 | if (*sec != old_sec[*idx]) { 127 | old_sec[*idx] = *sec; 128 | *idx = tstamp_next_idx(*idx); 129 | return 1; 130 | } 131 | return 0; 132 | } 133 | 134 | void output_os_clk(uint32_t *ts_sec, uint32_t *ts_tick, 135 | struct timespec *t_raw, struct timespec *t_adj) { 136 | printf("C(0)=%u.%09u C(1)=%u.%09u C(2)=%u.%09u C(3)=%u.%09u\n" 137 | "R(0)=%lu.%09lu R(1)=%lu.%09lu R(2)=%lu.%09lu\n" 138 | "A(0)=%lu.%09lu A(1)=%lu.%09lu A(2)=%lu.%09lu\n\n", 139 | ts_sec[0],ts_tick[0], ts_sec[1],ts_tick[1], 140 | ts_sec[2],ts_tick[2], ts_sec[3],ts_tick[3], 141 | t_raw[0].tv_sec, t_raw[0].tv_nsec, 142 | t_raw[1].tv_sec, t_raw[1].tv_nsec, 143 | t_raw[2].tv_sec, t_raw[2].tv_nsec, 144 | t_adj[0].tv_sec, t_adj[0].tv_nsec, 145 | t_adj[1].tv_sec, t_adj[1].tv_nsec, 146 | t_adj[2].tv_sec, t_adj[2].tv_nsec); 147 | } 148 | 149 | void output_ref_clk(uint32_t sec, uint32_t tick) { 150 | printf("ext1pps C=%u.%09u\n", sec, tick); 151 | } 152 | 153 | int sample_os_clocks(int *ts_idx) { 154 | struct timespec t_raw[3], t_adj[3]; 155 | uint32_t ts_sec[4], ts_tick[4]; 156 | int i; 157 | /* sample clocks */ 158 | tstamp_cpu(1U << 24); 159 | clock_gettime(CLK_RAW, t_raw + 0); 160 | clock_gettime(CLK_ADJ, t_adj + 0); 161 | tstamp_cpu(2U << 24); 162 | clock_gettime(CLK_RAW, t_raw + 1); 163 | clock_gettime(CLK_ADJ, t_adj + 1); 164 | tstamp_cpu(3U << 24); 165 | clock_gettime(CLK_RAW, t_raw + 2); 166 | clock_gettime(CLK_ADJ, t_adj + 2); 167 | tstamp_cpu(4U << 24); 168 | /* gather HW timestamps */ 169 | __mb(); 170 | for (i = 0; i < 4; i++) { 171 | ts_tick[i] = tstamp_read(tmem_cpu, *ts_idx, ts_sec + i); 172 | /* check tag */ 173 | if ((ts_sec[i] & TTAG_MASK) != ((i+1) << 24)) { 174 | fprintf(stderr, "ERR: tstamp memory tag mismatch (expect:%x, read:%x)!\n", 175 | ((i+1) << 24), ts_sec[i] & TTAG_MASK); 176 | return -1; 177 | } 178 | ts_sec[i] &= ~TTAG_MASK; 179 | *ts_idx = tstamp_next_idx(*ts_idx); 180 | } 181 | output_os_clk(ts_sec, ts_tick, t_raw, t_adj); 182 | #ifdef SQL 183 | sql_output_os_clk(ts_sec, ts_tick, t_raw, t_adj); 184 | #endif 185 | return 0; 186 | } 187 | 188 | int cpu_tstamp_init(int *idx) { 189 | uint32_t tag; 190 | int i; 191 | /* clear buffer */ 192 | for (i = 0; i < TSTAMP_MEM_LEN; i++) 193 | tstamp_cpu(0); 194 | /* mark & find this last record */ 195 | tstamp_cpu(1U << 24); 196 | for (i = 0; i < TSTAMP_MEM_LEN; i++) { 197 | tstamp_read(tmem_cpu, i, &tag); 198 | if (tag & TTAG_MASK) { 199 | *idx = tstamp_next_idx(i); 200 | return 0; 201 | } 202 | } 203 | fprintf(stderr, "ERR: unable to initialize cpu tstamp memory!\n"); 204 | return -1; 205 | } 206 | 207 | void main_loop() { 208 | int cpu_idx, refclk_idx = -1; 209 | uint32_t sec, tick; 210 | struct timespec ts; 211 | /* init */ 212 | if (cpu_tstamp_init(&cpu_idx)) 213 | return; 214 | fprintf(stderr, "cpu timestamp pointer found @offset=%u\n", cpu_idx); 215 | __mb(); 216 | ref_pps_init(); 217 | /* loop */ 218 | for (;;) { 219 | clock_gettime(CLOCK_MONOTONIC, &ts); 220 | /* take internal clocks measurement */ 221 | sample_os_clocks(&cpu_idx); 222 | /* check ext 1pps */ 223 | if (refclk_idx == -1) { 224 | /* 1pps not yet encountered */ 225 | refclk_idx = ref_pps_detect(); 226 | if (refclk_idx != -1) { 227 | fprintf(stderr, "1pps detected @offset=%d\n", refclk_idx); 228 | } 229 | } 230 | else { 231 | while (ref_pps_get(&refclk_idx, &sec, &tick)) { 232 | sec &= ~TTAG_MASK; 233 | output_ref_clk(sec, tick); 234 | #ifdef SQL 235 | sql_output_ref_clk(sec, tick); 236 | #endif 237 | } 238 | } 239 | /* wait for next sample */ 240 | if ((ts.tv_nsec += PERIOD_NSEC) >= 1000000000) { 241 | ++ts.tv_sec; 242 | ts.tv_nsec -= 1000000000; 243 | } 244 | clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL); 245 | } 246 | } 247 | 248 | int main(int argc, char *argv[]) { 249 | fprintf(stderr, "init\n"); 250 | #ifdef SQL 251 | sql_init((argc == 2) ? argv[1] : DEFAULT_SQL_DB); 252 | sql_log("init"); 253 | #endif 254 | tstamp_init(); 255 | main_loop(); 256 | /* never shall get here: severe OS/HW error */ 257 | fprintf(stderr, "fatal error: closing\n"); 258 | #ifdef SQL 259 | sql_close(); 260 | #endif 261 | tstamp_close(); 262 | return -1; 263 | } 264 | -------------------------------------------------------------------------------- /clk_sync_observer/refclk.h: -------------------------------------------------------------------------------- 1 | #define PERIOD_NSEC 62500000 /*16Hz*/ 2 | 3 | #define CLK_RAW CLOCK_MONOTONIC_RAW 4 | #define CLK_ADJ CLOCK_REALTIME /* slower, more accurate */ 5 | //#define CLK_ADJ CLOCK_REALTIME_COARSE /* faster, less accurate */ 6 | 7 | #ifdef SQL 8 | #ifndef DEFAULT_SQL_DB 9 | #define DEFAULT_SQL_DB "/buben/cas/orloj.db" 10 | #endif 11 | #define LOG_CLK_ID CLK_RAW 12 | void sql_log(char *msg); 13 | void sql_output_os_clk(uint32_t *ts_sec, uint32_t *ts_tick, 14 | struct timespec *t_raw, struct timespec *t_adj); 15 | void sql_output_ref_clk(uint32_t sec, uint32_t tick); 16 | void sql_init(char *db_fname); 17 | void sql_close(); 18 | #endif 19 | -------------------------------------------------------------------------------- /clk_sync_observer/refclk_sql.c: -------------------------------------------------------------------------------- 1 | #ifdef SQL 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include "refclk.h" 12 | 13 | static sqlite3 *db; 14 | static sqlite3_stmt *ins_log_exe, *ins_os_clk_exe, *ins_ref_clk_exe; 15 | 16 | int sql_rc(int rc) { 17 | switch (rc) { 18 | case SQLITE_OK: 19 | case SQLITE_ROW: 20 | case SQLITE_DONE: 21 | case SQLITE_BUSY: 22 | return rc; 23 | default: 24 | fprintf(stderr, "sqlite error (%d): %s\n", rc, sqlite3_errmsg(db)); 25 | exit(rc); 26 | } 27 | } 28 | 29 | void sql_log(char *msg) { 30 | struct timespec ts; 31 | int i; 32 | clock_gettime(LOG_CLK_ID, &ts); 33 | if (msg[(i = strlen(msg)-1)] == '\n') 34 | msg[i] = '\0'; 35 | sql_rc(sqlite3_bind_int64(ins_log_exe, 1, ts.tv_sec)); 36 | sql_rc(sqlite3_bind_int64(ins_log_exe, 2, ts.tv_nsec)); 37 | sql_rc(sqlite3_bind_text(ins_log_exe, 3, msg, -1, SQLITE_STATIC)); 38 | while (sql_rc(sqlite3_step(ins_log_exe)) == SQLITE_BUSY) { 39 | fprintf(stderr, "db busy: wait & retry\n"); 40 | usleep(1000); 41 | } 42 | sql_rc(sqlite3_reset(ins_log_exe)); 43 | } 44 | 45 | void sql_output_os_clk(uint32_t *ts_sec, uint32_t *ts_tick, 46 | struct timespec *t_raw, struct timespec *t_adj) { 47 | /* table os_clk ( 48 | cnt_sec0 integer, 49 | cnt_tick0 integer, cnt_tick1 integer, cnt_tick2 integer, cnt_tick3 integer, 50 | raw_sec0 integer, raw_nsec1 integer, raw_nsec2 integer, raw_nsec3 integer, 51 | adj_sec0 integer, adj_nsec1 integer, adj_nsec2 integer, adj_nsec3 integer) 52 | */ 53 | int i = 0; 54 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, ts_sec[0])); 55 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, ts_tick[0])); 56 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, ts_tick[1])); 57 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, ts_tick[2])); 58 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, ts_tick[3])); 59 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_raw[0].tv_sec)); 60 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_raw[0].tv_nsec)); 61 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_raw[1].tv_nsec)); 62 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_raw[2].tv_nsec)); 63 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_adj[0].tv_sec)); 64 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_adj[0].tv_nsec)); 65 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_adj[1].tv_nsec)); 66 | sql_rc(sqlite3_bind_int64(ins_os_clk_exe, ++i, t_adj[2].tv_nsec)); 67 | while (sql_rc(sqlite3_step(ins_os_clk_exe)) == SQLITE_BUSY) { 68 | fprintf(stderr, "db busy: wait & retry\n"); 69 | usleep(1000); 70 | } 71 | sql_rc(sqlite3_reset(ins_os_clk_exe)); 72 | } 73 | 74 | void sql_output_ref_clk(uint32_t sec, uint32_t tick) { 75 | /* table ref_clk ( 76 | raw_sec integer, raw_nsec integer, 77 | cnt_sec integer, cnt_tick integer) 78 | */ 79 | int i = 0; 80 | /* get approximate raw clk time to be used in indexing */ 81 | struct timespec ts; 82 | clock_gettime(CLK_RAW, &ts); 83 | sql_rc(sqlite3_bind_int64(ins_ref_clk_exe, ++i, ts.tv_sec)); 84 | sql_rc(sqlite3_bind_int64(ins_ref_clk_exe, ++i, ts.tv_nsec)); 85 | sql_rc(sqlite3_bind_int64(ins_ref_clk_exe, ++i, sec)); 86 | sql_rc(sqlite3_bind_int64(ins_ref_clk_exe, ++i, tick)); 87 | while (sql_rc(sqlite3_step(ins_ref_clk_exe)) == SQLITE_BUSY) { 88 | fprintf(stderr, "db busy: wait & retry\n"); 89 | usleep(1000); 90 | } 91 | sql_rc(sqlite3_reset(ins_ref_clk_exe)); 92 | } 93 | 94 | void sql_init(char *db_fname) { 95 | const char 96 | ins_log_cmd[] = 97 | "INSERT INTO log VALUES (?,?,'refclk',?)", 98 | ins_os_clk_cmd[] = 99 | "INSERT INTO os_clk VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", 100 | ins_ref_clk_cmd[] = 101 | "INSERT INTO ref_clk VALUES (?,?,?,?)"; 102 | sql_rc(sqlite3_open(db_fname, &db)); 103 | sql_rc(sqlite3_prepare(db, ins_log_cmd, -1, &ins_log_exe, NULL)); 104 | sql_rc(sqlite3_prepare(db, ins_os_clk_cmd, -1, &ins_os_clk_exe, NULL)); 105 | sql_rc(sqlite3_prepare(db, ins_ref_clk_cmd, -1, &ins_ref_clk_exe, NULL)); 106 | } 107 | 108 | void sql_close() { 109 | sql_rc(sqlite3_finalize(ins_log_exe)); 110 | sql_rc(sqlite3_finalize(ins_os_clk_exe)); 111 | sql_rc(sqlite3_finalize(ins_ref_clk_exe)); 112 | sql_rc(sqlite3_close(db)); 113 | } 114 | 115 | #endif /*SQL*/ 116 | -------------------------------------------------------------------------------- /clk_sync_observer/txtpipe2sql.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define CLK_ID CLOCK_MONOTONIC_RAW 11 | #define MAX_STR_LEN 1023 12 | 13 | typedef struct record_t { 14 | struct timespec tstamp; 15 | char *head, *tail; 16 | } record_t; 17 | 18 | static sqlite3 *db; 19 | static int max_head_len = 16; 20 | static char *tail_separators = " \f\n\r\t\v"; 21 | 22 | int sql_rc(int rc) { 23 | switch (rc) { 24 | case SQLITE_OK: 25 | case SQLITE_ROW: 26 | case SQLITE_DONE: 27 | case SQLITE_BUSY: 28 | return rc; 29 | default: 30 | fprintf(stderr, "sqlite error (%d): %s\n", rc, sqlite3_errmsg(db)); 31 | exit(rc); 32 | } 33 | } 34 | 35 | int get_data(record_t *rec) { 36 | static char s[MAX_STR_LEN+1], head[MAX_STR_LEN+1]; 37 | int i; 38 | if (fgets(s, sizeof(s), stdin) == NULL) 39 | return -1; 40 | clock_gettime(CLK_ID, &rec->tstamp); 41 | /* remove trailing newline */ 42 | i = strlen(s); 43 | s[i-1] = '\0'; 44 | /* split log msg into indexing head and the rest of msg */ 45 | for (i = 0; s[i] && (i < max_head_len); i++) { 46 | if (strchr(tail_separators, s[i])) 47 | break; 48 | head[i] = s[i]; 49 | } 50 | head[i] = '\0'; 51 | rec->head = head; 52 | rec->tail = s + i; 53 | return 0; 54 | } 55 | 56 | void usage(char *self_name) { 57 | printf( 58 | "txtpipe2sql -- redirects plain text stdin into timestamped SQLite table\n" 59 | "usage: %s [-t table_name] [-m max_head_chars] [-d head_delim] sqlite_db_file\n" 60 | " -t table name, default: \"log\"\n" 61 | " -m maximum number of characters going into \"head\" field\n" 62 | " -d possible delimiter chars between \"head\" and \"tail\"\n\n", 63 | self_name); 64 | } 65 | 66 | int main(int argc, char *argv[]) { 67 | const char insert_cmd_proto[] = 68 | "INSERT INTO %s VALUES (?,?,?,?)"; 69 | char *insert_cmd; 70 | sqlite3_stmt *insert_exe; 71 | record_t rec; 72 | char *db_fname = NULL, *table_name = "log"; 73 | 74 | //-t tab_name -m max_head_chars -d head_delimiters db_name 75 | int opt, n; 76 | optind = opterr = 0; 77 | while ((opt = getopt(argc, argv, ":ht:m:d:")) != -1) 78 | switch (opt) { 79 | case 'h': 80 | usage(argv[0]); 81 | return 0; 82 | break; 83 | case 't': 84 | table_name = optarg; 85 | break; 86 | case 'm': 87 | n = atoi(optarg); 88 | if (n < 1) { 89 | fprintf(stderr, "WARN: invalid max_head_chars=%d, leaving default %d\n", 90 | n, max_head_len); 91 | } 92 | else if (n > MAX_STR_LEN) { 93 | max_head_len = MAX_STR_LEN; 94 | fprintf(stderr, "WARN: max_head_chars too large, setting to %d\n", 95 | max_head_len); 96 | } 97 | else { 98 | max_head_len = n; 99 | } 100 | break; 101 | case 'd': 102 | tail_separators = optarg; 103 | break; 104 | case ':': 105 | fprintf(stderr, "command line option -%c requires an argument\n" 106 | "use -h for help\n", 107 | optopt); 108 | return -1; 109 | break; 110 | case '?': 111 | fprintf(stderr, "unrecognized command line option -%c\n" 112 | "use -h for help\n", 113 | optopt); 114 | return -1; 115 | break; 116 | default: 117 | break; 118 | } 119 | if (optind < argc) { 120 | if (optind > argc) { 121 | fprintf(stderr, "more than one non-option argument: %s...\n" 122 | "use -h for help\n", 123 | argv[optind+1]); 124 | return -1; 125 | } 126 | db_fname = argv[optind]; 127 | } 128 | else { 129 | fprintf(stderr, "database file name must be given\n" 130 | "use -h for help\n"); 131 | return -1; 132 | } 133 | 134 | insert_cmd = (char*)malloc(1 + strlen(insert_cmd_proto) + strlen(table_name)); 135 | if (!insert_cmd) { 136 | fprintf(stderr, "fatal: can not malloc\n"); 137 | return -1; 138 | } 139 | sprintf(insert_cmd, insert_cmd_proto, table_name); 140 | 141 | sql_rc(sqlite3_open(db_fname, &db)); 142 | sql_rc(sqlite3_prepare(db, insert_cmd, -1, &insert_exe, NULL)); 143 | for (;;) { 144 | if (get_data(&rec)) 145 | break; 146 | sql_rc(sqlite3_bind_int64(insert_exe, 1, rec.tstamp.tv_sec)); 147 | sql_rc(sqlite3_bind_int64(insert_exe, 2, rec.tstamp.tv_nsec)); 148 | sql_rc(sqlite3_bind_text(insert_exe, 3, rec.head, -1, SQLITE_STATIC)); 149 | sql_rc(sqlite3_bind_text(insert_exe, 4, rec.tail, -1, SQLITE_STATIC)); 150 | while (sql_rc(sqlite3_step(insert_exe)) == SQLITE_BUSY) { 151 | fprintf(stderr, "db busy: wait & retry\n"); 152 | usleep(1000); 153 | } 154 | sql_rc(sqlite3_reset(insert_exe)); 155 | } 156 | sql_rc(sqlite3_finalize(insert_exe)); 157 | sql_rc(sqlite3_close(db)); 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /notes: -------------------------------------------------------------------------------- 1 | pcap 2 | pcap-linux.c 3 | if (handle->opt.tstamp_type == PCAP_TSTAMP_ADAPTER) { 4 | /* 5 | * Hardware timestamp, synchronized 6 | * with the system clock. 7 | */ 8 | timesource = SOF_TIMESTAMPING_SYS_HARDWARE; 9 | } else { 10 | /* 11 | * PCAP_TSTAMP_ADAPTER_UNSYNCED - hardware 12 | * timestamp, not synchronized with the 13 | * system clock. 14 | */ 15 | timesource = SOF_TIMESTAMPING_RAW_HARDWARE; 16 | } 17 | if (setsockopt(handle->fd, SOL_PACKET, PACKET_TIMESTAMP, 18 | (void *)×ource, sizeof(timesource))) { 19 | snprintf(handle->errbuf, PCAP_ERRBUF_SIZE, 20 | "can't set PACKET_TIMESTAMP: %s", 21 | pcap_strerror(errno)); 22 | } 23 | linux-kernel/net/socket.c 24 | __sock_recv_timestamp() 25 | linux-kernel/include/linux/skbuff.h 26 | struct skb_shared_info { 27 | ... 28 | struct skb_shared_hwtstamps hwtstamps; 29 | ... 30 | } 31 | linux-kernel/net/core/skbuff.c 32 | skb_tstamp_tx() /* here is the decision between using HW or SW tstamps */ 33 | linux-kernel/drivers/net/ethernet/intel/e1000e/netdev.c 34 | e1000e_systim_to_hwtstamp() 35 | win 36 | GET_TIME(result, BASE_TIME) /* save current timestamp to result (use 37 | BASE_TIME as the time start) 38 | the driver decides between the following possibilities: 39 | if winNT 40 | 32b system: RDTSC 41 | 64b system: 42 | PTime = KeQueryPerformanceCounter(&TimeFreq); 43 | or 44 | KeQuerySystemTime(&SystemTime); 45 | else 46 | return BASE_TIME 47 | */ 48 | -------------------------------------------------------------------------------- /requirements.md: -------------------------------------------------------------------------------- 1 | accurate 2 | * OS name + version 3 | * pcap version 4 | * NTP version (present in the packet header) + configuration 5 | * HW eth card 6 | * type + vendor 7 | * driver name + version 8 | * the whole NTP packet 9 | * system time [gettimeofday()] when the frame was captured (i.e. ASAP) 10 | * system time each second (independent from the packet capturing - e.g. in second thread) 11 | 12 | 4 long-time measurements (at least 25 hours) with exactly 1 3 5 and maybe 10 stratum_servers/boundary_clock 13 | * with current NTP 14 | * with PTP|NTP utilizing the new compositing filter 15 | --------------------------------------------------------------------------------