Преглед изворни кода

change_baud_rate and verify_md5 commands added.

Response structures and response reading function had to be modified
in order to allow MD5 command response to be read.
Tests were updated as well.
Martin Valik пре 6 година
родитељ
комит
994d09655a

+ 1 - 0
CMakeLists.txt

@@ -1,6 +1,7 @@
 set(COMPONENT_SRCS 
     "src/esp_loader.c"
     "src/serial_comm.c"
+    "src/md5_hash.c"
     "port/esp32_uart.c")
 
 set(COMPONENT_ADD_INCLUDEDIRS "include")

+ 23 - 1
example/main/serial_flash_example_main.c

@@ -18,8 +18,11 @@
 
 static const char *TAG = "example";
 
+const uint32_t HIGHER_BAUD_RATE = 921600;
 const uint32_t APP_START_ADDRESS = 0x10000;
-static char payload[1024];
+static uint8_t payload[1024];
+
+esp_loader_error_t loader_port_change_baudrate(uint32_t baudrate);
 
 
 static void flash_binary(FILE *image, size_t image_size)
@@ -35,6 +38,18 @@ static void flash_binary(FILE *image, size_t image_size)
     }
     ESP_LOGI(TAG, "Connected to target");
 
+    err = esp_loader_change_baudrate(HIGHER_BAUD_RATE);
+    if (err != ESP_LOADER_SUCCESS) {
+        ESP_LOGE(TAG, "Unable to change baud rate on target.");
+        return;
+    }
+
+    err = loader_port_change_baudrate(HIGHER_BAUD_RATE);
+    if (err != ESP_LOADER_SUCCESS) {
+        ESP_LOGE(TAG, "Unable to change baud rate.");
+        return;
+    }
+
     err = esp_loader_flash_start(APP_START_ADDRESS, image_size, sizeof(payload));
     if (err != ESP_LOADER_SUCCESS) {
         ESP_LOGE(TAG, "Flash start operation failed.");
@@ -63,6 +78,13 @@ static void flash_binary(FILE *image, size_t image_size)
     };
 
     ESP_LOGI(TAG, "Finished programming");
+
+    err = esp_loader_flash_verify();
+    if (err != ESP_LOADER_SUCCESS) {
+        ESP_LOGE(TAG, "MD5 does not match. err: %d", err);
+        return;
+    }
+    ESP_LOGI(TAG, "Flash verified");
 }
 
 

BIN
example/spiffs_image/hello-world.bin


+ 33 - 0
include/esp_loader.h

@@ -30,6 +30,7 @@ typedef enum
     ESP_LOADER_SUCCESS,                /*!< Success */
     ESP_LOADER_ERROR_FAIL,             /*!< Unspecified error */
     ESP_LOADER_ERROR_TIMEOUT,          /*!< Timeout elapsed */
+    ESP_LOADER_ERROR_INVALID_MD5,      /*!< Computed and receied MD5 does not match */
     ESP_LOADER_ERROR_INVALID_RESPONSE  /*!< Internal error */
 } esp_loader_error_t;
 
@@ -133,6 +134,38 @@ esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_valu
   */
 esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value);
 
+/**
+  * @brief Change baud rate.
+  *
+  * @note  Baud rate has to be also adjusted accordingly on host MCU, as
+  *        target's baud rate is changed upon return from this function.
+  *
+  * @param baudrate[in]     new baud rate to be set.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_change_baudrate(uint32_t baudrate);
+
+/**
+  * @brief Verify target's flash integrity by checking MD5.
+  *        MD5 checksum is computed from data pushed to target's memory by calling
+  *        esp_loader_flash_write() function and compared against target's MD5.
+  *        Target computes checksum based on offset and image_size passed to 
+  *        esp_loader_flash_start() function.
+  *
+  * @note  This function is only available if MD5_ENABLED is set.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_INVALID_MD5 MD5 does not match
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_flash_verify(void);
+
 /**
   * @brief Toggles reset pin.
   */

+ 31 - 0
include/loader_config.h

@@ -0,0 +1,31 @@
+/* Copyright 2020 Espressif Systems (Shanghai) PTE LTD
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// MD5 checksum can be used to check, if new image was flashed successfully.
+// When enabled, esp_loader_flash_verify() function can be called to to verify
+// flash integrity. In case verification is unnecessary, this option can be 
+// disabled in order to reduce code size.
+#define MD5_ENABLED  1
+
+
+#ifdef __cplusplus
+}
+#endif

+ 6 - 0
port/esp32_uart.c

@@ -153,4 +153,10 @@ uint32_t loader_port_remaining_time(void)
 void loader_port_debug_print(const char *str)
 {
     printf("DEBUG: %s\n", str);
+}
+
+esp_loader_error_t loader_port_change_baudrate(uint32_t baudrate)
+{
+    esp_err_t err = uart_set_baudrate(UART_PORT, baudrate);
+    return (err == ESP_OK) ? ESP_LOADER_SUCCESS : ESP_LOADER_ERROR_FAIL;
 }

+ 28 - 0
private_include/md5_hash.h

@@ -0,0 +1,28 @@
+/*
+ * MD5 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+struct MD5Context {
+	uint32_t buf[4];
+	uint32_t bits[2];
+	uint8_t in[64];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, unsigned char const *buf, unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+
+#ifdef __cplusplus
+}
+#endif

+ 2 - 5
private_include/serial_comm.h

@@ -48,12 +48,9 @@ esp_loader_error_t loader_sync_cmd(void);
 
 esp_loader_error_t loader_spi_attach_cmd(uint32_t config);
 
+esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate);
 
-// TODO implement the rest of supported commands
-// esp_loader_error_t set_spi_params(uint32_t *params);
-// esp_loader_error_t change_baudrate(uint32_t *baud);
-// esp_loader_error_t flash_write_defl();
-// esp_loader_error_t spi_flash_md5_is(uint8_t data[16]);
+esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out);
 
 
 #ifdef __cplusplus

+ 35 - 1
private_include/serial_comm_prv.h

@@ -28,6 +28,8 @@ extern "C" {
 #define READ_DIRECTION  1
 #define WRITE_DIRECTION 0
 
+#define MD5_SIZE 32
+
 typedef enum __attribute__((packed))
 {
     FLASH_BEGIN = 0x02,
@@ -128,18 +130,50 @@ typedef struct __attribute__((packed))
     uint32_t zero; // ESP32 ROM only
 } spi_attach_command_t;
 
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t new_baudrate;
+    uint32_t old_baudrate;
+} change_baudrate_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t address;
+    uint32_t size;
+    uint32_t reserved_0;
+    uint32_t reserved_1;
+} spi_flash_md5_command_t;
+
 typedef struct __attribute__((packed))
 {
     uint8_t direction;
     uint8_t command;    // One of command_t
     uint16_t size;
     uint32_t value;
-    uint8_t status;
+} common_response_t;
+
+typedef struct __attribute__((packed))
+{
+    uint8_t failed;
     uint8_t error;
     uint8_t reserved_0; // ESP32 ROM only
     uint8_t reserved_1; // ESP32 ROM only
+} response_status_t;
+
+typedef struct __attribute__((packed))
+{
+    common_response_t common;
+    response_status_t status;
 } response_t;
 
+typedef struct __attribute__((packed))
+{
+    common_response_t common;
+    uint8_t md5[MD5_SIZE];     // ROM only
+    response_status_t status;
+} rom_md5_response_t;
 
 #ifdef __cplusplus
 }

+ 115 - 15
src/esp_loader.c

@@ -13,9 +13,13 @@
  * limitations under the License.
  */
 
+#include "loader_config.h"
+#include "serial_comm_prv.h"
 #include "serial_comm.h"
 #include "serial_io.h"
 #include "esp_loader.h"
+#include "md5_hash.h"
+#include <string.h>
 
 #ifndef MAX
 #define MAX(a, b) ((a) > (b)) ? (a) : (b)
@@ -30,8 +34,40 @@ static const uint8_t  PADDING_PATTERN = 0xFF;
 
 static uint32_t s_flash_write_size = 0;
 
+#if MD5_ENABLED
 
-static uint32_t timeout_per_mb(uint32_t size_bytes)
+static const uint32_t MD5_TIMEOUT_PER_MB = 800;
+static struct MD5Context s_md5_context;
+static uint32_t s_start_address;
+static uint32_t s_image_size;
+
+static inline void init_md5(uint32_t address, uint32_t size)
+{
+    s_start_address = address;
+    s_image_size = size;
+    MD5Init(&s_md5_context);
+}
+
+static inline void md5_update(const uint8_t *data, uint32_t size)
+{
+    MD5Update(&s_md5_context, data, size);
+}
+
+static inline void md5_final(uint8_t digets[16])
+{
+    MD5Final(digets, &s_md5_context);
+}
+
+#else
+
+static inline void init_md5(uint32_t address, uint32_t size) { }
+static inline void md5_update(const uint8_t *data, uint32_t size) { }
+static inline void md5_final(uint8_t digets[16]) { }
+
+#endif
+
+
+static uint32_t timeout_per_mb(uint32_t size_bytes, uint32_t time_per_mb)
 {
     uint32_t timeout = ERASE_REGION_TIMEOUT_PER_MB * (size_bytes / 1e6);
     return MAX(timeout, DEFAULT_FLASH_TIMEOUT);
@@ -40,22 +76,26 @@ static uint32_t timeout_per_mb(uint32_t size_bytes)
 
 esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args)
 {
+    esp_loader_error_t err;
     int32_t trials = connect_args->trials;
 
     loader_port_enter_bootloader();
 
     do {
         loader_port_start_timer(connect_args->sync_timeout);
-        esp_loader_error_t err = loader_sync_cmd();
-        if (err != ESP_LOADER_ERROR_TIMEOUT) {
-            return err;
-        }
-        if (--trials > 0) {
+        err = loader_sync_cmd();
+        if (err == ESP_LOADER_ERROR_TIMEOUT) {
+            if (--trials == 0) {
+                return ESP_LOADER_ERROR_TIMEOUT;
+            }
             loader_port_delay_ms(100);
+        } else if (err != ESP_LOADER_SUCCESS) {
+            return err;
         }
-    } while (trials > 0);
+    } while (err != ESP_LOADER_SUCCESS);
 
-    return ESP_LOADER_ERROR_TIMEOUT;
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+    return loader_spi_attach_cmd(SPI_PIN_CONFIG_DEFAULT);
 }
 
 
@@ -65,11 +105,9 @@ esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size,
     uint32_t erase_size = block_size * blocks_to_write;
     s_flash_write_size = block_size;
 
-    loader_port_start_timer(DEFAULT_TIMEOUT);
-
-    RETURN_ON_ERROR( loader_spi_attach_cmd(SPI_PIN_CONFIG_DEFAULT) );
+    init_md5(offset, image_size);
 
-    loader_port_start_timer(timeout_per_mb(erase_size));
+    loader_port_start_timer(timeout_per_mb(erase_size, ERASE_REGION_TIMEOUT_PER_MB));
 
     return loader_flash_begin_cmd(offset, erase_size, block_size, blocks_to_write);
 }
@@ -79,11 +117,14 @@ esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size)
 {
     uint32_t padding_bytes = s_flash_write_size - size;
     uint8_t *data = (uint8_t *)payload;
+    uint32_t padding_index = size;
 
     while (padding_bytes--) {
-        data[size++] = PADDING_PATTERN;
+        data[padding_index++] = PADDING_PATTERN;
     }
 
+    md5_update(payload, (size + 3) & ~3);
+
     loader_port_start_timer(DEFAULT_TIMEOUT);
 
     return loader_flash_data_cmd(data, s_flash_write_size);
@@ -113,8 +154,67 @@ esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_valu
     return loader_write_reg_cmd(address, reg_value, 0xFFFFFFFF, 0);
 }
 
-void esp_loader_reset_target(void)
+esp_loader_error_t esp_loader_change_baudrate(uint32_t baudrate)
 {
-    loader_port_reset_target();
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+
+    return loader_change_baudrate_cmd(baudrate);
+}
+
+#if MD5_ENABLED
+
+static void hexify(const uint8_t raw_md5[16], uint8_t hex_md5_out[32])
+{
+    uint8_t high_nibble, low_nibble;
+
+    static const uint8_t dec_to_hex[] = {
+        '0', '1', '2', '3', '4', '5', '6', '7',
+        '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    for (int i = 0; i < 16; i++) {
+        high_nibble = (raw_md5[i] / 16);
+        low_nibble = (raw_md5[i] - (high_nibble * 16));
+        *hex_md5_out++ = dec_to_hex[high_nibble];
+        *hex_md5_out++ = dec_to_hex[low_nibble];
+    }
+}
+
+
+esp_loader_error_t esp_loader_flash_verify(void)
+{
+    uint8_t raw_md5[16];
+    uint8_t hex_md5[MD5_SIZE + 1];
+    uint8_t received_md5[MD5_SIZE + 1];
+
+    md5_final(raw_md5);
+    hexify(raw_md5, hex_md5);
+
+    loader_port_start_timer(timeout_per_mb(s_image_size, MD5_TIMEOUT_PER_MB));
+
+    RETURN_ON_ERROR( loader_md5_cmd(s_start_address, s_image_size, received_md5) );
+
+    bool md5_match = memcmp(hex_md5, received_md5, MD5_SIZE) == 0;
+    
+    if(!md5_match) {
+        hex_md5[MD5_SIZE] = '\n';
+        received_md5[MD5_SIZE] = '\n';
+
+        loader_port_debug_print("Error: MD5 checksum does not match:\n");
+        loader_port_debug_print("Expected:\n");
+        loader_port_debug_print((char*)received_md5);
+        loader_port_debug_print("Actual:\n");
+        loader_port_debug_print((char*)hex_md5);
+
+        return ESP_LOADER_ERROR_INVALID_MD5;
+    }
+
+    return ESP_LOADER_SUCCESS;
 }
 
+#endif
+
+void esp_loader_reset_target(void)
+{
+    loader_port_reset_target();
+}

+ 262 - 0
src/md5_hash.c

@@ -0,0 +1,262 @@
+/*
+ * MD5 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+
+#include "md5_hash.h"
+#include <stdlib.h>
+#include <string.h>
+
+
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16]);
+
+
+/* ===== start - public domain MD5 implementation ===== */
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest.  This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+
+#ifndef WORDS_BIGENDIAN
+#define byteReverse(buf, len)   /* Nothing */
+#else
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+static void byteReverse(unsigned char *buf, unsigned longs)
+{
+    uint32_t t;
+    do {
+        t = (uint32_t) ((unsigned) buf[3] << 8 | buf[2]) << 16 |
+            ((unsigned) buf[1] << 8 | buf[0]);
+        *(uint32_t *) buf = t;
+        buf += 4;
+    } while (--longs);
+}
+#endif
+
+/*
+ * Start MD5 accumulation.  Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void MD5Init(struct MD5Context *ctx)
+{
+    ctx->buf[0] = 0x67452301;
+    ctx->buf[1] = 0xefcdab89;
+    ctx->buf[2] = 0x98badcfe;
+    ctx->buf[3] = 0x10325476;
+
+    ctx->bits[0] = 0;
+    ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+    uint32_t t;
+
+    /* Update bitcount */
+
+    t = ctx->bits[0];
+    if ((ctx->bits[0] = t + ((uint32_t) len << 3)) < t) {
+        ctx->bits[1]++;    /* Carry from low to high */
+    }
+    ctx->bits[1] += len >> 29;
+
+    t = (t >> 3) & 0x3f;    /* Bytes already in shsInfo->data */
+
+    /* Handle any leading odd-sized chunks */
+
+    if (t) {
+        unsigned char *p = (unsigned char *) ctx->in + t;
+
+        t = 64 - t;
+        if (len < t) {
+            memcpy(p, buf, len);
+            return;
+        }
+        memcpy(p, buf, t);
+        byteReverse(ctx->in, 16);
+        MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in);
+        buf += t;
+        len -= t;
+    }
+    /* Process data in 64-byte chunks */
+
+    while (len >= 64) {
+        memcpy(ctx->in, buf, 64);
+        byteReverse(ctx->in, 16);
+        MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in);
+        buf += 64;
+        len -= 64;
+    }
+
+    /* Handle any remaining bytes of data. */
+
+    memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void MD5Final(unsigned char digest[16], struct MD5Context *ctx)
+{
+    unsigned count;
+    unsigned char *p;
+
+    /* Compute number of bytes mod 64 */
+    count = (ctx->bits[0] >> 3) & 0x3F;
+
+    /* Set the first char of padding to 0x80.  This is safe since there is
+       always at least one byte free */
+    p = ctx->in + count;
+    *p++ = 0x80;
+
+    /* Bytes of padding needed to make 64 bytes */
+    count = 64 - 1 - count;
+
+    /* Pad out to 56 mod 64 */
+    if (count < 8) {
+        /* Two lots of padding:  Pad the first block to 64 bytes */
+        memset(p, 0, count);
+        byteReverse(ctx->in, 16);
+        MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in);
+
+        /* Now fill the next block with 56 bytes */
+        memset(ctx->in, 0, 56);
+    } else {
+        /* Pad block to 56 bytes */
+        memset(p, 0, count - 8);
+    }
+    byteReverse(ctx->in, 14);
+
+    /* Append length in bits and transform */
+    ((uint32_t *) ctx->in)[14] = ctx->bits[0];
+    ((uint32_t *) ctx->in)[15] = ctx->bits[1];
+
+    MD5Transform((uint32_t *)ctx->buf, (uint32_t *) ctx->in);
+    byteReverse((unsigned char *) ctx->buf, 4);
+    memcpy(digest, ctx->buf, 16);
+    memset(ctx, 0, sizeof(struct MD5Context));  /* In case it's sensitive */
+}
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+    ( w += f(x, y, z) + data,  w = w<<s | w>>(32-s),  w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data.  MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+static void MD5Transform(uint32_t buf[4], uint32_t const in[16])
+{
+    register uint32_t a, b, c, d;
+
+    a = buf[0];
+    b = buf[1];
+    c = buf[2];
+    d = buf[3];
+
+    MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
+    MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
+    MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
+    MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
+    MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
+    MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
+    MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
+    MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
+    MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
+    MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
+    MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
+    MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
+    MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
+    MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
+    MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
+    MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
+
+    MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
+    MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
+    MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
+    MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
+    MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
+    MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
+    MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
+    MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
+    MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
+    MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
+    MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
+    MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
+    MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
+    MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
+    MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
+    MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
+
+    MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
+    MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
+    MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
+    MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
+    MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
+    MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
+    MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
+    MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
+    MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
+    MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
+    MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
+    MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
+    MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
+    MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
+    MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
+    MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
+
+    MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
+    MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
+    MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
+    MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
+    MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
+    MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
+    MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
+    MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
+    MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
+    MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
+    MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
+    MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
+    MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
+    MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
+    MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
+    MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
+
+    buf[0] += a;
+    buf[1] += b;
+    buf[2] += c;
+    buf[3] += d;
+}
+/* ===== end - public domain MD5 implementation ===== */

+ 101 - 92
src/serial_comm.c

@@ -17,6 +17,8 @@
 #include "serial_comm.h"
 #include "serial_io.h"
 #include <stddef.h>
+#include <string.h>
+// #include <stdio.h>
 
 
 static uint32_t s_sequence_number = 0;
@@ -25,7 +27,7 @@ static const uint8_t DELIMITER = 0xc0;
 static const uint8_t C0_REPLACEMENT[2] = {0xdb, 0xdc};
 static const uint8_t BD_REPLACEMENT[2] = {0xdb, 0xdd};
 
-static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value);
+static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size);
 
 
 static inline esp_loader_error_t serial_read(uint8_t *buff, size_t size)
@@ -49,20 +51,11 @@ static uint8_t compute_checksum(const uint8_t *data, uint32_t size)
     return checksum;
 }
 
-
-static esp_loader_error_t SLIP_receive_packet(uint8_t *buff, uint32_t buff_size)
+static esp_loader_error_t SLIP_receive_data(uint8_t *buff, uint32_t size)
 {
     uint8_t ch;
 
-    // Wait for delimiter
-    do {
-        esp_loader_error_t err = serial_read(&ch, 1);
-        if (err != ESP_LOADER_SUCCESS) {
-            return err;
-        }
-    } while (ch != DELIMITER);
-
-    for (int i = 0; i < buff_size; i++) {
+    for (int i = 0; i < size; i++) {
         RETURN_ON_ERROR( serial_read(&ch, 1)) ;
 
         if (ch == 0xdb) {
@@ -79,6 +72,24 @@ static esp_loader_error_t SLIP_receive_packet(uint8_t *buff, uint32_t buff_size)
         }
     }
 
+    return ESP_LOADER_SUCCESS;
+}
+
+
+static esp_loader_error_t SLIP_receive_packet(uint8_t *buff, uint32_t size)
+{
+    uint8_t ch;
+
+    // Wait for delimiter
+    do {
+        esp_loader_error_t err = serial_read(&ch, 1);
+        if (err != ESP_LOADER_SUCCESS) {
+            return err;
+        }
+    } while (ch != DELIMITER);
+
+    RETURN_ON_ERROR( SLIP_receive_data(buff, size) );
+
     // Delimiter
     RETURN_ON_ERROR( serial_read(&ch, 1) );
     if (ch != DELIMITER) {
@@ -130,19 +141,21 @@ static esp_loader_error_t SLIP_send_delimiter(void)
 
 static esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value)
 {
+    response_t response;
     command_t command = ((command_common_t *)cmd_data)->command;
 
     RETURN_ON_ERROR( SLIP_send_delimiter() );
     RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, size) );
     RETURN_ON_ERROR( SLIP_send_delimiter() );
 
-    return check_response(command, reg_value);
+    return check_response(command, reg_value, &response, sizeof(response));
 }
 
 
 static esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size,
-        const void *data, size_t data_size)
+                                             const void *data, size_t data_size)
 {
+    response_t response;
     command_t command = ((command_common_t *)cmd_data)->command;
 
     RETURN_ON_ERROR( SLIP_send_delimiter() );
@@ -150,12 +163,31 @@ static esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_si
     RETURN_ON_ERROR( SLIP_send(data, data_size) );
     RETURN_ON_ERROR( SLIP_send_delimiter() );
 
-    return check_response(command, NULL);
+    return check_response(command, NULL, &response, sizeof(response));
+}
+
+
+static esp_loader_error_t send_cmd_md5(const void *cmd_data, size_t cmd_size, uint8_t md5_out[MD5_SIZE])
+{
+    rom_md5_response_t response;
+    command_t command = ((command_common_t *)cmd_data)->command;
+
+    RETURN_ON_ERROR( SLIP_send_delimiter() );
+    RETURN_ON_ERROR( SLIP_send((const uint8_t *)cmd_data, cmd_size) );
+    RETURN_ON_ERROR( SLIP_send_delimiter() );
+
+    RETURN_ON_ERROR( check_response(command, NULL, &response, sizeof(response)) );
+
+    memcpy(md5_out, response.md5, MD5_SIZE);
+
+    return ESP_LOADER_SUCCESS;
 }
 
 
 static void log_loader_internal_error(error_code_t error)
 {
+    loader_port_debug_print("Error: ");
+
     switch (error) {
         case INVALID_CRC:     loader_port_debug_print("INVALID_CRC"); break;
         case INVALID_COMMAND: loader_port_debug_print("INVALID_COMMAND"); break;
@@ -166,28 +198,32 @@ static void log_loader_internal_error(error_code_t error)
         case DEFLATE_ERROR:   loader_port_debug_print("DEFLATE_ERROR"); break;
         default:              loader_port_debug_print("UNKNOWN ERROR"); break;
     }
+    
+    loader_port_debug_print("\n");
 }
 
 
-static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value)
+static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value, void* resp, uint32_t resp_size)
 {
-    response_t response;
     esp_loader_error_t err;
+    common_response_t *response = (common_response_t *)resp;
 
     do {
-        err = SLIP_receive_packet((uint8_t *)&response, sizeof(response_t));
+        err = SLIP_receive_packet(resp, resp_size);
         if (err != ESP_LOADER_SUCCESS) {
             return err;
         }
-    } while ((response.direction != READ_DIRECTION) || (response.command != cmd));
+    } while ((response->direction != READ_DIRECTION) || (response->command != cmd));
 
-    if (response.status == STATUS_FAILURE) {
-        log_loader_internal_error(response.error);
+    response_status_t *status = (response_status_t *)(resp + resp_size - sizeof(response_status_t));
+
+    if (status->failed) {
+        log_loader_internal_error(status->error);
         return ESP_LOADER_ERROR_INVALID_RESPONSE;
     }
 
     if (reg_value != NULL) {
-        *reg_value = response.value;
+        *reg_value = response->value;
     }
 
     return ESP_LOADER_SUCCESS;
@@ -195,13 +231,13 @@ static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value)
 
 
 esp_loader_error_t loader_flash_begin_cmd(uint32_t offset,
-        uint32_t erase_size,
-        uint32_t block_size,
-        uint32_t blocks_to_write)
+                                          uint32_t erase_size,
+                                          uint32_t block_size,
+                                          uint32_t blocks_to_write)
 {
     begin_command_t begin_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = FLASH_BEGIN,
             .size = 16,
             .checksum = 0
@@ -222,7 +258,7 @@ esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size)
 {
     data_command_t data_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = FLASH_DATA,
             .size = 16,
             .checksum = compute_checksum(data, size)
@@ -241,7 +277,7 @@ esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader)
 {
     flash_end_command_t end_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = FLASH_END,
             .size = 4,
             .checksum = 0
@@ -257,7 +293,7 @@ esp_loader_error_t loader_sync_cmd(void)
 {
     sync_command_t sync_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = SYNC,
             .size = 36,
             .checksum = 0
@@ -275,72 +311,12 @@ esp_loader_error_t loader_sync_cmd(void)
 }
 
 
-esp_loader_error_t loader_mem_begin_cmd(uint32_t offset,
-                                        uint32_t total_size,
-                                        uint32_t packet_size,
-                                        uint32_t packets_to_write)
-{
-    begin_command_t begin_cmd = {
-        .common = {
-            .direction = 0,
-            .command = MEM_BEGIN,
-            .size = 16,
-            .checksum = 0
-        },
-        .erase_size = total_size,
-        .packet_count = packets_to_write,
-        .packet_size = packet_size,
-        .offset = offset
-    };
-
-    s_sequence_number = 0;
-
-    return send_cmd(&begin_cmd, sizeof(begin_cmd), NULL);
-}
-
-
-esp_loader_error_t loader_mem_data_cmd(uint8_t *data, uint32_t size)
-{
-    data_command_t data_cmd = {
-        .common = {
-            .direction = 0,
-            .command = MEM_DATA,
-            .size = 16,
-            .checksum = compute_checksum(data, size)
-        },
-        .data_size = size,
-        .sequence_number = s_sequence_number++,
-        .zero_0 = 0,
-        .zero_1 = 0
-    };
-
-    return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size);
-}
-
-
-esp_loader_error_t loader_mem_end_cmd(bool stay_in_loader, uint32_t address)
-{
-    mem_end_command_t end_cmd = {
-        .common = {
-            .direction = 0,
-            .command = MEM_END,
-            .size = 8,
-            .checksum = 0
-        },
-        .stay_in_loader = stay_in_loader,
-        .entry_point_address = address
-    };
-
-    return send_cmd(&end_cmd, sizeof(end_cmd), NULL);
-}
-
-
 esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value,
                                         uint32_t mask, uint32_t delay_us)
 {
     write_reg_command_t write_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = WRITE_REG,
             .size = 16,
             .checksum = 0
@@ -359,7 +335,7 @@ esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg)
 {
     read_reg_command_t read_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = READ_REG,
             .size = 16,
             .checksum = 0
@@ -375,7 +351,7 @@ esp_loader_error_t loader_spi_attach_cmd(uint32_t config)
 {
     spi_attach_command_t attach_cmd = {
         .common = {
-            .direction = 0,
+            .direction = WRITE_DIRECTION,
             .command = SPI_ATTACH,
             .size = 8,
             .checksum = 0
@@ -387,6 +363,39 @@ esp_loader_error_t loader_spi_attach_cmd(uint32_t config)
     return send_cmd(&attach_cmd, sizeof(attach_cmd), NULL);
 }
 
+esp_loader_error_t loader_change_baudrate_cmd(uint32_t baudrate)
+{
+    change_baudrate_command_t baudrate_cmd = {
+        .common = {
+            .direction = WRITE_DIRECTION,
+            .command = CHANGE_BAUDRATE,
+            .size = 8,
+            .checksum = 0
+        },
+        .new_baudrate = baudrate,
+        .old_baudrate = 0 // ESP32 ROM only
+    };
+
+    return send_cmd(&baudrate_cmd, sizeof(baudrate_cmd), NULL);
+}
+
+esp_loader_error_t loader_md5_cmd(uint32_t address, uint32_t size, uint8_t *md5_out)
+{
+    spi_flash_md5_command_t md5_cmd = {
+        .common = {
+            .direction = WRITE_DIRECTION,
+            .command = SPI_FLASH_MD5,
+            .size = 16,
+            .checksum = 0
+        },
+        .address = address,
+        .size = size,
+        .reserved_0 = 0,
+        .reserved_1 = 0
+    };
+
+    return send_cmd_md5(&md5_cmd, sizeof(md5_cmd), md5_out);
+}
 
 __attribute__ ((weak)) void loader_port_debug_print(const char *str)
 {

+ 1 - 0
test/CMakeLists.txt

@@ -9,6 +9,7 @@ set( TEST_SOURCES
   test_main.cpp 
   ../src/serial_comm.c
   ../src/esp_loader.c
+  ../src/md5_hash.c
 )
 
 # Set -DQEMU_TEST=True to run qemu tests. Host tests are run otherwise.

BIN
test/hello-world.bin


+ 2 - 0
test/qemu_test.cpp

@@ -103,6 +103,8 @@ TEST_CASE( "Can write application to flash" )
     
     REQUIRE ( file_compare(new_image, qemu_image, new_image_size) );
 
+    ESP_ERR_CHECK ( esp_loader_flash_verify() );
+
     // NOTE: loader_flash_finish() is not called to prevent reset of target 
 }
 

+ 22 - 24
test/run_qemu.sh

@@ -53,14 +53,14 @@ static void arrays_match(int8_t *array_1, int8_t *array_2, size_t size)
 struct __attribute__((packed)) expected_response {
     expected_response(command_t cmd)
     {
-        data.direction = READ_DIRECTION;
-        data.command = cmd;
-        data.size = 16;
-        data.value = 0;
-        data.status = STATUS_SUCCESS;
-        data.error = 0;
-        data.reserved_0 = 0;
-        data.reserved_1 = 0;
+        data.common.direction = READ_DIRECTION;
+        data.common.command = cmd;
+        data.common.size = 16;
+        data.common.value = 0;
+        data.status.failed = STATUS_SUCCESS;
+        data.status.error = 0;
+        data.status.reserved_0 = 0;
+        data.status.reserved_1 = 0;
     }
 
     response_t data;
@@ -83,18 +83,6 @@ expected_response sync_response(SYNC);
 
 
 struct __attribute__((packed)) flash_start_frame {
-    uint8_t delimiter_1 = 0xc0;
-    spi_attach_command_t attach_cmd = {
-        .common = {
-            .direction = WRITE_DIRECTION,
-            .command = SPI_ATTACH,
-            .size = 8,
-            .checksum = 0,
-        },
-        .configuration = 0,
-        .zero = 0
-    };
-    uint8_t delimiter_2 = 0xc0;
     uint8_t delimiter_3 = 0xc0;
     begin_command_t begin_cmd  = {
         .common = {
@@ -218,7 +206,6 @@ TEST_CASE( "Large payload that does not fit BLOCK_SIZE is split into \
 
     // Check flash start operation 
     clear_buffers();
-    queue_response(attach_response);
     queue_response(flash_begin_response);
 
     REQUIRE ( esp_loader_flash_start(0, sizeof(data) * 3, BLOCK_SIZE) == ESP_LOADER_SUCCESS );
@@ -246,6 +233,7 @@ TEST_CASE( "Can connect within specified time " )
 {
     clear_buffers();
     queue_response(sync_response);
+    queue_response(attach_response);
 
     esp_loader_connect_args_t connect_config = {
         .sync_timeout = 10,
@@ -278,7 +266,7 @@ TEST_CASE( "Register can be read correctly" )
 {
     clear_buffers();
     uint32_t reg_value = 0;
-    read_reg_response.data.value = 55;
+    read_reg_response.data.common.value = 55;
 
     queue_response(read_reg_response);
 
@@ -291,7 +279,7 @@ TEST_CASE( "Register can be read correctly" )
 TEST_CASE( "Register can be written correctly" )
 {
     write_reg_cmd_response expected;
-    write_reg_response.data.value = 55;
+    write_reg_response.data.common.value = 55;
 
     clear_buffers();
     queue_response(write_reg_response);
@@ -362,7 +350,7 @@ TEST_CASE( "Sync command is constructed correctly" )
 TEST_CASE( "Register can be read and decoded correctly" )
 {
     clear_buffers();
-    read_reg_response.data.value = 55;
+    read_reg_response.data.common.value = 55;
     queue_response(read_reg_response);
 
     uint32_t reg_value = 0;
@@ -371,7 +359,17 @@ TEST_CASE( "Register can be read and decoded correctly" )
     REQUIRE( reg_value == 55 );
 }
 
+TEST_CASE( "Received response (in SLIP format) is decoded correctly" )
+{
+    clear_buffers();
+    read_reg_response.data.common.value = 0xC0BD; // C0, BD has to be replaced 
+    queue_response(read_reg_response);
+
+    uint32_t reg_value = 0;
+    esp_loader_read_register(0, &reg_value);
 
+    REQUIRE( reg_value == 0xC0BD );
+}
 
 
 // --------------------  Serial mock test  -----------------------