Coverage Report

Created: 2025-07-12 06:36

/src/gpsd/gpsd-3.26.2~dev/drivers/driver_casic.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Driver for the "CASIC" protocol used by the
3
 * 杭州中科微电子有限公司 / Zhongkewei /
4
 * icofchina.com AT6558 family(?) of chips.
5
 *
6
 * Documentation is the original "CASIC 多模卫星导航接收机协议规范",
7
 * and the "CASIC Multimode Satellite Navigation Receiver Protocol
8
 * specification" in English, which seems to be a machine translation,
9
 * both V4.2.0.3 2020-01-06.  Using both documents is helpful because
10
 * the translation loses all the images and much of the formatting.  A
11
 * Google translation of the oroginal was also helpful to understand
12
 * some things that were lost in the official translation, but beware
13
 * of errors.
14
 *
15
 * This chip is often used in inexpensive modules calling themselves
16
 * "ATGM336H", made by Zhongkewei and many other vendors.  It's also
17
 * used in the AI Thinker GP-01/GP-02 modules.  There are many boards
18
 * being sold under those names on Aliexpress.  The chip is also used
19
 * on boards by some of the slightly higher-level OEMs like Beitian
20
 * and Quescan, I think.
21
 *
22
 * At startup, mine says:
23
 *
24
 * $GPTXT,01,01,02,IC=AT6558F-5N-32-1C580900*06
25
 * $GPTXT,01,01,02,SW=URANUS5,V5.3.0.0*1D
26
 * $GPTXT,01,01,02,TB=2020-03-26,13:25:12*4B
27
 * $GPTXT,01,01,02,MO=GB*77
28
 *
29
 * Mine is configured to emit both NMEA and CASIC messages. It also
30
 * responds to the UBX probe, at least partly.  That's a completely
31
 * undocumented feature.
32
 *
33
 * This code will probably work with the AT331C/AT332D modules.
34
 *
35
 * CASIC binary is another not quite clone of UBX Binary.
36
 * The message header fields are reordered:
37
 *  header, length, ID, payload, checksum
38
 *
39
 * Header changed feom the UBX 0xb5 0x62 to 0xba 0xce
40
 * Classes, IDs, and payloads are sometimes same/similar
41
 * Checksum algorithm is a simple 32-bit checksum of
42
 * everything after the header.
43
 *
44
 * This code was copied from driver_allystar.c, most of it was
45
 * deleted, then it was lightly altered to support the CASIC protocol.
46
 *
47
 * This file is Copyright John Hood
48
 * This file is Copyright by the GPSD project
49
 * SPDX-License-Identifier: BSD-2-clause
50
 */
51
52
#include "../include/gpsd_config.h"  // must be before all includes
53
54
#include <math.h>
55
#include <stdbool.h>
56
#include <stdio.h>
57
#include <stdlib.h>                // for abs()
58
#include <string.h>
59
60
#include "../include/gpsd.h"
61
#include "../include/bits.h"
62
63
// one byte class
64
typedef enum {
65
    CASIC_NAV = 0x01,     // Navigation
66
    CASIC_TIM = 0x02,     // Timing message: time pulse, time mark
67
    CASIC_RXM = 0x03,     // Receiver Manager Messages
68
    CASIC_ACK = 0x05,     // (Not) Acknowledges for cfg messages
69
    CASIC_CFG = 0x06,     // Configuration requests
70
    CASIC_MSG = 0x08,     // Satellite information
71
    CASIC_MON = 0x0a,     // System monitoring
72
    CASIC_AID = 0x0b,     // AGPS
73
} casic_classes_t;
74
75
#define MSGID(cls_, id_) (((cls_)<<8)|(id_))
76
77
typedef enum {
78
    NAV_STATUS      = MSGID(CASIC_NAV, 0x00),
79
    NAV_DOP         = MSGID(CASIC_NAV, 0x01),
80
    NAV_SOL         = MSGID(CASIC_NAV, 0x02),
81
    NAV_PV          = MSGID(CASIC_NAV, 0x03),
82
    NAV_TIMEUTC     = MSGID(CASIC_NAV, 0x10),
83
    NAV_CLOCK       = MSGID(CASIC_NAV, 0x11),
84
    NAV_GPSINFO     = MSGID(CASIC_NAV, 0x20),
85
    NAV_BDSINFO     = MSGID(CASIC_NAV, 0x21),
86
    NAV_GLNINFO     = MSGID(CASIC_NAV, 0x22),
87
88
    TIM_TP          = MSGID(CASIC_TIM, 0x00),
89
90
    RXM_MEASX       = MSGID(CASIC_RXM, 0x10),
91
    RXM_SVPOS       = MSGID(CASIC_RXM, 0x11),
92
93
    ACK_NAK         = MSGID(CASIC_ACK, 0x00),
94
    ACK_ACK         = MSGID(CASIC_ACK, 0x01),
95
96
    CFG_PRT         = MSGID(CASIC_CFG, 0x00),
97
    CFG_MSG         = MSGID(CASIC_CFG, 0x01),
98
    CFG_RST         = MSGID(CASIC_CFG, 0x02),
99
    CFG_TP          = MSGID(CASIC_CFG, 0x03),
100
    CFG_RATE        = MSGID(CASIC_CFG, 0x04),
101
    CFG_CFG         = MSGID(CASIC_CFG, 0x05),
102
    CFG_TMODE       = MSGID(CASIC_CFG, 0x06),
103
    CFG_NAVX        = MSGID(CASIC_CFG, 0x07),
104
    CFG_GROUP       = MSGID(CASIC_CFG, 0x08),
105
106
    MSG_BDSUTC      = MSGID(CASIC_MSG, 0x00),
107
    MSG_BDSION      = MSGID(CASIC_MSG, 0x01),
108
    MSG_BDSEPH      = MSGID(CASIC_MSG, 0x02),
109
    MSG_GPSUTC      = MSGID(CASIC_MSG, 0x05),
110
    MSG_GPSION      = MSGID(CASIC_MSG, 0x06),
111
    MSG_GPSEPH      = MSGID(CASIC_MSG, 0x07),
112
    MSG_GLNEPH      = MSGID(CASIC_MSG, 0x08),
113
114
    MON_VER         = MSGID(CASIC_MON, 0x04),
115
    MON_HW          = MSGID(CASIC_MON, 0x09),
116
117
    AID_INI         = MSGID(CASIC_MON, 0x01),
118
    AID_HUI         = MSGID(CASIC_MON, 0x03),
119
} casic_msgs_t;
120
121
// 2 bytes payload length, 2 bytes leader, 2 bytes ID
122
0
#define CASIC_PREFIX_LEN 6
123
124
static struct vlist_t vclass[] = {
125
    {0x01, "NAV"},
126
    {0x02, "TIM"},
127
    {0x03, "RXM"},
128
    {0x05, "ACK"},
129
    {0x06, "CFG"},
130
    {0x08, "MSG"},
131
    {0x0a, "MON"},
132
    {0x0b, "AID"},
133
    {0, NULL},
134
};
135
136
/* send a CASIC message.
137
 * calculate checksums, etc.
138
 *
139
 * Return: True -- read-only, or sent OK
140
 *         False -- send failed
141
 */
142
bool casic_write(struct gps_device_t * session,
143
                unsigned int msg_class, unsigned int msg_id,
144
                const unsigned char *msg, size_t payload_len)
145
0
{
146
0
    ssize_t count;
147
0
    bool ok;
148
0
    unsigned checksum;
149
150
    // do not write if -b (readonly) option set
151
    // "passive" handled earlier
152
0
    if (session->context->readonly) {
153
0
        return true;
154
0
    }
155
156
0
    if ((sizeof(session->msgbuf) - 10) <= payload_len ||
157
0
        2048 < payload_len) {
158
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
159
0
                 "=> GPS: CASIC class: %02x, id: %02x, len: %zd TOO LONG!\n",
160
0
                 msg_class, msg_id, payload_len);
161
0
        return false;
162
0
    }
163
0
    if (0 != (payload_len % 4)) {
164
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
165
0
                 "=> GPS: CASIC class: %02x, id: %02x, len: %zd UN ALIGNED!\n",
166
0
                 msg_class, msg_id, payload_len);
167
0
        return false;
168
0
    }
169
170
0
    session->msgbuf[0] = 0xba;
171
0
    session->msgbuf[1] = 0xce;
172
0
    session->msgbuf[2] = payload_len & 0xff;
173
0
    session->msgbuf[3] = (payload_len >> 8) & 0xff;
174
0
    session->msgbuf[4] = msg_class;
175
0
    session->msgbuf[5] = msg_id;
176
177
0
    if (NULL != msg &&
178
0
        0 < payload_len) {
179
0
        (void)memcpy(&session->msgbuf[CASIC_PREFIX_LEN], msg, payload_len);
180
0
    }
181
182
0
    checksum = casic_checksum((unsigned char *)session->msgbuf + 2,
183
0
                              payload_len + 4);
184
0
    putle32(session->msgbuf, payload_len + 6, checksum);
185
186
0
    session->msgbuflen = payload_len + 10;
187
188
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
189
0
             "=> GPS: CASIC: class: %02x, id: %02x, len: %zd, csum: %04x\n",
190
0
             msg_class, msg_id, payload_len,
191
0
             checksum);
192
0
    count = gpsd_write(session, session->msgbuf, session->msgbuflen);
193
0
    ok = (count == (ssize_t) session->msgbuflen);
194
0
    return ok;
195
0
}
196
197
// ACK-*
198
199
// ACK-ACK
200
static gps_mask_t msg_ack_ack(struct gps_device_t *session,
201
                              unsigned char *buf, size_t payload_len UNUSED)
202
0
{
203
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
204
0
             "CASIC: ACK-ACK: class: %02x(%s), id: %02x\n",
205
0
              buf[0], val2str(buf[0], vclass), buf[1]);
206
0
    return 0;
207
0
}
208
209
// ACK-NAK
210
static gps_mask_t msg_ack_nak(struct gps_device_t *session,
211
                              unsigned char *buf, size_t payload_len UNUSED)
212
0
{
213
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
214
0
             "CASIC: ACK-NAK: class: %02x(%s), id: %02x\n",
215
0
              buf[0], val2str(buf[0], vclass), buf[1]);
216
0
    return 0;
217
0
}
218
219
// CFG-*
220
221
/**
222
 * Port configuration
223
 * CFG-PRT
224
 *
225
 * buf points to payload.
226
 * payload_len is length of payload.
227
 *
228
 */
229
static gps_mask_t msg_cfg_prt(struct gps_device_t *session,
230
                              unsigned char *buf,
231
                              size_t payload_len UNUSED)
232
0
{
233
0
    unsigned portID = getub(buf, 0);
234
0
    unsigned protoMask = getub(buf, 1);
235
0
    unsigned mode = getleu16(buf, 2);
236
0
    unsigned long long baudRate = getleu32(buf, 4);
237
238
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
239
0
             "CASIC: CFG-PRT: portID %d protoMask %02x mode %04x "
240
0
             " baudRate %llu\n",
241
0
             portID, protoMask, mode, baudRate);
242
243
0
    return 0;
244
0
}
245
246
// MON-*
247
248
/**
249
 * Receiver/Software Version
250
 * MON-VER
251
 *
252
 * buf points to payload.
253
 * payload_len is length of payload.
254
 *
255
 */
256
static gps_mask_t msg_mon_ver(struct gps_device_t *session,
257
                              unsigned char *buf,
258
                              size_t payload_len UNUSED)
259
0
{
260
    // save SW and HW Version as subtype
261
0
    (void)snprintf(session->subtype, sizeof(session->subtype),
262
0
                   "SW %.32s,HW %.32s",
263
0
                   (char *)buf,
264
0
                   (char *)(buf + 32));
265
266
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
267
0
             "CASIC: MON-VER: %s\n",
268
0
             session->subtype);
269
270
0
    return 0;
271
0
}
272
273
/**
274
 * Positioning precision factor
275
 * NAV-DOP
276
 *
277
 * buf points to payload.
278
 * payload_len is length of payload.
279
 *
280
 */
281
static gps_mask_t msg_nav_dop(struct gps_device_t *session,
282
                              unsigned char *buf,
283
                              size_t payload_len)
284
0
{
285
0
    if (28 > payload_len) {
286
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
287
0
        "CASIC: NAV-DOP: runt payload len %zd", payload_len);
288
0
        return 0;
289
0
    }
290
291
0
    session->gpsdata.dop.pdop = getlef32((char *)buf, 4);    // Location DOP
292
0
    session->gpsdata.dop.hdop = getlef32((char *)buf, 8);    // Horizontal DOP
293
0
    session->gpsdata.dop.vdop = getlef32((char *)buf, 12);   // Vertical DOP
294
0
    session->gpsdata.dop.ydop = getlef32((char *)buf, 16);   // Northbound DOP
295
0
    session->gpsdata.dop.xdop = getlef32((char *)buf, 20);   // Eastbound DOP
296
0
    session->gpsdata.dop.tdop = getlef32((char *)buf, 24);   // Time DOP
297
298
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
299
0
             "CASIC: NAV-DOP: pdop=%.2f hdop=%.2f vdop=%.2f tdop=%.2f "
300
0
             "ydop=%.2f xdop=%.2f\n",
301
0
             session->gpsdata.dop.pdop,
302
0
             session->gpsdata.dop.hdop,
303
0
             session->gpsdata.dop.vdop,
304
0
             session->gpsdata.dop.tdop,
305
0
             session->gpsdata.dop.ydop,
306
0
             session->gpsdata.dop.xdop);
307
308
0
    return DOP_SET;
309
0
}
310
311
/* msg_decode() -- dispatch all message types to proper decoder
312
 */
313
static gps_mask_t msg_decode(struct gps_device_t *session,
314
                             unsigned char *buf, size_t payload_len)
315
0
{
316
0
    unsigned msgid = getbes16(buf, 4);
317
0
    gps_mask_t mask = 0;
318
0
    size_t needed_len;
319
0
    const char *msg_name;
320
0
    gps_mask_t (* p_decode)(struct gps_device_t *, unsigned char *, size_t);
321
322
0
    switch (msgid) {
323
0
    case ACK_NAK:
324
0
        needed_len = 4;
325
0
        msg_name = "ACK-NAK";
326
0
        p_decode = msg_ack_nak;
327
0
        break;
328
0
    case ACK_ACK:
329
0
        needed_len = 4;
330
0
        msg_name = "ACK-ACK";
331
0
        p_decode = msg_ack_ack;
332
0
        break;
333
0
    case CFG_PRT:
334
0
        msg_name ="CFG-PRT";
335
0
        needed_len = 8;
336
0
        p_decode = msg_cfg_prt;
337
0
        break;
338
0
    case MON_VER:
339
0
        msg_name ="MON-VER";
340
0
        needed_len = 64;
341
0
        p_decode = msg_mon_ver;
342
0
        break;
343
0
    case NAV_DOP:
344
0
        msg_name ="NAV-DOP";
345
0
        needed_len = 28;
346
0
        p_decode = msg_nav_dop;
347
0
        break;
348
0
    default:
349
0
        msg_name ="UNK-UNK";
350
0
        needed_len = 0;
351
0
        p_decode = NULL;
352
0
        break;
353
0
    }
354
0
    if (needed_len > payload_len) {
355
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
356
0
                 "CASIC: %s: runt payload len %zd need %zd\n",
357
0
                 msg_name, payload_len, needed_len);
358
0
        return 0;
359
0
    }
360
0
    if (NULL == p_decode) {
361
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
362
0
                 "CASIC: Unsupported/unknown %s(%02x)-%02x payload_len %zd\n",
363
0
                 val2str((msgid >> 8) & 0xff, vclass),
364
0
                 (msgid >> 8) & 0xff,
365
0
                 msgid & 0xff, payload_len);
366
0
        return 0;
367
0
    }
368
0
    mask = p_decode(session, &buf[CASIC_PREFIX_LEN], payload_len);
369
0
    return mask;
370
0
}
371
372
static gps_mask_t casic_parse(struct gps_device_t * session,
373
                              unsigned char *buf, size_t len)
374
0
{
375
0
    size_t payload_len;
376
0
    gps_mask_t mask = 0;
377
0
    int class, id;
378
379
    /* Minimum packet size is 10: header (2), length (2), Message ID
380
    *  (2), payload (0), and checksum (4).  The packetizer should
381
    *  already guarantee this to protect against malicious fuzzing. */
382
0
    if (10 > len) {
383
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
384
0
                 "CASIC: runt message len %zu\n", len);
385
0
        return 0;
386
0
    }
387
388
    // extract payload length, check against actual length
389
0
    payload_len = getles16(buf, 2);
390
391
0
    if ((len - 10) != payload_len) {
392
0
        GPSD_LOG(LOG_WARN, &session->context->errout,
393
0
                 "CASIC: len (%zu) does not match payload (%zu) + 10\n",
394
0
                 len, payload_len);
395
0
        return 0;
396
0
    }
397
398
0
    class = buf[4];
399
0
    id = buf[5];
400
401
0
    GPSD_LOG(LOG_PROG, &session->context->errout,
402
0
             "CASIC: %s(%02x)-%02x\n",
403
0
             val2str(class, vclass), class, id);
404
405
0
    mask = msg_decode(session, buf, payload_len);
406
407
0
    return mask;
408
0
}
409
410
static gps_mask_t parse_input(struct gps_device_t *session)
411
0
{
412
0
    if (CASIC_PACKET == session->lexer.type) {
413
0
        return casic_parse(session, session->lexer.outbuffer,
414
0
                           session->lexer.outbuflen);
415
0
    }
416
    // a comment, JSON, or NMEA 0183
417
0
    return generic_parse_input(session);
418
0
}
419
420
// not used by gpsd, it's for gpsctl and friends
421
static ssize_t control_send(struct gps_device_t *session, char *msg,
422
                            size_t data_len)
423
0
{
424
0
    return casic_write(session, (unsigned int)msg[0], (unsigned int)msg[1],
425
0
                      (unsigned char *)msg + 2,
426
0
                      (size_t)(data_len - 2)) ? ((ssize_t)(data_len + 7)) : -1;
427
0
}
428
429
430
static void event_hook(struct gps_device_t *session, event_t event)
431
0
{
432
0
    if (session->context->readonly) {
433
0
        return;
434
0
    }
435
0
    if (EVENT_IDENTIFIED != event) {
436
0
        return;
437
0
    }
438
0
    GPSD_LOG(LOG_PROG, &session->context->errout, "CASIC: identified\n");
439
    // We would like MON-VER but it at least sometimes doesn't work.
440
0
    (void)casic_write(session, CASIC_MON, 0x04, NULL, 0);
441
    // Port configuration seems to work.
442
0
    (void)casic_write(session, CASIC_CFG, 0x00, NULL, 0);
443
0
}
444
445
static void init_query(struct gps_device_t *session)
446
0
{
447
    // MON-VER: query for version information
448
0
    (void)casic_write(session, CASIC_MON, 0x04, NULL, 0);
449
0
}
450
451
// this is everything we export
452
// *INDENT-OFF*
453
const struct gps_type_t driver_casic =
454
{
455
    .type_name      = "CASIC",                  // full name of type
456
    .packet_type    = CASIC_PACKET,             // lexer packet type
457
    .flags          = DRIVER_STICKY,            // remember this
458
    .trigger        = NULL,                     // recognize the type
459
    .channels       = 240,                      //  a guess
460
    .probe_detect   = NULL,                     // no probe
461
    .get_packet     = packet_get1,              // use generic one
462
    .parse_packet   = parse_input,              // parse message packets
463
    .rtcm_writer    = gpsd_write,               // send RTCM data straight
464
    .init_query     = init_query,               // non-perturbing query
465
    .event_hook     = event_hook,               // lifetime event handler
466
    .speed_switcher = NULL,                     // we can change baud rates
467
    .mode_switcher  = NULL,                     // there is a mode switcher
468
    .rate_switcher  = NULL,                     // change sample rate
469
    .min_cycle.tv_sec  = 1,                     // default
470
    .min_cycle.tv_nsec = 0,                     // default
471
    .control_send   = control_send,             // how to send a control string
472
    .time_offset    = NULL,                     // no NTP fudge factor
473
};
474
// *INDENT-ON*
475
476
// vim: set expandtab shiftwidth=4