Sfoglia il codice sorgente

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 anni fa
parent
commit
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  -----------------------