Просмотр исходного кода

Merge branch 'serial_flasher' into 'master'

Serial flasher core

See merge request espressif/esp-serial-flasher!1
Ivan Grokhotkov 6 лет назад
Родитель
Сommit
0b89ea98a0
10 измененных файлов с 1170 добавлено и 9 удалено
  1. 2 0
      .gitignore
  2. 18 0
      CMakeLists.txt
  3. 11 9
      README.md
  4. 145 0
      include/esp_loader.h
  5. 117 0
      include/serial_io.h
  6. 156 0
      port/esp32_uart.c
  7. 61 0
      private_include/serial_comm.h
  8. 146 0
      private_include/serial_comm_prv.h
  9. 120 0
      src/esp_loader.c
  10. 394 0
      src/serial_comm.c

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+build
+sdkconfig

+ 18 - 0
CMakeLists.txt

@@ -0,0 +1,18 @@
+set(COMPONENT_SRCS 
+    "src/esp_loader.c"
+    "src/serial_comm.c"
+    "port/esp32_uart.c")
+
+set(COMPONENT_ADD_INCLUDEDIRS "include")
+
+set(COMPONENT_PRIV_INCLUDEDIRS "private_include" )
+
+if (NOT ESP_PLATFORM)
+    message(FATAL_ERROR  "This CMakeLists.txt file is only for use inside ESP-IDF. 
+                          It will need modifying to work standalone or in another project.")
+endif()
+
+
+register_component()
+
+component_compile_options(-Wstrict-prototypes)

+ 11 - 9
README.md

@@ -2,24 +2,26 @@
 
 ## Overview
 
-Serial flasher component provides portable library for flashing ESP32 from other host microcontroller. ESP32 is normally programmed via serial interface (UART) and port layer for given host microcontroller has to be implemented, if not available. Details can be found in section bolew.
+Serial flasher component provides portable library for flashing ESP32 from other host microcontroller. ESP32 is normally programmed via serial interface (UART) and port layer for given host microcontroller has to be implemented, if not available. Details can be found in section below.
 
 
 ## Supporting new target
 
 In order to support new target, following function has to be implemented by user:
 
-* serial_read()
-* serial_write()
-* enter_bootloader()
-* delay_miliseconds()
+* loader_port_serial_read()
+* loader_port_serial_write()
+* loader_port_enter_bootloader()
+* loader_port_delay_ms()
+* loader_port_start_timer()
+* loader_port_remaining_time()
 
 Following functions are part of serial_io.h header for convenience, however, user does not have to strictly follow function signatures, as there are not called directly from library. 
 
-* serial_init()
-* serial_deinit()
-* reset_target()
-* debug_print()
+* loader_port_serial_init()
+* loader_port_serial_deinit()
+* loader_port_reset_target()
+* loader_port_debug_print()
 
 Prototypes of all function mentioned above can be found in [serial_io.h](include/serial_io.h).
 Please refer to ports in `port` directory. Currently, only [ESP32 port](port/esp32_uart.c) is available.

+ 145 - 0
include/esp_loader.h

@@ -0,0 +1,145 @@
+/* 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
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Error codes
+ */
+typedef enum
+{
+    ESP_LOADER_SUCCESS,                /*!< Success */
+    ESP_LOADER_ERROR_FAIL,             /*!< Unspecified error */
+    ESP_LOADER_ERROR_TIMEOUT,          /*!< Timeout elapsed */
+    ESP_LOADER_ERROR_INVALID_RESPONSE  /*!< Internal error */
+} esp_loader_error_t;
+
+/**
+ * @brief Connection arguments
+ */
+typedef struct 
+{
+  uint32_t sync_timeout;  /*!< Maximum time to wait for response from serial interface. */
+  int32_t trials;         /*!< Number of trials to connect to target. If greater than 1,
+                               100 millisecond delay is inserted after each try. */
+} esp_loader_connect_args_t;
+
+#define ESP_LOADER_CONNECT_DEFAULT() { \
+  .sync_timeout = 100,  \
+  .trials = 10  \
+}
+
+/**
+  * @brief Connects to the target
+  *
+  * @param connect_args[in] Timing parameters to be used for connecting to target.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args);
+
+/**
+  * @brief Initiates flash operation
+  *
+  * @param offset[in]       Address from which flash operation will be performed.
+  * @param image_size[in]   Size of the whole binary to be loaded into flash.
+  * @param block_size[in]   Size of buffer used in subsequent calls to esp_loader_flash_write.
+  *
+  * @note  image_size is size of the whole image, whereas, block_size is chunk of data sent 
+  *        to the target, each time esp_loader_flash_write function is called.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size);
+
+/**
+  * @brief Writes supplied data to target's flash memory.
+  *
+  * @param payload[in]      Data to be flashed into target's memory.
+  * @param size[in]         Size of payload in bytes.
+  *
+  * @note  size must not be greater that block_size supplied to previously called
+  *        esp_loader_flash_start function. If size is less than block_size, 
+  *        remaining bytes of payload buffer will be padded with 0xff.
+  *        Therefore, size of payload buffer has to be equal or greater than block_size.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size);
+
+/**
+  * @brief Ends flash operation.
+  *
+  * @param reboot[in]       reboot the target if true.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_flash_finish(bool reboot);
+
+/**
+  * @brief Writes register.
+  *
+  * @param address[in]      Address of register.
+  * @param reg_value[in]    New register value.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value);
+
+/**
+  * @brief Reads register.
+  *
+  * @param address[in]      Address of register.
+  * @param reg_value[out]   Register value.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value);
+
+/**
+  * @brief Toggles reset pin.
+  */
+void esp_loader_reset_target(void);
+
+
+
+#ifdef __cplusplus
+}
+#endif

+ 117 - 0
include/serial_io.h

@@ -0,0 +1,117 @@
+/* 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
+
+#include <stdint.h>
+#include "esp_loader.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+  * @brief Initializes serial interface.
+  *
+  * @param baud_rate[in]       Communication speed.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_FAIL Initialization failure
+  */
+esp_loader_error_t loader_port_serial_init(uint32_t baud_rate);
+
+
+/**
+  * @brief Deinitialize serial interface.
+  */
+void loader_port_serial_deinit(void);
+
+/**
+  * @brief Writes data to serial interface.
+  *
+  * @param data[in]     Buffer with data to be written.
+  * @param size[in]     Size of data in bytes.
+  * @param timeout[in]  Timeout in milliseconds.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed
+  */
+esp_loader_error_t loader_port_serial_write(const uint8_t *data, uint16_t size, uint32_t timeout);
+
+/**
+  * @brief Reads data from serial interface.
+  *
+  * @param data[out]    Buffer into which received data will be written.
+  * @param size[in]     Number of bytes to read.
+  * @param timeout[in]  Timeout in milliseconds.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout elapsed
+  */
+esp_loader_error_t loader_port_serial_read(uint8_t *data, uint16_t size, uint32_t timeout);
+
+/**
+  * @brief Delay in milliseconds.
+  *
+  * @param ms[in]   Number of milliseconds.
+  *
+  */
+void loader_port_delay_ms(uint32_t ms);
+
+/**
+  * @brief Starts timeout timer.
+  *
+  * @param ms[in]   Number of milliseconds.
+  *
+  */
+void loader_port_start_timer(uint32_t ms);
+
+/**
+  * @brief Returns remaining time since timer was started by calling esp_loader_start_timer.
+  *        0 if timer has elapsed.
+  *
+  * @return   Number of milliseconds.
+  *
+  */
+uint32_t loader_port_remaining_time(void);
+
+/**
+  * @brief Asserts bootstrap pins to enter boot mode and toggles reset pin.
+  *
+  * @note  Reset pin should stay asserted for at least 20 milliseconds.
+  */
+void loader_port_enter_bootloader(void);
+
+/**
+  * @brief Toggles reset pin.
+  *
+  * @note  Reset pin should stay asserted for at least 20 milliseconds.
+  */
+void loader_port_reset_target(void);
+
+/**
+  * @brief Function can be defined by user to print debug message.
+  *
+  * @note  Empty weak function is used, otherwise.
+  *
+  */
+void loader_port_debug_print(const char *str);
+
+#ifdef __cplusplus
+}
+#endif

+ 156 - 0
port/esp32_uart.c

@@ -0,0 +1,156 @@
+/* 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.
+ */
+
+#include "serial_io.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/uart.h"
+#include "driver/gpio.h"
+#include "esp_timer.h"
+#include "esp_log.h"
+#include <unistd.h>
+
+// #define SERIAL_DEBUG_ENABLE
+
+#define UART_TXD_PIN        GPIO_NUM_4
+#define UART_RXD_PIN        GPIO_NUM_5
+#define UART_PORT           UART_NUM_1
+
+#define RESET_TRIGER_PIN    GPIO_NUM_25
+#define GPIO0_TRIGER_PIN    GPIO_NUM_26
+
+#ifdef SERIAL_DEBUG_ENABLE
+void serial_debug_print(const uint8_t *data, uint16_t size)
+{
+    ESP_LOG_BUFFER_HEX("LOADER ESP32 PORT", data, size);
+}
+#else
+void serial_debug_print(const uint8_t *data, uint16_t size) { }
+#endif
+
+static int64_t s_time_end;
+
+
+esp_loader_error_t loader_port_serial_init(uint32_t baud_rate)
+{
+    // Initialize UART
+    uart_config_t uart_config = {
+        .baud_rate = baud_rate,
+        .data_bits = UART_DATA_8_BITS,
+        .parity    = UART_PARITY_DISABLE,
+        .stop_bits = UART_STOP_BITS_1,
+        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE
+    };
+
+    const int rx_buffer_size = 2 * 200;
+    const int tx_buffer_size = 2 * 200;
+
+    if ( uart_param_config(UART_PORT, &uart_config) != ESP_OK ) {
+        return ESP_LOADER_ERROR_FAIL;
+    }
+    if ( uart_set_pin(UART_PORT, UART_TXD_PIN, UART_RXD_PIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE) != ESP_OK ) {
+        return ESP_LOADER_ERROR_FAIL;
+    }
+    if ( uart_driver_install(UART_PORT, rx_buffer_size, tx_buffer_size, 0, NULL, 0) != ESP_OK ) {
+        return ESP_LOADER_ERROR_FAIL;
+    }
+
+    // Initialize boot pin selection pins
+    gpio_pad_select_gpio(RESET_TRIGER_PIN);
+    gpio_set_pull_mode(RESET_TRIGER_PIN, GPIO_PULLUP_ONLY);
+    gpio_set_direction(RESET_TRIGER_PIN, GPIO_MODE_OUTPUT);
+
+    gpio_pad_select_gpio(GPIO0_TRIGER_PIN);
+    gpio_set_pull_mode(GPIO0_TRIGER_PIN, GPIO_PULLUP_ONLY);
+    gpio_set_direction(GPIO0_TRIGER_PIN, GPIO_MODE_OUTPUT);
+
+    return ESP_LOADER_SUCCESS;
+}
+
+
+esp_loader_error_t loader_port_serial_write(const uint8_t *data, uint16_t size, uint32_t timeout)
+{
+    serial_debug_print(data, size);
+
+    uart_write_bytes(UART_PORT, (const char *)data, size);
+    esp_err_t err = uart_wait_tx_done(UART_PORT, pdMS_TO_TICKS(timeout));
+    
+    if (err == ESP_OK) {
+        return ESP_LOADER_SUCCESS;
+    } else if (err == ESP_ERR_TIMEOUT) {
+        return ESP_LOADER_ERROR_TIMEOUT;
+    } else {
+        return ESP_LOADER_ERROR_FAIL;
+    }
+}
+
+
+esp_loader_error_t loader_port_serial_read(uint8_t *data, uint16_t size, uint32_t timeout)
+{
+    int read = uart_read_bytes(UART_PORT, data, size, pdMS_TO_TICKS(timeout));
+    
+    if (read < 0) {
+        return ESP_LOADER_ERROR_FAIL;
+    } else if (read < size) {
+        return ESP_LOADER_ERROR_TIMEOUT;
+    } else {
+        return ESP_LOADER_SUCCESS;
+    }
+}
+
+
+// Set GPIO0 LOW, then
+// assert reset pin for 50 milliseconds.
+void loader_port_enter_bootloader(void)
+{
+    gpio_set_level(GPIO0_TRIGER_PIN, 0);
+    gpio_set_level(RESET_TRIGER_PIN, 0);
+    gpio_set_level(RESET_TRIGER_PIN, 1);
+    loader_port_delay_ms(50);
+    gpio_set_level(GPIO0_TRIGER_PIN, 1);
+}
+
+
+void loader_port_reset_target(void)
+{
+    gpio_set_level(RESET_TRIGER_PIN, 0);
+    loader_port_delay_ms(50);
+    gpio_set_level(RESET_TRIGER_PIN, 1);
+}
+
+
+void loader_port_delay_ms(uint32_t ms)
+{
+    usleep(ms * 1000);
+}
+
+
+void loader_port_start_timer(uint32_t ms)
+{
+    s_time_end = esp_timer_get_time() + ms * 1000;
+}
+
+
+uint32_t loader_port_remaining_time(void)
+{
+    int64_t remaining = (s_time_end - esp_timer_get_time()) / 1000;
+    return (remaining > 0) ? (uint32_t)remaining : 0;
+}
+
+
+void loader_port_debug_print(const char *str)
+{
+    printf("DEBUG: %s\n", str);
+}

+ 61 - 0
private_include/serial_comm.h

@@ -0,0 +1,61 @@
+/* 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
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "esp_loader.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Macro which can be used to check the error code,
+ * and return in case the code is not ESP_LOADER_SUCCESS.
+ */
+#define RETURN_ON_ERROR(x) do {         \
+    esp_loader_error_t _err_ = (x);     \
+    if (_err_ != ESP_LOADER_SUCCESS) {  \
+        return _err_;                   \
+    }                                   \
+} while(0)
+
+esp_loader_error_t loader_flash_begin_cmd(uint32_t offset, uint32_t erase_size, uint32_t block_size, uint32_t blocks_to_write);
+
+esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size);
+
+esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader);
+
+esp_loader_error_t loader_write_reg_cmd(uint32_t address, uint32_t value, uint32_t mask, uint32_t delay_us);
+
+esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg);
+
+esp_loader_error_t loader_sync_cmd(void);
+
+esp_loader_error_t loader_spi_attach_cmd(uint32_t config);
+
+
+// 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]);
+
+
+#ifdef __cplusplus
+}
+#endif

+ 146 - 0
private_include/serial_comm_prv.h

@@ -0,0 +1,146 @@
+/* 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
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define STATUS_FAILURE  1
+#define STATUS_SUCCESS  0
+
+#define READ_DIRECTION  1
+#define WRITE_DIRECTION 0
+
+typedef enum __attribute__((packed))
+{
+    FLASH_BEGIN = 0x02,
+    FLASH_DATA  = 0x03,
+    FLASH_END   = 0x04,
+    MEM_BEGIN   = 0x05,
+    MEM_END     = 0x06,
+    MEM_DATA    = 0x07,
+    SYNC        = 0x08,
+    WRITE_REG   = 0x09,
+    READ_REG    = 0x0a,
+
+    SPI_SET_PARAMS   = 0x0b,
+    SPI_ATTACH       = 0x0d,
+    CHANGE_BAUDRATE  = 0x0f,
+    FLASH_DEFL_BEGIN = 0x10,
+    FLASH_DEFL_DATA  = 0x11,
+    FLASH_DEFL_END   = 0x12,
+    SPI_FLASH_MD5    = 0x13,
+} command_t;
+
+typedef enum __attribute__((packed))
+{
+    RESPONSE_OK     = 0x00,
+    INVALID_COMMAND = 0x05, // parameters or length field is invalid
+    COMMAND_FAILED  = 0x06, // Failed to act on received message
+    INVALID_CRC     = 0x07, // Invalid CRC in message
+    FLASH_WRITE_ERR = 0x08, // After writing a block of data to flash, the ROM loader reads the value back and the 8-bit CRC is compared to the data read from flash. If they don't match, this error is returned.
+    FLASH_READ_ERR  = 0x09, // SPI read failed
+    READ_LENGTH_ERR = 0x0a, // SPI read request length is too long
+    DEFLATE_ERROR   = 0x0b, // ESP32 compressed uploads only
+} error_code_t;
+
+typedef struct __attribute__((packed))
+{
+    uint8_t direction;
+    uint8_t command;    // One of command_t
+    uint16_t size;
+    uint32_t checksum;
+} command_common_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t erase_size;
+    uint32_t packet_count;
+    uint32_t packet_size;
+    uint32_t offset;
+} begin_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t data_size;
+    uint32_t sequence_number;
+    uint32_t zero_0;
+    uint32_t zero_1;
+} data_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t stay_in_loader;
+} flash_end_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t stay_in_loader;
+    uint32_t entry_point_address;
+} mem_end_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint8_t sync_sequence[36];
+} sync_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t address;
+    uint32_t value;
+    uint32_t mask;
+    uint32_t delay_us;
+} write_reg_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t address;
+} read_reg_command_t;
+
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t configuration;
+    uint32_t zero; // ESP32 ROM only
+} spi_attach_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;
+    uint8_t error;
+    uint8_t reserved_0; // ESP32 ROM only
+    uint8_t reserved_1; // ESP32 ROM only
+} response_t;
+
+
+#ifdef __cplusplus
+}
+#endif

+ 120 - 0
src/esp_loader.c

@@ -0,0 +1,120 @@
+/* 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.
+ */
+
+#include "serial_comm.h"
+#include "serial_io.h"
+#include "esp_loader.h"
+
+#ifndef MAX
+#define MAX(a, b) ((a) > (b)) ? (a) : (b)
+#endif
+
+static const uint32_t DEFAULT_TIMEOUT = 500;
+static const uint32_t SPI_PIN_CONFIG_DEFAULT = 0;
+static const uint32_t DEFAULT_FLASH_TIMEOUT = 3000;       // timeout for most flash operations
+static const uint32_t ERASE_REGION_TIMEOUT_PER_MB = 3000; // timeout (per megabyte) for erasing a region
+static const uint8_t  PADDING_PATTERN = 0xFF;
+
+
+static uint32_t s_flash_write_size = 0;
+
+
+static uint32_t timeout_per_mb(uint32_t size_bytes)
+{
+    uint32_t timeout = ERASE_REGION_TIMEOUT_PER_MB * (size_bytes / 1e6);
+    return MAX(timeout, DEFAULT_FLASH_TIMEOUT);
+}
+
+
+esp_loader_error_t esp_loader_connect(esp_loader_connect_args_t *connect_args)
+{
+    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) {
+            loader_port_delay_ms(100);
+        }
+    } while (trials > 0);
+
+    return ESP_LOADER_ERROR_TIMEOUT;
+}
+
+
+esp_loader_error_t esp_loader_flash_start(uint32_t offset, uint32_t image_size, uint32_t block_size)
+{
+    uint32_t blocks_to_write = (image_size + block_size - 1) / block_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) );
+
+    loader_port_start_timer(timeout_per_mb(erase_size));
+
+    return loader_flash_begin_cmd(offset, erase_size, block_size, blocks_to_write);
+}
+
+
+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;
+
+    while (padding_bytes--) {
+        data[size++] = PADDING_PATTERN;
+    }
+
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+
+    return loader_flash_data_cmd(data, s_flash_write_size);
+}
+
+
+esp_loader_error_t esp_loader_flash_finish(bool reboot)
+{
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+
+    return loader_flash_end_cmd(!reboot);
+}
+
+
+esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value)
+{
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+
+    return loader_read_reg_cmd(address, reg_value);
+}
+
+
+esp_loader_error_t esp_loader_write_register(uint32_t address, uint32_t reg_value)
+{
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+
+    return loader_write_reg_cmd(address, reg_value, 0xFFFFFFFF, 0);
+}
+
+void esp_loader_reset_target(void)
+{
+    loader_port_reset_target();
+}
+

+ 394 - 0
src/serial_comm.c

@@ -0,0 +1,394 @@
+/* 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.
+ */
+
+#include "serial_comm_prv.h"
+#include "serial_comm.h"
+#include "serial_io.h"
+#include <stddef.h>
+
+
+static uint32_t s_sequence_number = 0;
+
+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 inline esp_loader_error_t serial_read(uint8_t *buff, size_t size)
+{
+    return loader_port_serial_read(buff, size, loader_port_remaining_time());
+}
+
+static inline esp_loader_error_t serial_write(const uint8_t *buff, size_t size)
+{
+    return loader_port_serial_write(buff, size, loader_port_remaining_time());
+}
+
+static uint8_t compute_checksum(const uint8_t *data, uint32_t size)
+{
+    uint8_t checksum = 0xEF;
+
+    while (size--) {
+        checksum ^= *data++;
+    }
+
+    return checksum;
+}
+
+
+static esp_loader_error_t SLIP_receive_packet(uint8_t *buff, uint32_t buff_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++) {
+        RETURN_ON_ERROR( serial_read(&ch, 1)) ;
+
+        if (ch == 0xdb) {
+            RETURN_ON_ERROR( serial_read(&ch, 1) );
+            if (ch == 0xdc) {
+                buff[i] = 0xc0;
+            } else if (ch == 0xdd) {
+                buff[i] = 0xbd;
+            } else {
+                return ESP_LOADER_ERROR_INVALID_RESPONSE;
+            }
+        } else {
+            buff[i] = ch;
+        }
+    }
+
+    // Delimiter
+    RETURN_ON_ERROR( serial_read(&ch, 1) );
+    if (ch != DELIMITER) {
+        return ESP_LOADER_ERROR_INVALID_RESPONSE;
+    }
+
+    return ESP_LOADER_SUCCESS;
+}
+
+
+static esp_loader_error_t SLIP_send(const uint8_t *data, uint32_t size)
+{
+    uint32_t to_write = 0;
+    uint32_t written = 0;
+
+    for (int i = 0; i < size; i++) {
+        if (data[i] != 0xc0 && data[i] != 0xdb) {
+            to_write++;
+            continue;
+        }
+
+        if (to_write > 0) {
+            RETURN_ON_ERROR( serial_write(&data[written], to_write) );
+        }
+
+        if (data[i] == 0xc0) {
+            RETURN_ON_ERROR( serial_write(C0_REPLACEMENT, 2) );
+        } else {
+            RETURN_ON_ERROR( serial_write(BD_REPLACEMENT, 2) );
+        }
+
+        written = i + 1;
+        to_write = 0;
+    }
+
+    if (to_write > 0) {
+        RETURN_ON_ERROR( serial_write(&data[written], to_write) );
+    }
+
+    return ESP_LOADER_SUCCESS;
+}
+
+
+static esp_loader_error_t SLIP_send_delimiter(void)
+{
+    return serial_write(&DELIMITER, 1);
+}
+
+
+static esp_loader_error_t send_cmd(const void *cmd_data, uint32_t size, uint32_t *reg_value)
+{
+    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);
+}
+
+
+static esp_loader_error_t send_cmd_with_data(const void *cmd_data, size_t cmd_size,
+        const void *data, size_t data_size)
+{
+    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(data, data_size) );
+    RETURN_ON_ERROR( SLIP_send_delimiter() );
+
+    return check_response(command, NULL);
+}
+
+
+static void log_loader_internal_error(error_code_t error)
+{
+    switch (error) {
+        case INVALID_CRC:     loader_port_debug_print("INVALID_CRC"); break;
+        case INVALID_COMMAND: loader_port_debug_print("INVALID_COMMAND"); break;
+        case COMMAND_FAILED:  loader_port_debug_print("COMMAND_FAILED"); break;
+        case FLASH_WRITE_ERR: loader_port_debug_print("FLASH_WRITE_ERR"); break;
+        case FLASH_READ_ERR:  loader_port_debug_print("FLASH_READ_ERR"); break;
+        case READ_LENGTH_ERR: loader_port_debug_print("READ_LENGTH_ERR"); break;
+        case DEFLATE_ERROR:   loader_port_debug_print("DEFLATE_ERROR"); break;
+        default:              loader_port_debug_print("UNKNOWN ERROR"); break;
+    }
+}
+
+
+static esp_loader_error_t check_response(command_t cmd, uint32_t *reg_value)
+{
+    response_t response;
+    esp_loader_error_t err;
+
+    do {
+        err = SLIP_receive_packet((uint8_t *)&response, sizeof(response_t));
+        if (err != ESP_LOADER_SUCCESS) {
+            return err;
+        }
+    } while ((response.direction != READ_DIRECTION) || (response.command != cmd));
+
+    if (response.status == STATUS_FAILURE) {
+        log_loader_internal_error(response.error);
+        return ESP_LOADER_ERROR_INVALID_RESPONSE;
+    }
+
+    if (reg_value != NULL) {
+        *reg_value = response.value;
+    }
+
+    return ESP_LOADER_SUCCESS;
+}
+
+
+esp_loader_error_t loader_flash_begin_cmd(uint32_t offset,
+        uint32_t erase_size,
+        uint32_t block_size,
+        uint32_t blocks_to_write)
+{
+    begin_command_t begin_cmd = {
+        .common = {
+            .direction = 0,
+            .command = FLASH_BEGIN,
+            .size = 16,
+            .checksum = 0
+        },
+        .erase_size = erase_size,
+        .packet_count = blocks_to_write,
+        .packet_size = block_size,
+        .offset = offset
+    };
+
+    s_sequence_number = 0;
+
+    return send_cmd(&begin_cmd, sizeof(begin_cmd), NULL);
+}
+
+
+esp_loader_error_t loader_flash_data_cmd(const uint8_t *data, uint32_t size)
+{
+    data_command_t data_cmd = {
+        .common = {
+            .direction = 0,
+            .command = FLASH_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_flash_end_cmd(bool stay_in_loader)
+{
+    flash_end_command_t end_cmd = {
+        .common = {
+            .direction = 0,
+            .command = FLASH_END,
+            .size = 4,
+            .checksum = 0
+        },
+        .stay_in_loader = stay_in_loader
+    };
+
+    return send_cmd(&end_cmd, sizeof(end_cmd), NULL);
+}
+
+
+esp_loader_error_t loader_sync_cmd(void)
+{
+    sync_command_t sync_cmd = {
+        .common = {
+            .direction = 0,
+            .command = SYNC,
+            .size = 36,
+            .checksum = 0
+        },
+        .sync_sequence = {
+            0x07, 0x07, 0x12, 0x20,
+            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+        }
+    };
+
+    return send_cmd(&sync_cmd, sizeof(sync_cmd), NULL);
+}
+
+
+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,
+            .command = WRITE_REG,
+            .size = 16,
+            .checksum = 0
+        },
+        .address = address,
+        .value = value,
+        .mask = mask,
+        .delay_us = delay_us
+    };
+
+    return send_cmd(&write_cmd, sizeof(write_cmd), NULL);
+}
+
+
+esp_loader_error_t loader_read_reg_cmd(uint32_t address, uint32_t *reg)
+{
+    read_reg_command_t read_cmd = {
+        .common = {
+            .direction = 0,
+            .command = READ_REG,
+            .size = 16,
+            .checksum = 0
+        },
+        .address = address,
+    };
+
+    return send_cmd(&read_cmd, sizeof(read_cmd), reg);
+}
+
+
+esp_loader_error_t loader_spi_attach_cmd(uint32_t config)
+{
+    spi_attach_command_t attach_cmd = {
+        .common = {
+            .direction = 0,
+            .command = SPI_ATTACH,
+            .size = 8,
+            .checksum = 0
+        },
+        .configuration = config,
+        .zero = 0
+    };
+
+    return send_cmd(&attach_cmd, sizeof(attach_cmd), NULL);
+}
+
+
+__attribute__ ((weak)) void esp_loader_debug_print(const char *str)
+{
+
+}