From 3e34d2cea8844557376669f4782f07f5085253c9 Mon Sep 17 00:00:00 2001 From: Michael Gernoth Date: Fri, 3 Jun 2016 20:05:34 +0200 Subject: [PATCH] hmuartlgw: add initial support for HM-MOD-UART The RPi module is now supported in hmsniff and flash-ota. --- .gitignore | 2 + Makefile | 4 +- flash-ota.c | 321 +++++++++++++++++++++++++++--- hm.h | 13 ++ hmsniff.c | 137 ++++++++++--- hmuartlgw.c | 481 +++++++++++++++++++++++++++++++++++++++++++++ hmuartlgw.h | 74 +++++++ reset-hmmoduart.sh | 15 ++ 8 files changed, 994 insertions(+), 53 deletions(-) create mode 100644 hmuartlgw.c create mode 100644 hmuartlgw.h create mode 100755 reset-hmmoduart.sh diff --git a/.gitignore b/.gitignore index df1fea6..e1e2a29 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,8 @@ hmland.o hmsniff hmsniff.d hmsniff.o +hmuartlgw.d +hmuartlgw.o util.d util.o *.enc diff --git a/Makefile b/Makefile index 0cf13bd..1a29116 100644 --- a/Makefile +++ b/Makefile @@ -7,9 +7,9 @@ LDLIBS=-lusb-1.0 -lrt CC=gcc HMLAN_OBJS=hmcfgusb.o hmland.o util.o -HMSNIFF_OBJS=hmcfgusb.o hmsniff.o +HMSNIFF_OBJS=hmcfgusb.o hmuartlgw.o hmsniff.o FLASH_HMCFGUSB_OBJS=hmcfgusb.o firmware.o util.o flash-hmcfgusb.o -FLASH_OTA_OBJS=hmcfgusb.o culfw.o firmware.o util.o flash-ota.o hm.o aes.o +FLASH_OTA_OBJS=hmcfgusb.o culfw.o hmuartlgw.o firmware.o util.o flash-ota.o hm.o aes.o OBJS=$(HMLAN_OBJS) $(HMSNIFF_OBJS) $(FLASH_HMCFGUSB_OBJS) $(FLASH_OTA_OBJS) diff --git a/flash-ota.c b/flash-ota.c index 7291e17..2625209 100644 --- a/flash-ota.c +++ b/flash-ota.c @@ -41,6 +41,7 @@ #include "version.h" #include "hmcfgusb.h" #include "culfw.h" +#include "hmuartlgw.h" #include "util.h" #define MAX_RETRIES 5 @@ -57,22 +58,20 @@ int32_t kNo = -1; /* Maximum payloadlen supported by IO */ uint32_t max_payloadlen = NORMAL_MAX_PAYLOAD; -enum device_type { - DEVICE_TYPE_HMCFGUSB, - DEVICE_TYPE_CULFW, -}; - -struct ota_dev { - int type; - struct hmcfgusb_dev *hmcfgusb; - struct culfw_dev *culfw; -}; - enum message_type { MESSAGE_TYPE_E = 1, MESSAGE_TYPE_R = 2, }; +enum hmuartlgw_state { + HMUARTLGW_STATE_GET_HMID, + HMUARTLGW_STATE_GET_FIRMWARE, + HMUARTLGW_STATE_GET_CREDITS, + HMUARTLGW_STATE_DONE, + HMUARTLGW_STATE_WAIT_APP, + HMUARTLGW_STATE_ACK_APP, +}; + struct recv_data { uint8_t message[64]; enum message_type message_type; @@ -80,6 +79,8 @@ struct recv_data { int speed; uint16_t version; uint8_t credits; + enum hmuartlgw_state uartlgw_state; + uint8_t uartlgw_version[3]; }; static int parse_hmcfgusb(uint8_t *buf, int buf_len, void *data) @@ -205,7 +206,65 @@ static int parse_culfw(uint8_t *buf, int buf_len, void *data) return 1; } -int send_hm_message(struct ota_dev *dev, struct recv_data *rdata, uint8_t *msg) +static int parse_hmuartlgw(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data) +{ + struct recv_data *rdata = data; + + if (dst == HMUARTLGW_OS) { + switch (rdata->uartlgw_state) { + case HMUARTLGW_STATE_GET_FIRMWARE: + if (buf[0] == HMUARTLGW_OS_ACK) { + rdata->uartlgw_version[0] = buf[5]; + rdata->uartlgw_version[1] = buf[6]; + rdata->uartlgw_version[2] = buf[7]; + rdata->uartlgw_state = HMUARTLGW_STATE_DONE; + } + break; + case HMUARTLGW_STATE_GET_CREDITS: + if (buf[0] == HMUARTLGW_OS_ACK) { + rdata->credits = buf[2] / 2; + rdata->uartlgw_state = HMUARTLGW_STATE_DONE; + } + break; + default: + break; + } + return 0; + } + + switch(buf[0]) { + case HMUARTLGW_APP_ACK: + if (rdata->uartlgw_state == HMUARTLGW_STATE_GET_HMID) { + my_hmid = (buf[4] << 16) | (buf[5] << 8) | buf[6]; + } + + rdata->status = buf[1]; + rdata->message_type = MESSAGE_TYPE_R; + rdata->uartlgw_state = HMUARTLGW_STATE_ACK_APP; +#if 0 + hexdump(buf, buf_len, "ACK Status: "); +#endif + + break; + case HMUARTLGW_APP_RECV: + if ((!hmid) || + ((buf[7] == ((hmid >> 16) & 0xff)) && + (buf[8] == ((hmid >> 8) & 0xff)) && + (buf[9] == (hmid & 0xff)))) { + memset(rdata->message, 0, sizeof(rdata->message)); + memcpy(rdata->message + 1, buf + 4, buf_len - 4); + rdata->message[LEN] = buf_len - 4; + rdata->message_type = MESSAGE_TYPE_E; + } + break; + default: + break; + } + + return 1; +} + +int send_hm_message(struct hm_dev *dev, struct recv_data *rdata, uint8_t *msg) { static uint32_t id = 1; struct timeval tv; @@ -351,13 +410,52 @@ int send_hm_message(struct ota_dev *dev, struct recv_data *rdata, uint8_t *msg) } } break; + case DEVICE_TYPE_HMUARTLGW: + memset(out, 0, sizeof(out)); + + out[0] = HMUARTLGW_APP_SEND; + out[1] = 0x00; + out[2] = 0x00; + out[3] = (msg[CTL] & 0x10) ? 0x01 : 0x00; /* Burst?! */ + memcpy(&out[4], &msg[1], msg[0]); + + memset(rdata, 0, sizeof(struct recv_data)); + hmuartlgw_send(dev->hmuartlgw, out, msg[0] + 4, HMUARTLGW_APP); + + while (1) { + if (rdata->message_type == MESSAGE_TYPE_R) { + if ((rdata->status == 0x02) || + (rdata->status == 0x03) || + (rdata->status == 0x0c)) { + break; + } else { + if (rdata->status == 0x0d) { + fprintf(stderr, "\nAES handshake failed!\n"); + } else if (rdata->status == 0x04 || rdata->status == 0x06) { + fprintf(stderr, "\nMissing ACK!\n"); + } else { + fprintf(stderr, "\nInvalid status: %04x\n", rdata->status); + } + return 0; + } + } + errno = 0; + pfd = hmuartlgw_poll(dev->hmuartlgw, 1000); + if ((pfd < 0) && errno) { + if (errno != ETIMEDOUT) { + perror("\n\nhmcfgusb_poll"); + exit(EXIT_FAILURE); + } + } + } + break; } id++; return 1; } -static int switch_speed(struct ota_dev *dev, struct recv_data *rdata, uint8_t speed) +static int switch_speed(struct hm_dev *dev, struct recv_data *rdata, uint8_t speed) { uint8_t out[0x40]; int pfd; @@ -392,6 +490,17 @@ static int switch_speed(struct ota_dev *dev, struct recv_data *rdata, uint8_t sp return culfw_send(dev->culfw, "Ar\r\n", 4); } break; + case DEVICE_TYPE_HMUARTLGW: + if (speed == 100) { + out[0] = HMUARTLGW_OS_UPDATE_MODE; + out[1] = 0xe9; + out[2] = 0xca; + hmuartlgw_send(dev->hmuartlgw, out, 3, HMUARTLGW_OS); + } else { + out[0] = HMUARTLGW_OS_NORMAL_MODE; + hmuartlgw_send(dev->hmuartlgw, out, 1, HMUARTLGW_OS); + } + break; } return 1; @@ -408,6 +517,7 @@ void flash_ota_syntax(char *prog) fprintf(stderr, "\t-b bps\t\tuse CUL with speed \"bps\" (default: %u)\n", DEFAULT_CUL_BPS); fprintf(stderr, "\t-l\t\tlower payloadlen (required for devices with little RAM, e.g. CUL v2 and CUL v4)\n"); fprintf(stderr, "\t-S serial\tuse HM-CFG-USB with given serial\n"); + fprintf(stderr, "\t-U device\tuse HM-MOD-UART on given device\n"); fprintf(stderr, "\t-h\t\tthis help\n"); fprintf(stderr, "\nOptional parameters for automatically sending device to bootloader\n"); fprintf(stderr, "\t-C\t\tHMID of central (3 hex-bytes, no prefix, e.g. ABCDEF)\n"); @@ -424,7 +534,7 @@ int main(int argc, char **argv) char *culfw_dev = NULL; char *endptr = NULL; unsigned int bps = DEFAULT_CUL_BPS; - struct ota_dev dev; + struct hm_dev dev; struct recv_data rdata; uint8_t out[0x40]; uint8_t *pos; @@ -432,6 +542,7 @@ int main(int argc, char **argv) uint16_t len; struct firmware *fw; char *hmcfgusb_serial = NULL; + char *uart = NULL; int block; int pfd; int debug = 0; @@ -443,7 +554,7 @@ int main(int argc, char **argv) printf("HomeMatic OTA flasher version " VERSION "\n\n"); - while((opt = getopt(argc, argv, "b:c:f:hls:C:D:K:S:")) != -1) { + while((opt = getopt(argc, argv, "b:c:f:hls:C:D:K:S:U:")) != -1) { switch (opt) { case 'b': bps = atoi(optarg); @@ -500,6 +611,9 @@ int main(int argc, char **argv) case 'S': hmcfgusb_serial = optarg; break; + case 'U': + uart = optarg; + break; case 'h': case ':': case '?': @@ -521,7 +635,7 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); memset(&rdata, 0, sizeof(rdata)); - memset(&dev, 0, sizeof(struct ota_dev)); + memset(&dev, 0, sizeof(struct hm_dev)); if (culfw_dev) { printf("Opening culfw-device at path %s with speed %u\n", culfw_dev, bps); @@ -564,6 +678,95 @@ int main(int argc, char **argv) fprintf(stderr, "\nThis version does _not_ support firmware upgrade mode, you need at least 1.58!\n"); exit(EXIT_FAILURE); } + } else if (uart) { + uint32_t new_hmid = my_hmid; + + hmuartlgw_set_debug(debug); + + dev.hmuartlgw = hmuart_init(uart, parse_hmuartlgw, &rdata); + if (!dev.hmuartlgw) { + fprintf(stderr, "Can't initialize HM-MOD-UART\n"); + exit(EXIT_FAILURE); + } + dev.type = DEVICE_TYPE_HMUARTLGW; + + out[0] = HMUARTLGW_APP_GET_HMID; + do { + rdata.uartlgw_state = HMUARTLGW_STATE_GET_HMID; + hmuartlgw_send(dev.hmuartlgw, out, 1, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + + out[0] = HMUARTLGW_OS_GET_FIRMWARE; + do { + rdata.uartlgw_state = HMUARTLGW_STATE_GET_FIRMWARE; + hmuartlgw_send(dev.hmuartlgw, out, 1, HMUARTLGW_OS); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_DONE); + } while (rdata.status == 0x08); + + out[0] = HMUARTLGW_OS_GET_CREDITS; + do { + rdata.uartlgw_state = HMUARTLGW_STATE_GET_CREDITS; + hmuartlgw_send(dev.hmuartlgw, out, 1, HMUARTLGW_OS); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_DONE); + } while (rdata.status == 0x08); + + printf("HM-MOD-UART firmware version: %u.%u.%u, used credits: %u%%\n", + rdata.uartlgw_version[0], + rdata.uartlgw_version[1], + rdata.uartlgw_version[2], + rdata.credits); + + if (rdata.credits >= 40) { + printf("\nRebooting HM-MOD-UART to avoid running out of credits\n"); + + hmuartlgw_enter_bootloader(dev.hmuartlgw); + hmuartlgw_enter_app(dev.hmuartlgw); + } + + printf("\nHM-MOD-UART opened\n\n"); + + if (new_hmid && (my_hmid != new_hmid)) { + printf("Changing hmid from %06x to %06x\n", my_hmid, new_hmid); + + out[0] = HMUARTLGW_APP_SET_HMID; + out[1] = (new_hmid >> 16) & 0xff; + out[2] = (new_hmid >> 8) & 0xff; + out[3] = new_hmid & 0xff; + do { + rdata.uartlgw_state = HMUARTLGW_STATE_WAIT_APP; + hmuartlgw_send(dev.hmuartlgw, out, 4, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + + my_hmid = new_hmid; + } + + if (kNo > 0) { + printf("Setting AES-key\n"); + + memset(out, 0, sizeof(out)); + out[0] = HMUARTLGW_APP_SET_CURRENT_KEY; + memcpy(&(out[1]), key, 16); + out[17] = kNo; + + do { + rdata.uartlgw_state = HMUARTLGW_STATE_WAIT_APP; + hmuartlgw_send(dev.hmuartlgw, out, 18, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + + memset(out, 0, sizeof(out)); + out[0] = HMUARTLGW_APP_SET_OLD_KEY; + memcpy(&(out[1]), key, 16); + out[17] = kNo; + + do { + rdata.uartlgw_state = HMUARTLGW_STATE_WAIT_APP; + hmuartlgw_send(dev.hmuartlgw, out, 18, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + } } else { uint32_t new_hmid = my_hmid; @@ -680,6 +883,38 @@ int main(int argc, char **argv) } if (hmid && my_hmid) { + switch (dev.type) { + case DEVICE_TYPE_HMCFGUSB: + printf("Adding HMID\n"); + + memset(out, 0, sizeof(out)); + out[0] = '+'; + out[1] = (hmid >> 16) & 0xff; + out[2] = (hmid >> 8) & 0xff; + out[3] = hmid & 0xff; + + hmcfgusb_send(dev.hmcfgusb, out, sizeof(out), 1); + break; + case DEVICE_TYPE_HMUARTLGW: + printf("Adding HMID\n"); + + memset(out, 0, sizeof(out)); + out[0] = HMUARTLGW_APP_ADD_PEER; + out[1] = (hmid >> 16) & 0xff; + out[2] = (hmid >> 8) & 0xff; + out[3] = hmid & 0xff; + out[4] = (kNo > 0) ? kNo : 0x00; /* KeyIndex */ + out[5] = 0x00; /* WakeUp? */ + out[6] = 0x00; /* WakeUp? */ + + do { + rdata.uartlgw_state = HMUARTLGW_STATE_WAIT_APP; + hmuartlgw_send(dev.hmuartlgw, out, 7, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + + break; + } printf("Sending device with hmid %06x to bootloader\n", hmid); out[CTL] = 0x30; out[TYPE] = 0x11; @@ -713,9 +948,14 @@ int main(int argc, char **argv) pfd = culfw_poll(dev.culfw, 1000); break; case DEVICE_TYPE_HMCFGUSB: - default: pfd = hmcfgusb_poll(dev.hmcfgusb, 1000); break; + case DEVICE_TYPE_HMUARTLGW: + pfd = hmuartlgw_poll(dev.hmuartlgw, 1000); + break; + default: + pfd = -1; + break; } if ((pfd < 0) && errno) { @@ -743,16 +983,37 @@ int main(int argc, char **argv) printf("Device with serial %s (HMID: %06x) entered firmware-update-mode\n", serial, hmid); - if (dev.type == DEVICE_TYPE_HMCFGUSB) { - printf("Adding HMID\n"); + switch (dev.type) { + case DEVICE_TYPE_HMCFGUSB: + printf("Adding HMID\n"); - memset(out, 0, sizeof(out)); - out[0] = '+'; - out[1] = (hmid >> 16) & 0xff; - out[2] = (hmid >> 8) & 0xff; - out[3] = hmid & 0xff; + memset(out, 0, sizeof(out)); + out[0] = '+'; + out[1] = (hmid >> 16) & 0xff; + out[2] = (hmid >> 8) & 0xff; + out[3] = hmid & 0xff; - hmcfgusb_send(dev.hmcfgusb, out, sizeof(out), 1); + hmcfgusb_send(dev.hmcfgusb, out, sizeof(out), 1); + break; + case DEVICE_TYPE_HMUARTLGW: + printf("Adding HMID\n"); + + memset(out, 0, sizeof(out)); + out[0] = HMUARTLGW_APP_ADD_PEER; + out[1] = (hmid >> 16) & 0xff; + out[2] = (hmid >> 8) & 0xff; + out[3] = hmid & 0xff; + out[4] = 0x00; /* KeyIndex */ + out[5] = 0x00; /* WakeUp? */ + out[6] = 0x00; /* WakeUp? */ + + do { + rdata.uartlgw_state = HMUARTLGW_STATE_WAIT_APP; + hmuartlgw_send(dev.hmuartlgw, out, 7, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (rdata.uartlgw_state != HMUARTLGW_STATE_ACK_APP); + } while (rdata.status == 0x08); + + break; } switchcnt = 3; @@ -904,8 +1165,11 @@ int main(int argc, char **argv) } printf("Waiting for device to reboot\n"); + rdata.message_type = MESSAGE_TYPE_R; cnt = 10; + if (dev.type == DEVICE_TYPE_HMUARTLGW) + cnt = 200; /* FIXME */ do { errno = 0; switch(dev.type) { @@ -913,9 +1177,14 @@ int main(int argc, char **argv) pfd = culfw_poll(dev.culfw, 1000); break; case DEVICE_TYPE_HMCFGUSB: - default: pfd = hmcfgusb_poll(dev.hmcfgusb, 1000); break; + case DEVICE_TYPE_HMUARTLGW: + pfd = hmuartlgw_poll(dev.hmuartlgw, 1000); + break; + default: + pfd = -1; + break; } if ((pfd < 0) && errno) { if (errno != ETIMEDOUT) { diff --git a/hm.h b/hm.h index 8126cec..f80c58e 100644 --- a/hm.h +++ b/hm.h @@ -36,4 +36,17 @@ #define SET_LEN_FROM_PAYLOADLEN(buf, payloadlen) do { buf[0x00] = payloadlen + 0x09; } while(0) #define PAYLOADLEN(buf) (buf[0x00] - 0x09) +enum device_type { + DEVICE_TYPE_HMCFGUSB, + DEVICE_TYPE_CULFW, + DEVICE_TYPE_HMUARTLGW, +}; + +struct hm_dev { + int type; + struct hmcfgusb_dev *hmcfgusb; + struct culfw_dev *culfw; + struct hmuartlgw_dev *hmuartlgw; +}; + uint8_t* hm_sign(uint8_t *key, uint8_t *challenge, uint8_t *m_frame, uint8_t *exp_auth, uint8_t *resp); diff --git a/hmsniff.c b/hmsniff.c index 82907f7..638ae1d 100644 --- a/hmsniff.c +++ b/hmsniff.c @@ -36,6 +36,8 @@ #include "version.h" #include "hexdump.h" #include "hmcfgusb.h" +#include "hmuartlgw.h" +#include "hm.h" static int verbose = 0; @@ -213,12 +215,39 @@ static int parse_hmcfgusb(uint8_t *buf, int buf_len, void *data) return 1; } +static int parse_hmuartlgw(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data) +{ + if (dst == HMUARTLGW_OS) { + if ((buf[0] != HMUARTLGW_OS_ACK) || + (buf[1] != 0x01)) { + hexdump(buf, buf_len, "OS> "); + } + return 0; + } + + if (dst != HMUARTLGW_APP) { + return 0; + } + + switch(buf[0]) { + case HMUARTLGW_APP_RECV: + dissect_hm(buf + 3, buf_len - 3); + case HMUARTLGW_APP_ACK: + break; + default: + hexdump(buf, buf_len, "Unknown> "); + } + + return 1; +} + void hmsniff_syntax(char *prog) { fprintf(stderr, "Syntax: %s options\n\n", prog); fprintf(stderr, "Possible options:\n"); fprintf(stderr, "\t-f\t\tfast (100k/firmware update) mode\n"); fprintf(stderr, "\t-S serial\tuse HM-CFG-USB with given serial\n"); + fprintf(stderr, "\t-U device\tuse HM-MOD-UART on given device\n"); fprintf(stderr, "\t-v\t\tverbose mode\n"); fprintf(stderr, "\t-V\t\tshow version (" VERSION ")\n"); @@ -226,15 +255,18 @@ void hmsniff_syntax(char *prog) int main(int argc, char **argv) { - struct hmcfgusb_dev *dev; + struct hm_dev dev = { 0 }; struct recv_data rdata; char *serial = NULL; + char *uart = NULL; int quit = 0; int speed = 10; - uint8_t speed_buf[2]; + uint8_t buf[32]; int opt; - while((opt = getopt(argc, argv, "fS:vV")) != -1) { + dev.type = DEVICE_TYPE_HMCFGUSB; + + while((opt = getopt(argc, argv, "fS:U:vV")) != -1) { switch (opt) { case 'f': speed = 100; @@ -242,6 +274,10 @@ int main(int argc, char **argv) case 'S': serial = optarg; break; + case 'U': + uart = optarg; + dev.type = DEVICE_TYPE_HMUARTLGW; + break; case 'v': verbose = 1; break; @@ -259,58 +295,109 @@ int main(int argc, char **argv) } } - hmcfgusb_set_debug(0); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + hmcfgusb_set_debug(0); + } else { + hmuartlgw_set_debug(0); + } do { memset(&rdata, 0, sizeof(rdata)); rdata.wrong_hmid = 0; - dev = hmcfgusb_init(parse_hmcfgusb, &rdata, serial); - if (!dev) { - fprintf(stderr, "Can't initialize HM-CFG-USB, retrying in 1s...\n"); - sleep(1); - continue; - } - printf("HM-CFG-USB opened!\n"); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + dev.hmcfgusb = hmcfgusb_init(parse_hmcfgusb, &rdata, serial); + if (!dev.hmcfgusb) { + fprintf(stderr, "Can't initialize HM-CFG-USB, retrying in 1s...\n"); + sleep(1); + continue; + } + printf("HM-CFG-USB opened!\n"); - hmcfgusb_send_null_frame(dev, 1); - hmcfgusb_send(dev, (unsigned char*)"K", 1, 1); + hmcfgusb_send_null_frame(dev.hmcfgusb, 1); + hmcfgusb_send(dev.hmcfgusb, (unsigned char*)"K", 1, 1); - hmcfgusb_send_null_frame(dev, 1); - speed_buf[0] = 'G'; - speed_buf[1] = speed; - hmcfgusb_send(dev, speed_buf, 2, 1); + hmcfgusb_send_null_frame(dev.hmcfgusb, 1); + buf[0] = 'G'; + buf[1] = speed; + hmcfgusb_send(dev.hmcfgusb, buf, 2, 1); + } else { + dev.hmuartlgw = hmuart_init(uart, parse_hmuartlgw, &rdata); + if (!dev.hmuartlgw) { + fprintf(stderr, "Can't initialize HM-MOD-UART!\n"); + exit(1); + } + printf("HM-MOD-UART opened!\n"); + + buf[0] = HMUARTLGW_APP_SET_HMID; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + hmuartlgw_send(dev.hmuartlgw, buf, 4, HMUARTLGW_APP); + do { hmuartlgw_poll(dev.hmuartlgw, 500); } while (errno != ETIMEDOUT); + if (speed == 100) { + buf[0] = HMUARTLGW_OS_UPDATE_MODE; + buf[1] = 0xe9; + buf[2] = 0xca; + hmuartlgw_send(dev.hmuartlgw, buf, 3, HMUARTLGW_OS); + } else { + buf[0] = HMUARTLGW_OS_NORMAL_MODE; + hmuartlgw_send(dev.hmuartlgw, buf, 1, HMUARTLGW_OS); + } + } while(!quit) { int fd; if (rdata.wrong_hmid) { printf("changing hmId to 000000, this might reboot the device!\n"); - hmcfgusb_send(dev, (unsigned char*)"A\00\00\00", 4, 1); - rdata.wrong_hmid = 0; - hmcfgusb_send(dev, (unsigned char*)"K", 1, 1); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + hmcfgusb_send(dev.hmcfgusb, (unsigned char*)"A\00\00\00", 4, 1); + rdata.wrong_hmid = 0; + hmcfgusb_send(dev.hmcfgusb, (unsigned char*)"K", 1, 1); + } else { + buf[0] = HMUARTLGW_APP_SET_HMID; + buf[1] = 0x00; + buf[2] = 0x00; + buf[3] = 0x00; + hmuartlgw_send(dev.hmuartlgw, buf, 4, HMUARTLGW_APP); + } + } + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + fd = hmcfgusb_poll(dev.hmcfgusb, 1000); + } else { + fd = hmuartlgw_poll(dev.hmuartlgw, 60000); } - fd = hmcfgusb_poll(dev, 1000); if (fd >= 0) { fprintf(stderr, "activity on unknown fd %d!\n", fd); continue; } else if (fd == -1) { if (errno) { if (errno != ETIMEDOUT) { - perror("hmcfgusb_poll"); + perror("hmsniff_poll"); break; } else { /* periodically wakeup the device */ - hmcfgusb_send_null_frame(dev, 1); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + hmcfgusb_send_null_frame(dev.hmcfgusb, 1); + } } } } } - hmcfgusb_close(dev); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + if (dev.hmcfgusb) + hmcfgusb_close(dev.hmcfgusb); + } else { + if (dev.hmuartlgw) + hmuartlgw_close(dev.hmuartlgw); + } } while (!quit); - hmcfgusb_exit(); + if (dev.type == DEVICE_TYPE_HMCFGUSB) { + hmcfgusb_exit(); + } return EXIT_SUCCESS; } diff --git a/hmuartlgw.c b/hmuartlgw.c new file mode 100644 index 0000000..ccb5dbc --- /dev/null +++ b/hmuartlgw.c @@ -0,0 +1,481 @@ +/* HM-MOD-UART/HM-LGW-O-TW-W-EU driver + * + * Copyright (c) 2016 Michael Gernoth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hexdump.h" +#include "hmuartlgw.h" + +#define HMUARTLGW_INIT_TIMEOUT 10000 + +#define HMUARTLGW_SETTLE_TIME 2 + +static int debug = 0; + +enum hmuartlgw_state { + HMUARTLGW_QUERY_APPSTATE, + HMUARTLGW_ENTER_BOOTLOADER, + HMUARTLGW_ENTER_BOOTLOADER_ACK, + HMUARTLGW_BOOTLOADER, + HMUARTLGW_ENTER_APPLICATION, + HMUARTLGW_ENTER_APPLICATION_ACK, + HMUARTLGW_APPLICATION, +}; + +struct recv_data { + enum hmuartlgw_state state; +}; + + +#define CRC16_POLY 0x8005 + +static uint16_t crc16(uint8_t* buf, int length) +{ + uint16_t crc = 0xd77f; + int i; + + while (length--) { + crc ^= *buf++ << 8; + for (i = 0; i < 8; i++) { + if (crc & 0x8000) { + crc <<= 1; + crc ^= CRC16_POLY; + } else { + crc <<= 1; + } + } + } + + return crc; +} + +static int hmuartlgw_init_parse(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data) +{ + struct recv_data *rdata = data; + +#if 0 + if (debug) { + printf("Length: %d\n", buf_len); + hexdump(buf, buf_len, "INIT > "); + } +#endif + + if (dst != HMUARTLGW_OS) + return 0; + + if ((buf_len == 10) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_BL", 9)) { + rdata->state = HMUARTLGW_BOOTLOADER; + return 1; + } + + if ((buf_len == 11) && (buf[0] == 0x00) && !strncmp(((char*)buf)+1, "Co_CPU_App", 10)) { + rdata->state = HMUARTLGW_APPLICATION; + return 1; + } + + switch(rdata->state) { + case HMUARTLGW_QUERY_APPSTATE: + if ((buf[0] == 0x04) && (buf[1] == 0x02)) { + if (!strncmp(((char*)buf)+2, "Co_CPU_BL", 9)) { + rdata->state = HMUARTLGW_BOOTLOADER; + } else if (!strncmp(((char*)buf)+2, "Co_CPU_App", 10)) { + rdata->state = HMUARTLGW_APPLICATION; + } + } + break; + case HMUARTLGW_ENTER_BOOTLOADER: + if ((buf_len == 2) && + (buf[0] == 0x04) && + (buf[1] == 0x01)) { + rdata->state = HMUARTLGW_ENTER_BOOTLOADER_ACK; + } + break; + case HMUARTLGW_ENTER_BOOTLOADER_ACK: + rdata->state = HMUARTLGW_ENTER_BOOTLOADER; + break; + case HMUARTLGW_ENTER_APPLICATION: + if ((buf_len == 2) && + (buf[0] == 0x04) && + (buf[1] == 0x01)) { + rdata->state = HMUARTLGW_ENTER_APPLICATION_ACK; + } + break; + case HMUARTLGW_ENTER_APPLICATION_ACK: + rdata->state = HMUARTLGW_ENTER_APPLICATION; + break; + default: + return 0; + break; + } + + return 1; +} + +struct hmuartlgw_dev *hmuart_init(char *device, hmuartlgw_cb_fn cb, void *data) +{ + struct hmuartlgw_dev *dev = NULL; + struct termios oldtio, tio; + + dev = malloc(sizeof(struct hmuartlgw_dev)); + if (dev == NULL) { + perror("malloc(struct hmuartlgw_dev)"); + return NULL; + } + + memset(dev, 0, sizeof(struct hmuartlgw_dev)); + + dev->fd = open(device, O_RDWR | O_NOCTTY); + if (dev->fd < 0) { + perror("open(hmuartlgw)"); + goto out; + } + + if (debug) { + fprintf(stderr, "%s opened\n", device); + } + + if (tcgetattr(dev->fd, &oldtio) == -1) { + perror("tcgetattr"); + goto out2; + } + + memset(&tio, 0, sizeof(tio)); + + tio.c_cflag = B115200 | CS8 | CLOCAL | CREAD; + tio.c_iflag = IGNPAR; + tio.c_oflag = 0; + tio.c_lflag = 0; + tio.c_cc[VTIME] = 0; + tio.c_cc[VMIN] = 1; + + tcflush(dev->fd, TCIFLUSH); + if (tcsetattr(dev->fd, TCSANOW, &tio) == -1) { + perror("tcsetattr"); + goto out2; + } + + if (debug) { + fprintf(stderr, "serial parameters set\n"); + } + + hmuartlgw_flush(dev); + + hmuartlgw_enter_app(dev); + + dev->cb = cb; + dev->cb_data = data; + + return dev; + +out2: + close(dev->fd); +out: + free(dev); + return NULL; +} + +struct hmuartlgw_dev *hmlgw_init(char *device, hmuartlgw_cb_fn cb, void *data) +{ + struct hmuartlgw_dev *dev = NULL; + + return dev; +} + +void hmuartlgw_enter_bootloader(struct hmuartlgw_dev *dev) +{ + hmuartlgw_cb_fn cb_old = dev->cb; + void *cb_data_old = dev->cb_data; + struct recv_data rdata = { 0 }; + uint8_t buf[128] = { 0 }; + + if (debug) { + fprintf(stderr, "Entering bootloader\n"); + } + + buf[0] = HMUARTLGW_OS_CHANGE_APP; + + dev->cb = hmuartlgw_init_parse; + dev->cb_data = &rdata; + + rdata.state = HMUARTLGW_QUERY_APPSTATE; + buf[0] = HMUARTLGW_OS_GET_APP; + hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS); + do { + hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT); + } while (rdata.state == HMUARTLGW_QUERY_APPSTATE); + + if (rdata.state != HMUARTLGW_BOOTLOADER) { + rdata.state = HMUARTLGW_ENTER_BOOTLOADER; + buf[0] = HMUARTLGW_OS_CHANGE_APP; + hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS); + do { + hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT); + } while (rdata.state != HMUARTLGW_BOOTLOADER); + + printf("Waiting for bootloader to settle...\n"); + sleep(HMUARTLGW_SETTLE_TIME); + } + + dev->cb = cb_old; + dev->cb_data = cb_data_old; +} + +void hmuartlgw_enter_app(struct hmuartlgw_dev *dev) +{ + hmuartlgw_cb_fn cb_old = dev->cb; + void *cb_data_old = dev->cb_data; + struct recv_data rdata = { 0 }; + uint8_t buf[128] = { 0 }; + + if (debug) { + fprintf(stderr, "Entering application\n"); + } + + dev->cb = hmuartlgw_init_parse; + dev->cb_data = &rdata; + + rdata.state = HMUARTLGW_QUERY_APPSTATE; + buf[0] = HMUARTLGW_OS_GET_APP; + hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS); + do { + hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT); + } while (rdata.state == HMUARTLGW_QUERY_APPSTATE); + + if (rdata.state != HMUARTLGW_APPLICATION) { + rdata.state = HMUARTLGW_ENTER_APPLICATION; + buf[0] = HMUARTLGW_OS_CHANGE_APP; + hmuartlgw_send(dev, buf, 1, HMUARTLGW_OS); + do { + hmuartlgw_poll(dev, HMUARTLGW_INIT_TIMEOUT); + } while (rdata.state != HMUARTLGW_APPLICATION); + + printf("Waiting for application to settle...\n"); + sleep(HMUARTLGW_SETTLE_TIME); + } + + dev->cb = cb_old; + dev->cb_data = cb_data_old; +} + +static int hmuartlgw_escape(uint8_t *frame, int framelen) +{ + int i; + + for (i = 1; i < framelen; i++) { + if (frame[i] == 0xfc || frame[i] == 0xfd) { + memmove(frame + i + 1, frame + i, framelen - i); + frame[i++] = 0xfc; + frame[i] &= 0x7f; + framelen++; + } + } + return framelen; +} + +int hmuartlgw_send_raw(struct hmuartlgw_dev *dev, uint8_t *frame, int framelen) +{ + int w = 0; + int ret; + + if (debug) { + hexdump(frame, framelen, "UARTLGW < "); + } + + framelen = hmuartlgw_escape(frame, framelen); + + do { + ret = write(dev->fd, frame + w, framelen - w); + if (ret < 0) { + perror("write"); + return 0; + } + w += ret; + } while (w < framelen); + + return 1; +} + +int hmuartlgw_send(struct hmuartlgw_dev *dev, uint8_t *cmd, int cmdlen, enum hmuartlgw_dst dst) +{ + static uint8_t cnt = 0; + uint8_t frame[1024] = { 0 }; + uint16_t crc; + + frame[0] = 0xfd; + frame[1] = ((cmdlen + 2) >> 8) & 0xff; + frame[2] = (cmdlen + 2) & 0xff; + frame[3] = dst; + dev->last_send_cnt = cnt; + frame[4] = cnt++; + memcpy(&(frame[5]), cmd, cmdlen); + crc = crc16(frame, cmdlen + 5); + frame[cmdlen + 5] = (crc >> 8) & 0xff; + frame[cmdlen + 6] = crc & 0xff; + + return hmuartlgw_send_raw(dev, frame, cmdlen + 7); +} + +int hmuartlgw_poll(struct hmuartlgw_dev *dev, int timeout) +{ + struct pollfd pfds[1]; + int ret; + int r = 0; + uint16_t crc; + + errno = 0; + + memset(pfds, 0, sizeof(struct pollfd) * 1); + + pfds[0].fd = dev->fd; + pfds[0].events = POLLIN; + + ret = poll(pfds, 1, timeout); + if (ret == -1) + return -1; + + errno = 0; + if (ret == 0) { + errno = ETIMEDOUT; + return -1; + } + + if (!(pfds[0].revents & POLLIN)) { + errno = EIO; + return -1; + } + + r = read(dev->fd, dev->buf+dev->pos, 1); + if (r < 0) + return -1; + + if (r == 0) { + errno = EOF; + return -1; + } + + dev->pos += r; + + if (dev->buf[0] != 0xfd) { + memset(dev->buf, 0, sizeof(dev->buf)); + dev->pos = 0; + dev->unescape_next = 0; + return -1; + } + + if (dev->unescape_next) { + dev->buf[dev->pos-1] |= 0x80; + dev->unescape_next = 0; + } else if (dev->buf[dev->pos-1] == 0xfc) { + dev->unescape_next = 1; + dev->pos--; + return -1; + } + + if (dev->pos >= 3) { + uint16_t len; + + len = ((dev->buf[1] << 8) & 0xff00) | (dev->buf[2] & 0xff); + + if (dev->pos < len + 5) + return -1; + } else { + return -1; + } + + crc = crc16(dev->buf, dev->pos - 2); + if ((((crc >> 8) & 0xff) == dev->buf[dev->pos - 2]) && + ((crc & 0xff) == dev->buf[dev->pos - 1])) { + + if (debug) + hexdump(dev->buf, dev->pos, "UARTLGW > "); + + dev->cb(dev->buf[3], dev->buf + 5 , dev->pos - 7, dev->cb_data); + + memset(dev->buf, 0, sizeof(dev->buf)); + dev->pos = 0; + dev->unescape_next = 0; + } else { + fprintf(stderr, "Invalid checksum received!\n"); + hexdump(dev->buf, dev->pos, "ERR> "); + printf("calculated: %04x\n", crc); + + memset(dev->buf, 0, sizeof(dev->buf)); + dev->pos = 0; + dev->unescape_next = 0; + } + + errno = 0; + return -1; +} + +void hmuartlgw_close(struct hmuartlgw_dev *dev) +{ + close(dev->fd); +} + +void hmuartlgw_flush(struct hmuartlgw_dev *dev) +{ + struct pollfd pfds[1]; + int ret; + int r = 0; + uint8_t buf[1024]; + + tcflush(dev->fd, TCIOFLUSH); + + while(1) { + memset(pfds, 0, sizeof(struct pollfd) * 1); + + pfds[0].fd = dev->fd; + pfds[0].events = POLLIN; + + ret = poll(pfds, 1, 100); + if (ret <= 0) + break; + + if (!(pfds[0].revents & POLLIN)) + break; + + memset(buf, 0, sizeof(buf)); + r = read(dev->fd, buf, sizeof(buf)); + if (r <= 0) + break; + } + + return; +} + +void hmuartlgw_set_debug(int d) +{ + debug = d; +} diff --git a/hmuartlgw.h b/hmuartlgw.h new file mode 100644 index 0000000..8281bef --- /dev/null +++ b/hmuartlgw.h @@ -0,0 +1,74 @@ +/* HM-MOD-UART/HM-LGW-O-TW-W-EU driver + * + * Copyright (c) 2016 Michael Gernoth + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#define HMUARTLGW_OS_GET_APP 0x00 +#define HMUARTLGW_OS_GET_FIRMWARE 0x02 +#define HMUARTLGW_OS_CHANGE_APP 0x03 +#define HMUARTLGW_OS_ACK 0x04 +#define HMUARTLGW_OS_UNSOL_CREDITS 0x05 +#define HMUARTLGW_OS_NORMAL_MODE 0x06 +#define HMUARTLGW_OS_UPDATE_MODE 0x07 +#define HMUARTLGW_OS_GET_CREDITS 0x08 +#define HMUARTLGW_OS_GET_SERIAL 0x0B +#define HMUARTLGW_OS_SET_TIME 0x0E + +#define HMUARTLGW_APP_SET_HMID 0x00 +#define HMUARTLGW_APP_GET_HMID 0x01 +#define HMUARTLGW_APP_SEND 0x02 +#define HMUARTLGW_APP_SET_CURRENT_KEY 0x03 /* key index */ +#define HMUARTLGW_APP_ACK 0x04 +#define HMUARTLGW_APP_RECV 0x05 +#define HMUARTLGW_APP_ADD_PEER 0x06 +#define HMUARTLGW_APP_REMOVE_PEER 0x07 +#define HMUARTLGW_APP_PEER_ADD_AES 0x09 +#define HMUARTLGW_APP_PEER_REMOVE_AES 0x0A +#define HMUARTLGW_APP_SET_OLD_KEY 0x0F /* key index */ +#define HMUARTLGW_APP_DEFAULT_HMID 0x10 + +enum hmuartlgw_dst { + HMUARTLGW_OS = 0, + HMUARTLGW_APP = 1, +}; + +typedef int (*hmuartlgw_cb_fn)(enum hmuartlgw_dst dst, uint8_t *buf, int buf_len, void *data); + +struct hmuartlgw_dev { + int fd; + hmuartlgw_cb_fn cb; + void *cb_data; + uint8_t last_send_cnt; + uint8_t buf[1024]; + int pos; + int unescape_next; +}; + +struct hmuartlgw_dev *hmuart_init(char *device, hmuartlgw_cb_fn cb, void *data); +struct hmuartlgw_dev *hmlgw_init(char *device, hmuartlgw_cb_fn cb, void *data); +int hmuartlgw_send_raw(struct hmuartlgw_dev *dev, uint8_t *frame, int framelen); +int hmuartlgw_send(struct hmuartlgw_dev *dev, uint8_t *cmd, int cmdlen, enum hmuartlgw_dst dst); +int hmuartlgw_poll(struct hmuartlgw_dev *dev, int timeout); +void hmuartlgw_close(struct hmuartlgw_dev *dev); +void hmuartlgw_flush(struct hmuartlgw_dev *dev); +void hmuartlgw_enter_bootloader(struct hmuartlgw_dev *dev); +void hmuartlgw_enter_app(struct hmuartlgw_dev *dev); +void hmuartlgw_set_debug(int d); diff --git a/reset-hmmoduart.sh b/reset-hmmoduart.sh new file mode 100755 index 0000000..d49ec6d --- /dev/null +++ b/reset-hmmoduart.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +if [ "`id -u`" != "0" ]; then + exec sudo "${0}" +fi + +if [ ! -d "/sys/class/gpio/gpio18" ]; then + echo 18 >/sys/class/gpio/export +fi + +echo out >/sys/class/gpio/gpio18/direction +echo 0 >/sys/class/gpio/gpio18/value +sleep 0.2 +echo 1 >/sys/class/gpio/gpio18/value +sleep 2 -- 2.39.2