Coverage Report

Created: 2026-06-13 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gpsd/gpsd-3.27.6~dev/drivers/driver_zodiac.c
Line
Count
Source
1
/*
2
 * Handle the Rockwell binary packet format supported by the old Zodiac chipset
3
 *
4
 * Week counters are not limited to 10 bits. It's unknown what
5
 * the firmware is doing to disambiguate them, if anything; it might just
6
 * be adding a fixed offset based on a hidden epoch value, in which case
7
 * unhappy things will occur on the next rollover.
8
 *
9
 * This file is Copyright 2010 by the GPSD project
10
 * SPDX-License-Identifier: BSD-2-clause
11
 */
12
13
#include "../include/gpsd_config.h"  // must be before all includes
14
15
#include <math.h>
16
#include <stdbool.h>
17
#include <stdio.h>
18
#include <string.h>
19
#include <unistd.h>
20
21
#include "../include/gpsd.h"
22
#include "../include/bits.h"
23
#include "../include/strfuncs.h"
24
25
// Zodiac protocol description uses 1-origin indexing by little-endian word
26
77
#define get16z(buf, n)   ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
27
389
#define getu16z(buf, n)  (uint16_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8))
28
35
#define get32z(buf, n)   ((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
29
35
                          (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
30
5
#define getu32z(buf, n)  (uint32_t)((buf[2*(n)-2]) | (buf[2*(n)-1] << 8) | \
31
5
                                    (buf[2*(n)+0] << 16) | (buf[2*(n)+1] << 24))
32
#define getstringz(to, from, s, e)                      \
33
0
    (void)memcpy(to, from+2*(s)-2, 2*((e)-(s)+1))
34
35
#ifdef ZODIAC_ENABLE
36
struct header
37
{
38
    unsigned short sync;
39
    unsigned short id;
40
    unsigned short ndata;
41
    unsigned short flags;
42
    unsigned short csum;
43
};
44
45
static unsigned short zodiac_checksum(unsigned short *w, int n)
46
0
{
47
0
    unsigned short csum = 0;
48
49
0
    while (n-- > 0)
50
0
        csum += *(w++);
51
0
    return -csum;
52
0
}
53
54
// write an array of shorts in little-endian format
55
static ssize_t end_write(int fd, void *d, size_t len)
56
0
{
57
0
    unsigned char buf[BUFSIZ];
58
0
    short *data = (short *)d;
59
60
0
    size_t n;
61
0
    for (n = 0; n < (size_t)(len/2); n++)
62
0
        putle16(buf, n*2, data[n]);
63
0
    return write(fd, (char*)buf, len);
64
0
}
65
66
/* zodiac_spew - Takes a message type, an array of data words, and a length
67
 * for the array, and prepends a 5 word header (including checksum).
68
 * The data words are expected to be checksummed.
69
 */
70
static ssize_t zodiac_spew(struct gps_device_t *session, unsigned short type,
71
                           unsigned short *dat, int dlen)
72
0
{
73
0
    struct header h;
74
0
    int i;
75
0
    char buf[BUFSIZ];
76
77
0
    h.sync = 0x81ff;
78
0
    h.id = (unsigned short)type;
79
0
    h.ndata = (unsigned short)(dlen - 1);
80
0
    h.flags = 0;
81
0
    h.csum = zodiac_checksum((unsigned short *)&h, 4);
82
83
0
    if (!BAD_SOCKET(session->gpsdata.gps_fd)) {
84
0
        size_t hlen, datlen;
85
0
        hlen = sizeof(h);
86
0
        datlen = sizeof(unsigned short) * dlen;
87
0
        if (end_write(session->gpsdata.gps_fd, &h, hlen) != (ssize_t) hlen ||
88
0
            end_write(session->gpsdata.gps_fd, dat,
89
0
                      datlen) != (ssize_t) datlen) {
90
0
            GPSD_LOG(LOG_INFO, &session->context->errout,
91
0
                     "ZODIAC: Reconfigure write failed\n");
92
0
            return -1;
93
0
        }
94
0
    }
95
96
0
    (void)snprintf(buf, sizeof(buf),
97
0
                   "%04x %04x %04x %04x %04x",
98
0
                   h.sync, h.id, h.ndata, h.flags, h.csum);
99
0
    for (i = 0; i < dlen; i++)
100
0
        str_appendf(buf, sizeof(buf), " %04x", dat[i]);
101
102
0
    GPSD_LOG(LOG_RAW, &session->context->errout,
103
0
             "ZODIAC: Sent Zodiac packet: %s\n", buf);
104
105
0
    return 0;
106
0
}
107
108
static void send_rtcm(struct gps_device_t *session,
109
                      const char *rtcmbuf, size_t rtcmbytes)
110
0
{
111
0
    unsigned short data[34];
112
0
    int n = 1 + (int)(rtcmbytes / 2 + rtcmbytes % 2);
113
114
0
    if (session->driver.zodiac.sn++ > 32767)
115
0
        session->driver.zodiac.sn = 0;
116
117
0
    memset(data, 0, sizeof(data));
118
0
    data[0] = session->driver.zodiac.sn;        // sequence number
119
0
    memcpy(&data[1], rtcmbuf, rtcmbytes);
120
0
    data[n] = zodiac_checksum(data, n);
121
122
0
    (void)zodiac_spew(session, 1351, data, n + 1);
123
0
}
124
125
static ssize_t zodiac_send_rtcm(struct gps_device_t *session,
126
                                const char *rtcmbuf, size_t rtcmbytes)
127
0
{
128
0
    while (rtcmbytes > 0) {
129
0
        size_t len = (size_t) (rtcmbytes > 64 ? 64 : rtcmbytes);
130
0
        send_rtcm(session, rtcmbuf, len);
131
0
        rtcmbytes -= len;
132
0
        rtcmbuf += len;
133
0
    }
134
0
    return 1;
135
0
}
136
137
77
#define getzword(n)     get16z(session->lexer.outbuffer, n)
138
389
#define getzu16(n)      getu16z(session->lexer.outbuffer, n)
139
35
#define getzlong(n)     get32z(session->lexer.outbuffer, n)
140
5
#define getzu32(n)      getu32z(session->lexer.outbuffer, n)
141
142
// time-position-velocity report
143
static gps_mask_t handle1000(struct gps_device_t *session)
144
5
{
145
5
    gps_mask_t mask;
146
5
    struct tm unpacked_date = {0};
147
5
    int datum;
148
5
    char ts_buf[TIMESPEC_LEN];
149
150
    // ticks                      = getzlong(6);
151
    // sequence                   = getzword(8);
152
    // measurement_sequence       = getzword(9);
153
5
    session->newdata.status = (getzword(10) & 0x1c) ? 0 : 1;
154
5
    if (0 != session->newdata.status) {
155
0
        session->newdata.mode = (getzword(10) & 1) ? MODE_2D : MODE_3D;
156
5
    } else {
157
5
        session->newdata.mode = MODE_NO_FIX;
158
5
    }
159
160
    // solution_type                 = getzword(11);
161
5
    session->gpsdata.satellites_used = (int)getzword(12);
162
    // polar_navigation              = getzword(13);
163
5
    session->context->gps_week = (unsigned short)getzword(14);
164
    // gps_seconds                   = getzlong(15);
165
    // gps_nanoseconds               = getzlong(17);
166
5
    unpacked_date.tm_mday = (int)getzword(19);
167
5
    unpacked_date.tm_mon = (int)getzword(20) - 1;
168
5
    unpacked_date.tm_year = (int)getzword(21) - 1900;
169
5
    unpacked_date.tm_hour = (int)getzword(22);
170
5
    unpacked_date.tm_min = (int)getzword(23);
171
5
    unpacked_date.tm_sec = (int)getzword(24);
172
5
    session->newdata.time.tv_sec = mkgmtime(&unpacked_date);
173
5
    session->newdata.time.tv_nsec = getzu32(25);
174
5
    session->newdata.latitude = ((long)getzlong(27)) * RAD_2_DEG * 1e-8;
175
5
    session->newdata.longitude = ((long)getzlong(29)) * RAD_2_DEG * 1e-8;
176
    /*
177
     * The Rockwell Jupiter TU30-D140 reports altitude as uncorrected height
178
     * above WGS84 geoid.  The Zodiac binary protocol manual does not
179
     * specify whether word 31 is geodetic or WGS 84.
180
     * Here we assume altitude is always wgs84.
181
     */
182
5
    session->newdata.altHAE = ((long)getzlong(31)) * 1e-2;
183
5
    session->newdata.geoid_sep = ((short)getzword(33)) * 1e-2;
184
5
    session->newdata.speed = (int)getzlong(34) * 1e-2;
185
5
    session->newdata.track = (int)getzword(36) * RAD_2_DEG * 1e-3;
186
5
    session->newdata.magnetic_var = ((short)getzword(37)) * RAD_2_DEG * 1e-4;
187
5
    session->newdata.climb = ((short)getzword(38)) * 1e-2;
188
5
    datum = getzword(39);
189
5
    datum_code_string(datum, session->newdata.datum,
190
5
                      sizeof(session->newdata.datum));
191
    /*
192
     * The manual says these are 1-sigma.  Device reports only eph, circular
193
     * error.  Let gpsd_model_error() do the rest
194
     */
195
5
    session->newdata.eph = (int)getzlong(40) * 1e-2 * GPSD_CONFIDENCE;
196
5
    session->newdata.epv = (int)getzlong(42) * 1e-2 * GPSD_CONFIDENCE;
197
5
    session->newdata.ept = (int)getzlong(44) * 1e-2 * GPSD_CONFIDENCE;
198
5
    session->newdata.eps = (int)getzword(46) * 1e-2 * GPSD_CONFIDENCE;
199
    // clock_bias                  = (int)getzlong(47) * 1e-2;
200
    // clock_bias_sd               = (int)getzlong(49) * 1e-2;
201
    // clock_drift                 = (int)getzlong(51) * 1e-2;
202
    // clock_drift_sd              = (int)getzlong(53) * 1e-2;
203
204
5
    mask = TIME_SET | NTPTIME_IS | LATLON_SET | ALTITUDE_SET | CLIMB_SET |
205
5
           SPEED_SET | TRACK_SET | STATUS_SET | MODE_SET |
206
5
           HERR_SET | SPEEDERR_SET | VERR_SET;
207
5
    GPSD_LOG(LOG_DATA, &session->context->errout,
208
5
             "ZODIAC: 1000: time=%s lat=%.2f lon=%.2f altHAE=%.2f track=%.2f "
209
5
             "speed=%.2f climb=%.2f mode=%d status=%d\n",
210
5
             timespec_str(&session->newdata.time, ts_buf, sizeof(ts_buf)),
211
5
             session->newdata.latitude,
212
5
             session->newdata.longitude, session->newdata.altHAE,
213
5
             session->newdata.track, session->newdata.speed,
214
5
             session->newdata.climb, session->newdata.mode,
215
5
             session->newdata.status);
216
5
    return mask | CLEAR_IS | REPORT_IS;
217
5
}
218
219
// Message 1002: Channel Summary Message
220
static gps_mask_t handle1002(struct gps_device_t *session)
221
0
{
222
0
    unsigned i;
223
0
    timespec_t ts_tow;
224
225
    // ticks                      = getzlong(6);
226
    // sequence                   = getzword(8);
227
    // measurement_sequence       = getzword(9);
228
0
    unsigned short gps_week = getzu16(10);
229
0
    time_t gps_seconds = (time_t)getzu32(11);
230
0
    unsigned long gps_nanoseconds = getzu32(13);
231
0
    char ts_buf[TIMESPEC_LEN];
232
233
    // Note: this week counter is not limited to 10 bits.
234
0
    session->context->gps_week = gps_week;
235
0
    session->gpsdata.satellites_used = 0;
236
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
237
0
        int status, prn;
238
0
        session->driver.zodiac.Zv[i] = status = (int)getzword(15 + (3 * i));
239
0
        session->driver.zodiac.Zs[i] = prn = (int)getzword(16 + (3 * i));
240
241
0
        if (status & 1) {
242
0
            session->gpsdata.satellites_used++;
243
0
        }
244
245
0
        session->gpsdata.skyview[i].PRN = (short)prn;
246
0
        session->gpsdata.skyview[i].ss = (float)getzword(17 + (3 * i));
247
0
        session->gpsdata.skyview[i].used = (bool)(status & 1);
248
0
    }
249
0
    ts_tow.tv_sec = gps_seconds;
250
0
    ts_tow.tv_nsec = gps_nanoseconds;
251
0
    session->gpsdata.skyview_time = gpsd_gpstime_resolv(session, gps_week,
252
0
                                                        ts_tow);
253
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
254
0
             "ZODIAC: 1002: visible=%d used=%d mask={SATELLITE|USED} "
255
0
             "ime %s\n",
256
0
             session->gpsdata.satellites_visible,
257
0
             session->gpsdata.satellites_used,
258
0
             timespec_str(&session->gpsdata.skyview_time, ts_buf,
259
0
                          sizeof(ts_buf)));
260
0
    return SATELLITE_SET | USED_IS;
261
0
}
262
263
// skyview report
264
static gps_mask_t handle1003(struct gps_device_t *session)
265
1
{
266
1
    unsigned i, n;
267
1
    gps_mask_t mask = 0;
268
269
    /* The Polaris (and probably the DAGR) emit some strange variant of
270
     * this message which causes gpsd to crash filtering on impossible
271
     * number of satellites avoids this */
272
1
    n = getzword(14);
273
1
    if (ZODIAC_CHANNELS < n) {
274
1
        return 0;
275
1
    }
276
277
0
    gpsd_zero_satellites(&session->gpsdata);
278
279
    // ticks              = getzlong(6);
280
    // sequence           = getzword(8);
281
0
    session->gpsdata.dop.gdop = (unsigned int)getzword(9) * 1e-2;
282
0
    session->gpsdata.dop.pdop = (unsigned int)getzword(10) * 1e-2;
283
0
    session->gpsdata.dop.hdop = (unsigned int)getzword(11) * 1e-2;
284
0
    session->gpsdata.dop.vdop = (unsigned int)getzword(12) * 1e-2;
285
0
    session->gpsdata.dop.tdop = (unsigned int)getzword(13) * 1e-2;
286
0
    mask |= DOP_SET;
287
0
    session->gpsdata.satellites_visible = n;
288
289
0
    for (i = 0; i < ZODIAC_CHANNELS; i++) {
290
0
        if (i < session->gpsdata.satellites_visible) {
291
0
            session->gpsdata.skyview[i].PRN = (short)getzword(15 + (3 * i));
292
0
            session->gpsdata.skyview[i].azimuth =
293
0
                (((double)getzword(16 + (3 * i))) * RAD_2_DEG * 1e-4);
294
0
            if (session->gpsdata.skyview[i].azimuth < 0)
295
0
                session->gpsdata.skyview[i].azimuth += 360;
296
0
            session->gpsdata.skyview[i].elevation =
297
0
                (((double)getzword(17 + (3 * i))) * RAD_2_DEG * 1e-4);
298
0
        } else {
299
0
            session->gpsdata.skyview[i].PRN = 0;
300
0
            session->gpsdata.skyview[i].azimuth = NAN;
301
0
            session->gpsdata.skyview[i].elevation = NAN;
302
0
            session->gpsdata.skyview[i].ss = NAN;
303
0
        }
304
0
    }
305
0
    session->gpsdata.skyview_time.tv_sec = 0;
306
0
    session->gpsdata.skyview_time.tv_nsec = 0;
307
0
    mask |= SATELLITE_SET;
308
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
309
0
             "ZODIAC: NAVDOP: visible=%d gdop=%.2f pdop=%.2f "
310
0
             "hdop=%.2f vdop=%.2f tdop=%.2f mask={SATELLITE|DOP}\n",
311
0
             session->gpsdata.satellites_visible,
312
0
             session->gpsdata.dop.gdop,
313
0
             session->gpsdata.dop.hdop,
314
0
             session->gpsdata.dop.vdop,
315
0
             session->gpsdata.dop.pdop, session->gpsdata.dop.tdop);
316
0
    return mask;
317
1
}
318
319
// fix quality report
320
static gps_mask_t handle1005(struct gps_device_t *session UNUSED)
321
1
{
322
    // ticks              = getzlong(6);
323
    // sequence           = getzword(8);
324
1
    int numcorrections = (int)getzword(12);
325
326
1
    if (MODE_NO_FIX == session->newdata.mode) {
327
0
        session->newdata.status = STATUS_UNK;
328
1
    } else if (0 == numcorrections) {
329
0
        session->newdata.status = STATUS_GPS;
330
1
    } else {
331
1
        session->newdata.status = STATUS_DGPS;
332
1
    }
333
334
1
    return 0;
335
1
}
336
337
// version report
338
static gps_mask_t handle1011(struct gps_device_t *session)
339
0
{
340
    /*
341
     * This is UNTESTED -- but harmless if buggy.  Added to support
342
     * client querying of the ID with firmware version in 2006.
343
     * The Zodiac is supposed to send one of these messages on startup.
344
     */
345
    // software version field
346
0
    getstringz(session->subtype, session->lexer.outbuffer, 19, 28);
347
0
    GPSD_LOG(LOG_DATA, &session->context->errout,
348
0
             "ZODIAC: 1011: subtype=%s mask={DEVICEID}\n",
349
0
             session->subtype);
350
0
    return DEVICEID_SET;
351
0
}
352
353
354
// leap-second correction report
355
static gps_mask_t handle1108(struct gps_device_t *session)
356
0
{
357
    // ticks              = getzlong(6);
358
    // sequence           = getzword(8);
359
    // utc_week_seconds   = getzlong(14);
360
    // leap_nanoseconds   = getzlong(17);
361
0
    if ((getzword(19) & 3) == 3) {
362
0
        session->context->valid |= LEAP_SECOND_VALID;
363
0
        session->context->leap_seconds = (int)getzword(16);
364
0
    }
365
0
    return 0;
366
0
}
367
368
static gps_mask_t zodiac_analyze(struct gps_device_t *session)
369
389
{
370
389
    unsigned bad_len = 0;
371
389
    gps_mask_t mask = 0;
372
389
    unsigned id = 0;
373
374
389
    if (10 > session->lexer.outbuflen) {
375
0
        return 0;
376
0
    }
377
378
389
    id = getzu16(2);
379
380
389
    GPSD_LOG(LOG_PROG, &session->context->errout,
381
389
             "ZODIAC: %u: length %zd\n",
382
389
             id, session->lexer.outbuflen);
383
389
    GPSD_LOG(LOG_RAW, &session->context->errout,
384
389
             "ZODIAC: Raw Zodiac packet type %d length %zd: %s\n",
385
389
             id, session->lexer.outbuflen, gpsd_prettydump(session));
386
387
388
    /*
389
     * Normal cycle for these devices is 1001 1002.
390
     * We count 1001 as end of cycle because 1002 doesn't
391
     * carry fix information.
392
     */
393
389
    session->cycle_end_reliable = true;
394
395
389
    switch (id) {
396
51
    case 1000:
397
51
        if (110 > session->lexer.outbuflen) {
398
46
            bad_len = 110;
399
46
            break;
400
46
        }
401
5
        mask = handle1000(session);
402
5
        break;
403
10
    case 1002:
404
10
        if (102 > session->lexer.outbuflen) {
405
10
            bad_len = 102;
406
10
            break;
407
10
        }
408
0
        mask = handle1002(session);
409
0
        break;
410
16
    case 1003:
411
16
        if (102 > session->lexer.outbuflen) {
412
15
            bad_len = 102;
413
15
            break;
414
15
        }
415
1
        mask = handle1003(session);
416
1
        break;
417
7
    case 1005:
418
7
        if (50 > session->lexer.outbuflen) {
419
6
            bad_len = 50;
420
6
            break;
421
6
        }
422
1
        mask = handle1005(session);
423
1
        break;
424
1
    case 1011:
425
1
        if (118 > session->lexer.outbuflen) {
426
1
            bad_len = 118;
427
1
            break;
428
1
        }
429
0
        mask = handle1011(session);
430
0
        break;
431
2
    case 1108:
432
2
        if (40 > session->lexer.outbuflen) {
433
2
            bad_len = 40;
434
2
            break;
435
2
        }
436
0
        mask = handle1108(session);
437
0
        break;
438
302
    default:
439
302
        GPSD_LOG(LOG_WARN, &session->context->errout,
440
302
                 "ZODIAC: %u: unknwon message id, length %zd",
441
302
                 id, session->lexer.outbuflen);
442
302
        break;
443
389
    }
444
389
    if (bad_len) {
445
80
        GPSD_LOG(LOG_WARN, &session->context->errout,
446
80
                 "ZODIAC: %u: runt payload len %u s/b %zd",
447
80
                 id, bad_len, session->lexer.outbuflen);
448
80
    }
449
389
    return mask;
450
389
}
451
452
static ssize_t zodiac_control_send(struct gps_device_t *session,
453
                                   char *msg, size_t len)
454
0
{
455
0
    unsigned short shortwords[256];
456
457
0
    if (sizeof(shortwords) <= len) {
458
0
        return -1;
459
0
    }
460
461
0
#define min(x,y)        ((x) < (y) ? x : y)
462
    /*
463
     * We used to just cast msg to an unsigned short pointer.
464
     * This can fail on word-oriented architectures like a SPARC.
465
     */
466
0
    memcpy((char *)shortwords, msg, min(sizeof(shortwords), len));
467
468
    // and if len isn't even, it's your own fault
469
0
    return zodiac_spew(session, shortwords[0], shortwords + 1,
470
0
                       (int)(len / 2 - 1));
471
0
}
472
473
static bool zodiac_speed_switch(struct gps_device_t *session,
474
                                speed_t speed, char parity, int stopbits)
475
0
{
476
0
    unsigned short data[15];
477
478
0
    if (32767 < session->driver.zodiac.sn++) {
479
0
        session->driver.zodiac.sn = 0;
480
0
    }
481
482
0
    switch (parity) {
483
0
    case 'E':
484
0
    case 2:
485
0
        parity = (char)2;
486
0
        break;
487
0
    case 'O':
488
0
    case 1:
489
0
        parity = (char)1;
490
0
        break;
491
0
    case 'N':
492
0
    case 0:
493
0
    default:
494
0
        parity = (char)0;
495
0
        break;
496
0
    }
497
498
0
    memset(data, 0, sizeof(data));
499
    // data is the part of the message starting at word 6
500
0
    data[0] = session->driver.zodiac.sn;        // sequence number
501
0
    data[1] = 1;                                // port 1 data valid
502
0
    data[2] = (unsigned short)parity;   // port 1 character width (8 bits)
503
    // port 1 stop bits (1 stopbit)
504
0
    data[3] = (unsigned short)(stopbits - 1);
505
0
    data[4] = 0;                // port 1 parity (none)
506
    // port 1 speed
507
0
    data[5] = (unsigned short)(round(log((double)speed / 300) / GPS_LN2) + 1);
508
0
    data[14] = zodiac_checksum(data, 14);
509
510
0
    (void)zodiac_spew(session, 1330, data, 15);
511
0
    return true;                // it would be nice to error-check this
512
0
}
513
514
static double zodiac_time_offset(struct gps_device_t *session UNUSED)
515
0
{
516
    /* Removing/changing the magic number below is likely to disturb
517
     * the handling of the 1pps signal from the gps device. The regression
518
     * tests and simple gps applications do not detect this. A live test
519
     * with the 1pps signal active is required. */
520
0
    return 1.1;
521
0
}
522
523
// this is everything we export
524
// *INDENT-OFF*
525
const struct gps_type_t driver_zodiac =
526
{
527
    .type_name      = "Zodiac",            // full name of type
528
    .packet_type    = ZODIAC_PACKET,       // associated lexer packet type
529
    .flags          = DRIVER_STICKY,       // no flags set
530
    .trigger        = NULL,                // no trigger
531
    .channels       = 12,                  // consumer-grade GPS
532
    .probe_detect   = NULL,                // no probe
533
    .get_packet     = packet_get1,         // use the generic packet getter
534
    .parse_packet   = zodiac_analyze,      // parse message packets
535
    .rtcm_writer    = zodiac_send_rtcm,    // send DGPS correction
536
    .init_query     = NULL,                // non-perturbing initial query
537
    .event_hook     = NULL,                // no configuration
538
    .speed_switcher = zodiac_speed_switch, // we can change baud rate
539
    .mode_switcher  = NULL,                // no mode switcher
540
    .rate_switcher  = NULL,                // no sample-rate switcher
541
    .min_cycle.tv_sec  = 1,                // not relevant, no rate switch
542
    .min_cycle.tv_nsec = 0,                // not relevant, no rate switch
543
    .control_send   = zodiac_control_send, // for gpsctl and friends
544
    .time_offset     = zodiac_time_offset, // compute NTO fudge factor
545
};
546
// *INDENT-ON*
547
548
#endif  // ZODIAC_ENABLE
549
550
// vim: set expandtab shiftwidth=4