hmuartlgw: Add minimal support for DualCoPro-firmware
[hmcfgusb] / hmuartlgw.c
CommitLineData
3e34d2ce
MG
1/* HM-MOD-UART/HM-LGW-O-TW-W-EU driver
2 *
70faa273 3 * Copyright (c) 2016-17 Michael Gernoth <michael@gernoth.net>
3e34d2ce
MG
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24#include <errno.h>
25#include <fcntl.h>
26#include <inttypes.h>
27#include <poll.h>
28#include <stdlib.h>
29#include <stdio.h>
30#include <string.h>
31#include <strings.h>
32#include <sys/types.h>
33#include <sys/stat.h>
34#include <termios.h>
35#include <unistd.h>
36
37#include "hexdump.h"
38#include "hmuartlgw.h"
39
40#define HMUARTLGW_INIT_TIMEOUT 10000
41
9248b988 42#define HMUARTLGW_SETTLE_TIME 1
3e34d2ce
MG
43
44static int debug = 0;
45
46enum hmuartlgw_state {
47 HMUARTLGW_QUERY_APPSTATE,
48 HMUARTLGW_ENTER_BOOTLOADER,
49 HMUARTLGW_ENTER_BOOTLOADER_ACK,
50 HMUARTLGW_BOOTLOADER,
51 HMUARTLGW_ENTER_APPLICATION,
52 HMUARTLGW_ENTER_APPLICATION_ACK,
53 HMUARTLGW_APPLICATION,
70faa273 54 HMUARTLGW_DUAL_APPLICATION,
3e34d2ce
MG
55};
56
57struct recv_data {
58 enum hmuartlgw_state state;
70faa273 59 struct hmuartlgw_dev *dev;
3e34d2ce
MG
60};
61
62
63#define CRC16_POLY 0x8005
64
65static uint16_t crc16(uint8_t* buf, int length)
66{
67 uint16_t crc = 0xd77f;
68 int i;
69
70 while (length--) {
71 crc ^= *buf++ << 8;
72 for (i = 0; i < 8; i++) {
73 if (crc & 0x8000) {
74 crc <<= 1;
75 crc ^= CRC16_POLY;
76 } else {
77 crc <<= 1;
78 }
79 }
80 }
81
82 return crc;
83}
84
85static int hmuartlgw_init_parse(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data)
86{
87 struct recv_data *rdata = data;
88
89#if 0
90 if (debug) {
91 printf("Length: %d\n", buf_len);
92 hexdump(buf, buf_len, "INIT > ");
93 }
94#endif
95
70faa273
MG
96 /* Minimally handle DualCopro-Firmware */
97 if (dst == HMUARTLGW_DUAL) {
98 if ((buf_len == 14) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "DualCoPro_App", 13)) {
99 rdata->state = HMUARTLGW_DUAL_APPLICATION;
100 return 1;
101 }
102
103 switch (rdata->state) {
104 case HMUARTLGW_QUERY_APPSTATE:
105 if ((buf[0] == 0x05) && (buf[1] == 0x01)) {
106 if (!strncmp(((char*)buf)+2, "DualCoPro_App", 13)) {
107 rdata->state = HMUARTLGW_DUAL_APPLICATION;
108 return 1;
109 }
110 }
111 break;
112 case HMUARTLGW_ENTER_BOOTLOADER:
113 if ((buf_len == 2) &&
114 (buf[0] == 0x05) &&
115 (buf[1] == 0x01)) {
116 rdata->state = HMUARTLGW_ENTER_BOOTLOADER_ACK;
117 return 1;
118 }
119 break;
120 default:
121 fprintf(stderr, "Don't know how to handle this state (%d) for unsupported firmware, giving up!\n", rdata->state);
122 exit(1);
123 break;
124 }
125
126 return 0;
127 }
128
129 /* Re-send commands for DualCopro Firmware */
130 if (dst == HMUARTLGW_DUAL_ERR) {
131 uint8_t buf[128] = { 0 };
132
133 switch(rdata->state) {
134 case HMUARTLGW_QUERY_APPSTATE:
135 if (debug) {
136 printf("Re-sending appstate-query for new firmare\n");
137 }
138
139 buf[0] = HMUARTLGW_DUAL_GET_APP;
140 hmuartlgw_send(rdata->dev, buf, 1, HMUARTLGW_DUAL);
141 break;
142 case HMUARTLGW_ENTER_BOOTLOADER:
143 if (debug) {
144 printf("Re-sending switch to bootloader for new firmare\n");
145 }
146
147 buf[0] = HMUARTLGW_DUAL_CHANGE_APP;
148 hmuartlgw_send(rdata->dev, buf, 1, HMUARTLGW_DUAL);
149 break;
150 default:
151 fprintf(stderr, "Don't know how to handle this error-state (%d) for unsupported firmware, giving up!\n", rdata->state);
152 exit(1);
153 break;
154 }
155
156 return 0;
157 }
158
3e34d2ce
MG
159 if (dst != HMUARTLGW_OS)
160 return 0;
161
162 if ((buf_len == 10) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_BL", 9)) {
163 rdata->state = HMUARTLGW_BOOTLOADER;
164 return 1;
165 }
166
167 if ((buf_len == 11) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_App", 10)) {
168 rdata->state = HMUARTLGW_APPLICATION;
169 return 1;
170 }
171
172 switch(rdata->state) {
173 case HMUARTLGW_QUERY_APPSTATE:
9248b988 174 if ((buf[0] == HMUARTLGW_OS_ACK) && (buf[1] == 0x02)) {
3e34d2ce
MG
175 if (!strncmp(((char*)buf)+2, "Co_CPU_BL", 9)) {
176 rdata->state = HMUARTLGW_BOOTLOADER;
177 } else if (!strncmp(((char*)buf)+2, "Co_CPU_App", 10)) {
178 rdata->state = HMUARTLGW_APPLICATION;
179 }
180 }
181 break;
182 case HMUARTLGW_ENTER_BOOTLOADER:
183 if ((buf_len == 2) &&
9248b988 184 (buf[0] == HMUARTLGW_OS_ACK) &&
3e34d2ce
MG
185 (buf[1] == 0x01)) {
186 rdata->state = HMUARTLGW_ENTER_BOOTLOADER_ACK;
187 }
188 break;
189 case HMUARTLGW_ENTER_BOOTLOADER_ACK:
190 rdata->state = HMUARTLGW_ENTER_BOOTLOADER;
191 break;
192 case HMUARTLGW_ENTER_APPLICATION:
193 if ((buf_len == 2) &&
9248b988 194 (buf[0] == HMUARTLGW_OS_ACK) &&
3e34d2ce
MG
195 (buf[1] == 0x01)) {
196 rdata->state = HMUARTLGW_ENTER_APPLICATION_ACK;
197 }
198 break;
199 case HMUARTLGW_ENTER_APPLICATION_ACK:
200 rdata->state = HMUARTLGW_ENTER_APPLICATION;
201 break;
202 default:
203 return 0;
204 break;
205 }
206
207 return 1;
208}
209
853cbce9 210struct hmuartlgw_dev *hmuart_init(char *device, hmuartlgw_cb_fn cb, void *data, int app)
3e34d2ce
MG
211{
212 struct hmuartlgw_dev *dev = NULL;
213 struct termios oldtio, tio;
214
215 dev = malloc(sizeof(struct hmuartlgw_dev));
216 if (dev == NULL) {
217 perror("malloc(struct hmuartlgw_dev)");
218 return NULL;
219 }
220
221 memset(dev, 0, sizeof(struct hmuartlgw_dev));
222
223 dev->fd = open(device, O_RDWR | O_NOCTTY);
224 if (dev->fd < 0) {
225 perror("open(hmuartlgw)");
226 goto out;
227 }
228
229 if (debug) {
230 fprintf(stderr, "%s opened\n", device);
231 }
232
233 if (tcgetattr(dev->fd, &oldtio) == -1) {
234 perror("tcgetattr");
235 goto out2;
236 }
237
238 memset(&tio, 0, sizeof(tio));
239
240 tio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
241 tio.c_iflag = IGNPAR;
242 tio.c_oflag = 0;
243 tio.c_lflag = 0;
244 tio.c_cc[VTIME] = 0;
245 tio.c_cc[VMIN] = 1;
246
247 tcflush(dev->fd, TCIFLUSH);
248 if (tcsetattr(dev->fd, TCSANOW, &tio) == -1) {
249 perror("tcsetattr");
250 goto out2;
251 }
252
253 if (debug) {
254 fprintf(stderr, "serial parameters set\n");
255 }
256
257 hmuartlgw_flush(dev);
258
853cbce9
MG
259 if (app) {
260 hmuartlgw_enter_app(dev);
261 } else {
262 hmuartlgw_enter_bootloader(dev);
263 }
3e34d2ce
MG
264
265 dev->cb = cb;
266 dev->cb_data = data;
267
268 return dev;
269
270out2:
271 close(dev->fd);
272out:
273 free(dev);
274 return NULL;
275}
276
277struct hmuartlgw_dev *hmlgw_init(char *device, hmuartlgw_cb_fn cb, void *data)
278{
279 struct hmuartlgw_dev *dev = NULL;
280
281 return dev;
282}
283
284void hmuartlgw_enter_bootloader(struct hmuartlgw_dev *dev)
285{
286 hmuartlgw_cb_fn cb_old = dev->cb;
287 void *cb_data_old = dev->cb_data;
288 struct recv_data rdata = { 0 };
289 uint8_t buf[128] = { 0 };
290
291 if (debug) {
292 fprintf(stderr, "Entering bootloader\n");
293 }
294
3e34d2ce
MG
295 dev->cb = hmuartlgw_init_parse;
296 dev->cb_data = &rdata;
297
70faa273 298 rdata.dev = dev;
3e34d2ce
MG
299 rdata.state = HMUARTLGW_QUERY_APPSTATE;
300 buf[0] = HMUARTLGW_OS_GET_APP;
301 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
302 do {
303 hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
304 } while (rdata.state == HMUARTLGW_QUERY_APPSTATE);
305
306 if (rdata.state != HMUARTLGW_BOOTLOADER) {
70faa273 307 rdata.dev = dev;
3e34d2ce
MG
308 rdata.state = HMUARTLGW_ENTER_BOOTLOADER;
309 buf[0] = HMUARTLGW_OS_CHANGE_APP;
310 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
311 do {
312 hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
313 } while (rdata.state != HMUARTLGW_BOOTLOADER);
314
315 printf("Waiting for bootloader to settle...\n");
316 sleep(HMUARTLGW_SETTLE_TIME);
317 }
318
319 dev->cb = cb_old;
320 dev->cb_data = cb_data_old;
321}
322
323void hmuartlgw_enter_app(struct hmuartlgw_dev *dev)
324{
325 hmuartlgw_cb_fn cb_old = dev->cb;
326 void *cb_data_old = dev->cb_data;
327 struct recv_data rdata = { 0 };
328 uint8_t buf[128] = { 0 };
329
330 if (debug) {
331 fprintf(stderr, "Entering application\n");
332 }
333
334 dev->cb = hmuartlgw_init_parse;
335 dev->cb_data = &rdata;
336
70faa273 337 rdata.dev = dev;
3e34d2ce
MG
338 rdata.state = HMUARTLGW_QUERY_APPSTATE;
339 buf[0] = HMUARTLGW_OS_GET_APP;
340 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
341 do {
342 hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
343 } while (rdata.state == HMUARTLGW_QUERY_APPSTATE);
344
70faa273
MG
345 if ((rdata.state != HMUARTLGW_APPLICATION) &&
346 (rdata.state != HMUARTLGW_DUAL_APPLICATION)) {
347 rdata.dev = dev;
3e34d2ce
MG
348 rdata.state = HMUARTLGW_ENTER_APPLICATION;
349 buf[0] = HMUARTLGW_OS_CHANGE_APP;
350 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
351 do {
352 hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
70faa273
MG
353 } while ((rdata.state != HMUARTLGW_APPLICATION) &&
354 (rdata.state != HMUARTLGW_DUAL_APPLICATION));
3e34d2ce 355
70faa273
MG
356 if (rdata.state == HMUARTLGW_APPLICATION) {
357 printf("Waiting for application to settle...\n");
358 sleep(HMUARTLGW_SETTLE_TIME);
359 }
3e34d2ce
MG
360 }
361
70faa273
MG
362 if (rdata.state == HMUARTLGW_DUAL_APPLICATION) {
363 fprintf(stderr, "Unsupported firmware, please install HM-only firmware!\n");
364 exit(1);
365 }
366
367
3e34d2ce
MG
368 dev->cb = cb_old;
369 dev->cb_data = cb_data_old;
370}
371
372static int hmuartlgw_escape(uint8_t *frame, int framelen)
373{
374 int i;
375
376 for (i = 1; i < framelen; i++) {
377 if (frame[i] == 0xfc || frame[i] == 0xfd) {
378 memmove(frame + i + 1, frame + i, framelen - i);
379 frame[i++] = 0xfc;
380 frame[i] &= 0x7f;
381 framelen++;
382 }
383 }
384 return framelen;
385}
386
387int hmuartlgw_send_raw(struct hmuartlgw_dev *dev, uint8_t *frame, int framelen)
388{
389 int w = 0;
390 int ret;
391
392 if (debug) {
393 hexdump(frame, framelen, "UARTLGW < ");
394 }
395
396 framelen = hmuartlgw_escape(frame, framelen);
397
398 do {
399 ret = write(dev->fd, frame + w, framelen - w);
400 if (ret < 0) {
401 perror("write");
402 return 0;
403 }
404 w += ret;
405 } while (w < framelen);
406
407 return 1;
408}
409
410int hmuartlgw_send(struct hmuartlgw_dev *dev, uint8_t *cmd, int cmdlen, enum hmuartlgw_dst dst)
411{
412 static uint8_t cnt = 0;
853cbce9 413 uint8_t frame[4096] = { 0 };
3e34d2ce
MG
414 uint16_t crc;
415
416 frame[0] = 0xfd;
417 frame[1] = ((cmdlen + 2) >> 8) & 0xff;
418 frame[2] = (cmdlen + 2) & 0xff;
419 frame[3] = dst;
420 dev->last_send_cnt = cnt;
421 frame[4] = cnt++;
422 memcpy(&(frame[5]), cmd, cmdlen);
423 crc = crc16(frame, cmdlen + 5);
424 frame[cmdlen + 5] = (crc >> 8) & 0xff;
425 frame[cmdlen + 6] = crc & 0xff;
426
427 return hmuartlgw_send_raw(dev, frame, cmdlen + 7);
428}
429
430int hmuartlgw_poll(struct hmuartlgw_dev *dev, int timeout)
431{
432 struct pollfd pfds[1];
433 int ret;
434 int r = 0;
435 uint16_t crc;
436
437 errno = 0;
438
439 memset(pfds, 0, sizeof(struct pollfd) * 1);
440
441 pfds[0].fd = dev->fd;
442 pfds[0].events = POLLIN;
443
444 ret = poll(pfds, 1, timeout);
445 if (ret == -1)
446 return -1;
447
448 errno = 0;
449 if (ret == 0) {
450 errno = ETIMEDOUT;
451 return -1;
452 }
453
454 if (!(pfds[0].revents & POLLIN)) {
455 errno = EIO;
456 return -1;
457 }
458
459 r = read(dev->fd, dev->buf+dev->pos, 1);
460 if (r < 0)
461 return -1;
462
463 if (r == 0) {
464 errno = EOF;
465 return -1;
466 }
467
468 dev->pos += r;
469
470 if (dev->buf[0] != 0xfd) {
471 memset(dev->buf, 0, sizeof(dev->buf));
472 dev->pos = 0;
473 dev->unescape_next = 0;
474 return -1;
475 }
476
477 if (dev->unescape_next) {
478 dev->buf[dev->pos-1] |= 0x80;
479 dev->unescape_next = 0;
480 } else if (dev->buf[dev->pos-1] == 0xfc) {
481 dev->unescape_next = 1;
482 dev->pos--;
483 return -1;
484 }
485
486 if (dev->pos >= 3) {
487 uint16_t len;
488
489 len = ((dev->buf[1] << 8) & 0xff00) | (dev->buf[2] & 0xff);
490
491 if (dev->pos < len + 5)
492 return -1;
493 } else {
494 return -1;
495 }
496
b1d2ccc2
MG
497 crc = crc16(dev->buf, dev->pos);
498 if (crc == 0x0000) {
3e34d2ce
MG
499 if (debug)
500 hexdump(dev->buf, dev->pos, "UARTLGW > ");
501
502 dev->cb(dev->buf[3], dev->buf + 5 , dev->pos - 7, dev->cb_data);
503
504 memset(dev->buf, 0, sizeof(dev->buf));
505 dev->pos = 0;
506 dev->unescape_next = 0;
507 } else {
508 fprintf(stderr, "Invalid checksum received!\n");
509 hexdump(dev->buf, dev->pos, "ERR> ");
510 printf("calculated: %04x\n", crc);
511
512 memset(dev->buf, 0, sizeof(dev->buf));
513 dev->pos = 0;
514 dev->unescape_next = 0;
515 }
516
517 errno = 0;
518 return -1;
519}
520
521void hmuartlgw_close(struct hmuartlgw_dev *dev)
522{
523 close(dev->fd);
524}
525
526void hmuartlgw_flush(struct hmuartlgw_dev *dev)
527{
528 struct pollfd pfds[1];
529 int ret;
530 int r = 0;
531 uint8_t buf[1024];
532
533 tcflush(dev->fd, TCIOFLUSH);
534
535 while(1) {
536 memset(pfds, 0, sizeof(struct pollfd) * 1);
537
538 pfds[0].fd = dev->fd;
539 pfds[0].events = POLLIN;
540
541 ret = poll(pfds, 1, 100);
542 if (ret <= 0)
543 break;
544
545 if (!(pfds[0].revents & POLLIN))
546 break;
547
548 memset(buf, 0, sizeof(buf));
549 r = read(dev->fd, buf, sizeof(buf));
550 if (r <= 0)
551 break;
552 }
553
554 return;
555}
556
557void hmuartlgw_set_debug(int d)
558{
559 debug = d;
560}
Impressum, Datenschutz