/* generic firmware-functions for HomeMatic
*
- * Copyright (c) 2014 Michael Gernoth <michael@gernoth.net>
+ * Copyright (c) 2014-20 Michael Gernoth <michael@gernoth.net>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
+#include <unistd.h>
+#include "util.h"
#include "firmware.h"
+#define CRC16_INIT 0xFFFF
+#define CRC16_POLY 0x1021
+
/* This might be wrong, but it works for current fw */
-#define MAX_BLOCK_LENGTH 512
+#define MAX_BLOCK_LENGTH 2048
-static uint8_t ascii_to_nibble(uint8_t a)
+#define HEX_BLOCK_LENGTH_328P 128
+#define HEX_BLOCK_LENGTH_644P 256
+#define HEX_IMAGE_SIZE_328P 0x7000
+#define HEX_IMAGE_SIZE_644P 0xF000
+#define HEX_IMAGE_SIZE_MAX 0xFFFF
+
+static uint16_t crc16(uint8_t* buf, int length, uint16_t crc)
{
- uint8_t c = 0x00;
-
- if ((a >= '0') && (a <= '9')) {
- c = a - '0';
- } else if ((a >= 'A') && (a <= 'F')) {
- c = (a - 'A') + 10;
- } else if ((a >= 'a') && (a <= 'f')) {
- c = (a - 'a') + 10;
+ int i;
+ uint16_t flag;
+
+ while (length--) {
+ for (i = 0; i < 8; i++) {
+ flag = crc & 0x8000;
+ crc <<= 1;
+ if (*buf & 0x80) {
+ crc |= 1;
+ }
+ if (flag) {
+ crc ^= CRC16_POLY;
+ }
+ *buf <<= 1;
+ }
+ buf++;
}
- return c;
+ return crc;
}
-static int validate_nibble(uint8_t a)
+static struct firmware* firmware_read_ihex(int fd, struct firmware *fw, int atmega, int debug)
{
- if (((a >= '0') && (a <= '9')) ||
- ((a >= 'A') && (a <= 'F')) ||
- ((a >= 'a') && (a <= 'f')))
- return 1;
+ uint8_t buf[2*MAX_BLOCK_LENGTH];
+ uint8_t image[HEX_IMAGE_SIZE_MAX];
+ uint16_t len = 0;
+ uint16_t addr = 0;
+ uint16_t type = 0;
+ uint32_t offset = 0;
+ uint32_t image_size = HEX_IMAGE_SIZE_328P;
+ uint32_t block_length = HEX_BLOCK_LENGTH_328P;
+ int r;
+ int i;
+
+ switch (atmega) {
+ case ATMEGA_644P:
+ printf("Using Atmega644P values for direct hex flashing\n");
+ image_size = HEX_IMAGE_SIZE_644P;
+ block_length = HEX_BLOCK_LENGTH_644P;
+ break;
+ case ATMEGA_328P:
+ printf("Using Atmega328P values for direct hex flashing\n");
+ image_size = HEX_IMAGE_SIZE_328P;
+ block_length = HEX_BLOCK_LENGTH_328P;
+ break;
+ default:
+ fprintf(stderr, "Atmega-type (328P/644P) not specified for flashing hex files\n");
+ exit(EXIT_FAILURE);
+ break;
+ }
+
+ memset(image, 0xff, sizeof(image));
+
+ while (1) {
+ memset(buf, 0, sizeof(buf));
+ len = 2 /* len */ + 4 /* len */ + 2 /* type */;
+ r = read(fd, buf, len);
+ if (r < 0) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ } else if (r == 0) {
+ fprintf(stderr, "EOF without EOF record, Firmware file not valid!\n");
+ exit(EXIT_FAILURE);
+ } else if (r != len) {
+ printf("can't get record information!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < r; i++) {
+ if (!validate_nibble(buf[i])) {
+ fprintf(stderr, "Firmware file not valid!\n");
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ len = (ascii_to_nibble(buf[0]) & 0xf)<< 4;
+ len |= ascii_to_nibble(buf[1]) & 0xf;
+
+ addr = (ascii_to_nibble(buf[2]) & 0xf)<< 4;
+ addr |= ascii_to_nibble(buf[3]) & 0xf;
+ addr <<= 8;
+ addr |= (ascii_to_nibble(buf[4]) & 0xf)<< 4;
+ addr |= ascii_to_nibble(buf[5]) & 0xf;
+
+ type = (ascii_to_nibble(buf[6]) & 0xf)<< 4;
+ type |= ascii_to_nibble(buf[7]) & 0xf;
+
+ if (debug)
+ printf("Length: %d, Address: 0x%04x, Type: 0x%02x\n", len, addr, type);
+
+ if (len > MAX_BLOCK_LENGTH) {
+ fprintf(stderr, "Invalid block-length %u > %u for block %d!\n", len, MAX_BLOCK_LENGTH, fw->fw_blocks+1);
+ exit(EXIT_FAILURE);
+ }
+
+ if (type == 0x00) {
+ r = read(fd, buf, (len * 2) + 2 /* crc */);
+ if (r < 0) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ } else if (r == 0) {
+ break;
+ } else if (r < ((len * 2) + 2)) {
+ fprintf(stderr, "short read, aborting (%d < %d)\n", r, (len * 2) + 2);
+ exit(EXIT_FAILURE);
+ }
+
+ for (i = 0; i < len * 2; i+=2) {
+ if ((!validate_nibble(buf[i])) ||
+ (!validate_nibble(buf[i+1]))) {
+ fprintf(stderr, "Firmware file not valid!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ image[addr + (i/2)] = (ascii_to_nibble(buf[i]) & 0xf)<< 4;
+ image[addr + (i/2)] |= ascii_to_nibble(buf[i+1]) & 0xf;
+ }
+
+ while (1) {
+ r = read(fd, buf, 1);
+ if (r < 0) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ } else if (r == 0) {
+ break;
+ } else {
+ if (buf[0] == ':') {
+ break;
+ }
+ }
+ }
+ } else if (type == 0x01) {
+ break;
+ } else {
+ fprintf(stderr, "Can't handle iHex type 0x%02x\n", type);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ image[image_size-2] = 0x00;
+ image[image_size-1] = 0x00;
+
+ while (offset < image_size) {
+ fw->fw = realloc(fw->fw, sizeof(uint8_t*) * (fw->fw_blocks + 1));
+ if (fw->fw == NULL) {
+ perror("Can't reallocate fw->fw-blocklist");
+ exit(EXIT_FAILURE);
+ }
+
+ len = block_length;
+
+ fw->fw[fw->fw_blocks] = malloc(len + 4);
+ if (fw->fw[fw->fw_blocks] == NULL) {
+ perror("Can't allocate memory for fw->fw-block");
+ exit(EXIT_FAILURE);
+ }
+
+ fw->fw[fw->fw_blocks][0] = (fw->fw_blocks >> 8) & 0xff;
+ fw->fw[fw->fw_blocks][1] = fw->fw_blocks & 0xff;
+ fw->fw[fw->fw_blocks][2] = (len >> 8) & 0xff;
+ fw->fw[fw->fw_blocks][3] = len & 0xff;
+
+ memcpy(fw->fw[fw->fw_blocks] + 4, image + offset, len);
+
+ if ((len + offset) == image_size) {
+ uint16_t crc;
+
+ crc = crc16(image, image_size, CRC16_INIT);
- return 0;
+ if (debug)
+ printf("CRC: %04x\n", crc);
+
+ fw->fw[fw->fw_blocks][len+3] = (crc >> 8) & 0xff;
+ fw->fw[fw->fw_blocks][len+2] = crc & 0xff;
+ }
+
+ fw->fw_blocks++;
+ if (debug)
+ printf("Firmware block %d with length %u read.\n", fw->fw_blocks, len);
+
+ offset += len;
+ }
+
+ if (fw->fw_blocks == 0) {
+ fprintf(stderr, "Firmware file not valid!\n");
+ exit(EXIT_FAILURE);
+ }
+
+ printf("Firmware with %d blocks successfully read.\n", fw->fw_blocks);
+
+ return fw;
}
-struct firmware* firmware_read_firmware(char *filename, int debug)
+struct firmware* firmware_read_firmware(char *filename, int atmega, int debug)
{
struct firmware *fw;
struct stat stat_buf;
- uint8_t buf[4096];
+ uint8_t buf[2*MAX_BLOCK_LENGTH];
uint16_t len;
int fd;
int r;
}
printf("Reading firmware from %s...\n", filename);
+
+ memset(buf, 0, sizeof(buf));
+ r = read(fd, buf, 1);
+ if (r != 1) {
+ perror("read");
+ exit(EXIT_FAILURE);
+ }
+
+ //Intel hex?
+ if (buf[0] == ':') {
+ printf("HEX file detected (AsksinPP)\n");
+ return firmware_read_ihex(fd, fw, atmega, debug);
+ }
+
+ if (lseek(fd, 0, SEEK_SET) != 0) {
+ perror("lseek");
+ exit(EXIT_FAILURE);
+ }
+
do {
memset(buf, 0, sizeof(buf));
r = read(fd, buf, 4);