]> git.zerfleddert.de Git - hmcfgusb/blame - hmuartlgw.c
Allow the user to specify the compiler via environment variable
[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,
2c9eb7ad 51 HMUARTLGW_HMIP_BOOTLOADER,
3e34d2ce
MG
52 HMUARTLGW_ENTER_APPLICATION,
53 HMUARTLGW_ENTER_APPLICATION_ACK,
54 HMUARTLGW_APPLICATION,
70faa273 55 HMUARTLGW_DUAL_APPLICATION,
37254151 56 HMUARTLGW_HMIP_APPLICATION,
3e34d2ce
MG
57};
58
59struct recv_data {
60 enum hmuartlgw_state state;
70faa273 61 struct hmuartlgw_dev *dev;
3e34d2ce
MG
62};
63
64
65#define CRC16_POLY 0x8005
66
67static uint16_t crc16(uint8_t* buf, int length)
68{
69 uint16_t crc = 0xd77f;
70 int i;
71
72 while (length--) {
73 crc ^= *buf++ << 8;
74 for (i = 0; i < 8; i++) {
75 if (crc & 0x8000) {
76 crc <<= 1;
77 crc ^= CRC16_POLY;
78 } else {
79 crc <<= 1;
80 }
81 }
82 }
83
84 return crc;
85}
86
87static int hmuartlgw_init_parse(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data)
88{
89 struct recv_data *rdata = data;
90
91#if 0
92 if (debug) {
93 printf("Length: %d\n", buf_len);
94 hexdump(buf, buf_len, "INIT > ");
95 }
96#endif
97
70faa273
MG
98 /* Minimally handle DualCopro-Firmware */
99 if (dst == HMUARTLGW_DUAL) {
100 if ((buf_len == 14) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "DualCoPro_App", 13)) {
101 rdata->state = HMUARTLGW_DUAL_APPLICATION;
102 return 1;
103 }
104
105 switch (rdata->state) {
106 case HMUARTLGW_QUERY_APPSTATE:
107 if ((buf[0] == 0x05) && (buf[1] == 0x01)) {
108 if (!strncmp(((char*)buf)+2, "DualCoPro_App", 13)) {
109 rdata->state = HMUARTLGW_DUAL_APPLICATION;
110 return 1;
37254151
MG
111 } else if (!strncmp(((char*)buf)+2, "HMIP_TRX_App", 12)) {
112 rdata->state = HMUARTLGW_HMIP_APPLICATION;
113 return 1;
114 } else if (!strncmp(((char*)buf)+2, "HMIP_TRX_Bl", 11)) {
2c9eb7ad 115 rdata->state = HMUARTLGW_HMIP_BOOTLOADER;
37254151 116 return 1;
70faa273
MG
117 }
118 }
119 break;
120 case HMUARTLGW_ENTER_BOOTLOADER:
121 if ((buf_len == 2) &&
122 (buf[0] == 0x05) &&
123 (buf[1] == 0x01)) {
124 rdata->state = HMUARTLGW_ENTER_BOOTLOADER_ACK;
125 return 1;
126 }
127 break;
128 default:
129 fprintf(stderr, "Don't know how to handle this state (%d) for unsupported firmware, giving up!\n", rdata->state);
130 exit(1);
131 break;
132 }
133
134 return 0;
135 }
136
137 /* Re-send commands for DualCopro Firmware */
138 if (dst == HMUARTLGW_DUAL_ERR) {
139 uint8_t buf[128] = { 0 };
140
141 switch(rdata->state) {
142 case HMUARTLGW_QUERY_APPSTATE:
143 if (debug) {
144 printf("Re-sending appstate-query for new firmare\n");
145 }
146
147 buf[0] = HMUARTLGW_DUAL_GET_APP;
148 hmuartlgw_send(rdata->dev, buf, 1, HMUARTLGW_DUAL);
149 break;
150 case HMUARTLGW_ENTER_BOOTLOADER:
151 if (debug) {
152 printf("Re-sending switch to bootloader for new firmare\n");
153 }
154
155 buf[0] = HMUARTLGW_DUAL_CHANGE_APP;
156 hmuartlgw_send(rdata->dev, buf, 1, HMUARTLGW_DUAL);
157 break;
158 default:
159 fprintf(stderr, "Don't know how to handle this error-state (%d) for unsupported firmware, giving up!\n", rdata->state);
160 exit(1);
161 break;
162 }
163
164 return 0;
165 }
166
3e34d2ce
MG
167 if (dst != HMUARTLGW_OS)
168 return 0;
169
170 if ((buf_len == 10) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_BL", 9)) {
171 rdata->state = HMUARTLGW_BOOTLOADER;
172 return 1;
173 }
174
175 if ((buf_len == 11) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_App", 10)) {
176 rdata->state = HMUARTLGW_APPLICATION;
177 return 1;
178 }
179
180 switch(rdata->state) {
181 case HMUARTLGW_QUERY_APPSTATE:
9248b988 182 if ((buf[0] == HMUARTLGW_OS_ACK) && (buf[1] == 0x02)) {
3e34d2ce
MG
183 if (!strncmp(((char*)buf)+2, "Co_CPU_BL", 9)) {
184 rdata->state = HMUARTLGW_BOOTLOADER;
185 } else if (!strncmp(((char*)buf)+2, "Co_CPU_App", 10)) {
186 rdata->state = HMUARTLGW_APPLICATION;
187 }
188 }
189 break;
190 case HMUARTLGW_ENTER_BOOTLOADER:
191 if ((buf_len == 2) &&
9248b988 192 (buf[0] == HMUARTLGW_OS_ACK) &&
3e34d2ce
MG
193 (buf[1] == 0x01)) {
194 rdata->state = HMUARTLGW_ENTER_BOOTLOADER_ACK;
195 }
196 break;
197 case HMUARTLGW_ENTER_BOOTLOADER_ACK:
198 rdata->state = HMUARTLGW_ENTER_BOOTLOADER;
199 break;
200 case HMUARTLGW_ENTER_APPLICATION:
201 if ((buf_len == 2) &&
9248b988 202 (buf[0] == HMUARTLGW_OS_ACK) &&
3e34d2ce
MG
203 (buf[1] == 0x01)) {
204 rdata->state = HMUARTLGW_ENTER_APPLICATION_ACK;
205 }
206 break;
207 case HMUARTLGW_ENTER_APPLICATION_ACK:
208 rdata->state = HMUARTLGW_ENTER_APPLICATION;
209 break;
210 default:
211 return 0;
212 break;
213 }
214
37254151
MG
215 /* Try to query current app in case we might be in the DUAL/HMIP-Bootloader */
216 if ((buf[0] == HMUARTLGW_OS_ACK) && (buf[1] == 0x03)) {
217 buf[0] = HMUARTLGW_DUAL_GET_APP;
218 hmuartlgw_send(rdata->dev, buf, 1, HMUARTLGW_DUAL);
219 }
220
3e34d2ce
MG
221 return 1;
222}
223
853cbce9 224struct hmuartlgw_dev *hmuart_init(char *device, hmuartlgw_cb_fn cb, void *data, int app)
3e34d2ce
MG
225{
226 struct hmuartlgw_dev *dev = NULL;
227 struct termios oldtio, tio;
228
229 dev = malloc(sizeof(struct hmuartlgw_dev));
230 if (dev == NULL) {
231 perror("malloc(struct hmuartlgw_dev)");
232 return NULL;
233 }
234
235 memset(dev, 0, sizeof(struct hmuartlgw_dev));
236
237 dev->fd = open(device, O_RDWR | O_NOCTTY);
238 if (dev->fd < 0) {
239 perror("open(hmuartlgw)");
240 goto out;
241 }
242
243 if (debug) {
244 fprintf(stderr, "%s opened\n", device);
245 }
246
247 if (tcgetattr(dev->fd, &oldtio) == -1) {
248 perror("tcgetattr");
249 goto out2;
250 }
251
252 memset(&tio, 0, sizeof(tio));
253
254 tio.c_cflag = B115200 | CS8 | CLOCAL | CREAD;
255 tio.c_iflag = IGNPAR;
256 tio.c_oflag = 0;
257 tio.c_lflag = 0;
258 tio.c_cc[VTIME] = 0;
259 tio.c_cc[VMIN] = 1;
260
261 tcflush(dev->fd, TCIFLUSH);
262 if (tcsetattr(dev->fd, TCSANOW, &tio) == -1) {
263 perror("tcsetattr");
264 goto out2;
265 }
266
267 if (debug) {
268 fprintf(stderr, "serial parameters set\n");
269 }
270
271 hmuartlgw_flush(dev);
272
853cbce9
MG
273 if (app) {
274 hmuartlgw_enter_app(dev);
275 } else {
276 hmuartlgw_enter_bootloader(dev);
277 }
3e34d2ce
MG
278
279 dev->cb = cb;
280 dev->cb_data = data;
281
282 return dev;
283
284out2:
285 close(dev->fd);
286out:
287 free(dev);
288 return NULL;
289}
290
291struct hmuartlgw_dev *hmlgw_init(char *device, hmuartlgw_cb_fn cb, void *data)
292{
293 struct hmuartlgw_dev *dev = NULL;
294
295 return dev;
296}
297
298void hmuartlgw_enter_bootloader(struct hmuartlgw_dev *dev)
299{
300 hmuartlgw_cb_fn cb_old = dev->cb;
301 void *cb_data_old = dev->cb_data;
302 struct recv_data rdata = { 0 };
303 uint8_t buf[128] = { 0 };
8eb01624 304 int ret;
3e34d2ce
MG
305
306 if (debug) {
307 fprintf(stderr, "Entering bootloader\n");
308 }
309
3e34d2ce
MG
310 dev->cb = hmuartlgw_init_parse;
311 dev->cb_data = &rdata;
312
70faa273 313 rdata.dev = dev;
3e34d2ce
MG
314 rdata.state = HMUARTLGW_QUERY_APPSTATE;
315 buf[0] = HMUARTLGW_OS_GET_APP;
316 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
317 do {
8eb01624
MG
318 errno = 0;
319 ret = hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
320 if (ret == -1 && errno == ETIMEDOUT) {
321 fprintf(stderr, "Communication with the module timed out, is the serial port configured correctly?\n");
322 exit(1);
323 }
3e34d2ce
MG
324 } while (rdata.state == HMUARTLGW_QUERY_APPSTATE);
325
37254151 326 if ((rdata.state != HMUARTLGW_BOOTLOADER) &&
2c9eb7ad 327 (rdata.state != HMUARTLGW_HMIP_BOOTLOADER)) {
70faa273 328 rdata.dev = dev;
3e34d2ce
MG
329 rdata.state = HMUARTLGW_ENTER_BOOTLOADER;
330 buf[0] = HMUARTLGW_OS_CHANGE_APP;
331 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
332 do {
8eb01624
MG
333 errno = 0;
334 ret = hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
335 if (ret == -1 && errno == ETIMEDOUT) {
336 fprintf(stderr, "Communication with the module timed out, is the serial port configured correctly?\n");
337 exit(1);
338 }
37254151 339 } while ((rdata.state != HMUARTLGW_BOOTLOADER) &&
2c9eb7ad 340 (rdata.state != HMUARTLGW_HMIP_BOOTLOADER));
3e34d2ce
MG
341
342 printf("Waiting for bootloader to settle...\n");
343 sleep(HMUARTLGW_SETTLE_TIME);
344 }
345
346 dev->cb = cb_old;
347 dev->cb_data = cb_data_old;
348}
349
350void hmuartlgw_enter_app(struct hmuartlgw_dev *dev)
351{
352 hmuartlgw_cb_fn cb_old = dev->cb;
353 void *cb_data_old = dev->cb_data;
354 struct recv_data rdata = { 0 };
355 uint8_t buf[128] = { 0 };
8eb01624 356 int ret;
3e34d2ce
MG
357
358 if (debug) {
359 fprintf(stderr, "Entering application\n");
360 }
361
362 dev->cb = hmuartlgw_init_parse;
363 dev->cb_data = &rdata;
364
70faa273 365 rdata.dev = dev;
3e34d2ce
MG
366 rdata.state = HMUARTLGW_QUERY_APPSTATE;
367 buf[0] = HMUARTLGW_OS_GET_APP;
368 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
369 do {
8eb01624
MG
370 errno = 0;
371 ret = hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
372 if (ret == -1 && errno == ETIMEDOUT) {
373 fprintf(stderr, "Communication with the module timed out, is the serial port configured correctly?\n");
374 exit(1);
375 }
3e34d2ce
MG
376 } while (rdata.state == HMUARTLGW_QUERY_APPSTATE);
377
70faa273
MG
378 if ((rdata.state != HMUARTLGW_APPLICATION) &&
379 (rdata.state != HMUARTLGW_DUAL_APPLICATION)) {
380 rdata.dev = dev;
3e34d2ce
MG
381 rdata.state = HMUARTLGW_ENTER_APPLICATION;
382 buf[0] = HMUARTLGW_OS_CHANGE_APP;
383 hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS);
384 do {
8eb01624
MG
385 errno = 0;
386 ret = hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT);
387 if (ret == -1 && errno == ETIMEDOUT) {
388 fprintf(stderr, "Communication with the module timed out, is the serial port configured correctly?\n");
389 exit(1);
390 }
70faa273
MG
391 } while ((rdata.state != HMUARTLGW_APPLICATION) &&
392 (rdata.state != HMUARTLGW_DUAL_APPLICATION));
3e34d2ce 393
70faa273
MG
394 if (rdata.state == HMUARTLGW_APPLICATION) {
395 printf("Waiting for application to settle...\n");
396 sleep(HMUARTLGW_SETTLE_TIME);
397 }
3e34d2ce
MG
398 }
399
70faa273
MG
400 if (rdata.state == HMUARTLGW_DUAL_APPLICATION) {
401 fprintf(stderr, "Unsupported firmware, please install HM-only firmware!\n");
402 exit(1);
403 }
404
405
3e34d2ce
MG
406 dev->cb = cb_old;
407 dev->cb_data = cb_data_old;
408}
409
410static int hmuartlgw_escape(uint8_t *frame, int framelen)
411{
412 int i;
413
414 for (i = 1; i < framelen; i++) {
415 if (frame[i] == 0xfc || frame[i] == 0xfd) {
416 memmove(frame + i + 1, frame + i, framelen - i);
417 frame[i++] = 0xfc;
418 frame[i] &= 0x7f;
419 framelen++;
420 }
421 }
422 return framelen;
423}
424
425int hmuartlgw_send_raw(struct hmuartlgw_dev *dev, uint8_t *frame, int framelen)
426{
427 int w = 0;
428 int ret;
429
430 if (debug) {
431 hexdump(frame, framelen, "UARTLGW < ");
432 }
433
434 framelen = hmuartlgw_escape(frame, framelen);
435
436 do {
437 ret = write(dev->fd, frame + w, framelen - w);
438 if (ret < 0) {
439 perror("write");
440 return 0;
441 }
442 w += ret;
443 } while (w < framelen);
444
445 return 1;
446}
447
448int hmuartlgw_send(struct hmuartlgw_dev *dev, uint8_t *cmd, int cmdlen, enum hmuartlgw_dst dst)
449{
450 static uint8_t cnt = 0;
853cbce9 451 uint8_t frame[4096] = { 0 };
3e34d2ce
MG
452 uint16_t crc;
453
454 frame[0] = 0xfd;
455 frame[1] = ((cmdlen + 2) >> 8) & 0xff;
456 frame[2] = (cmdlen + 2) & 0xff;
457 frame[3] = dst;
458 dev->last_send_cnt = cnt;
459 frame[4] = cnt++;
460 memcpy(&(frame[5]), cmd, cmdlen);
461 crc = crc16(frame, cmdlen + 5);
462 frame[cmdlen + 5] = (crc >> 8) & 0xff;
463 frame[cmdlen + 6] = crc & 0xff;
464
465 return hmuartlgw_send_raw(dev, frame, cmdlen + 7);
466}
467
468int hmuartlgw_poll(struct hmuartlgw_dev *dev, int timeout)
469{
470 struct pollfd pfds[1];
471 int ret;
472 int r = 0;
473 uint16_t crc;
474
475 errno = 0;
476
477 memset(pfds, 0, sizeof(struct pollfd) * 1);
478
479 pfds[0].fd = dev->fd;
480 pfds[0].events = POLLIN;
481
482 ret = poll(pfds, 1, timeout);
483 if (ret == -1)
484 return -1;
485
486 errno = 0;
487 if (ret == 0) {
488 errno = ETIMEDOUT;
489 return -1;
490 }
491
492 if (!(pfds[0].revents & POLLIN)) {
493 errno = EIO;
494 return -1;
495 }
496
497 r = read(dev->fd, dev->buf+dev->pos, 1);
498 if (r < 0)
499 return -1;
500
501 if (r == 0) {
502 errno = EOF;
503 return -1;
504 }
505
506 dev->pos += r;
507
508 if (dev->buf[0] != 0xfd) {
509 memset(dev->buf, 0, sizeof(dev->buf));
510 dev->pos = 0;
511 dev->unescape_next = 0;
512 return -1;
513 }
514
515 if (dev->unescape_next) {
516 dev->buf[dev->pos-1] |= 0x80;
517 dev->unescape_next = 0;
518 } else if (dev->buf[dev->pos-1] == 0xfc) {
519 dev->unescape_next = 1;
520 dev->pos--;
521 return -1;
522 }
523
524 if (dev->pos >= 3) {
525 uint16_t len;
526
527 len = ((dev->buf[1] << 8) & 0xff00) | (dev->buf[2] & 0xff);
528
529 if (dev->pos < len + 5)
530 return -1;
531 } else {
532 return -1;
533 }
534
b1d2ccc2
MG
535 crc = crc16(dev->buf, dev->pos);
536 if (crc == 0x0000) {
3e34d2ce
MG
537 if (debug)
538 hexdump(dev->buf, dev->pos, "UARTLGW > ");
539
540 dev->cb(dev->buf[3], dev->buf + 5 , dev->pos - 7, dev->cb_data);
541
542 memset(dev->buf, 0, sizeof(dev->buf));
543 dev->pos = 0;
544 dev->unescape_next = 0;
545 } else {
546 fprintf(stderr, "Invalid checksum received!\n");
547 hexdump(dev->buf, dev->pos, "ERR> ");
548 printf("calculated: %04x\n", crc);
549
550 memset(dev->buf, 0, sizeof(dev->buf));
551 dev->pos = 0;
552 dev->unescape_next = 0;
553 }
554
555 errno = 0;
556 return -1;
557}
558
559void hmuartlgw_close(struct hmuartlgw_dev *dev)
560{
561 close(dev->fd);
562}
563
564void hmuartlgw_flush(struct hmuartlgw_dev *dev)
565{
566 struct pollfd pfds[1];
567 int ret;
568 int r = 0;
569 uint8_t buf[1024];
570
571 tcflush(dev->fd, TCIOFLUSH);
572
573 while(1) {
574 memset(pfds, 0, sizeof(struct pollfd) * 1);
575
576 pfds[0].fd = dev->fd;
577 pfds[0].events = POLLIN;
578
579 ret = poll(pfds, 1, 100);
580 if (ret <= 0)
581 break;
582
583 if (!(pfds[0].revents & POLLIN))
584 break;
585
586 memset(buf, 0, sizeof(buf));
587 r = read(dev->fd, buf, sizeof(buf));
588 if (r <= 0)
589 break;
590 }
591
592 return;
593}
594
595void hmuartlgw_set_debug(int d)
596{
597 debug = d;
598}
Impressum, Datenschutz