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

feature: support load binary to esp slave's RAM

wuzhenghui 3 лет назад
Родитель
Сommit
08ddca24b1

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 build
-sdkconfig
+sdkconfig*
 empty_file.bin
 binaries.c
+*.lock

+ 1 - 1
README.md

@@ -19,7 +19,7 @@ Supported **target** microcontrollers:
 - ESP32-S3
 - ESP32-C3
 - ESP32-C2
-- ESP32-H2 (Beta1 and Beta2)
+- ESP32-H4 (Beta1 and Beta2)
 
 ## Supporting new host target
 

BIN
examples/binaries/Hello-world/ESP32_H4/bootloader.bin


BIN
examples/binaries/Hello-world/ESP32_H4/hello_world.bin


BIN
examples/binaries/Hello-world/ESP32_H4/partition-table.bin


BIN
examples/binaries/RAM_APP/ESP32_H4/app.bin


+ 102 - 9
examples/common/example_common.c

@@ -14,7 +14,9 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
+#include <inttypes.h>
 #include <sys/param.h>
 #include "serial_io.h"
 #include "esp_loader.h"
@@ -22,10 +24,13 @@
 
 #ifndef SINGLE_TARGET_SUPPORT
 
-#define BOOTLOADER_ADDRESS_8266  0x1000
-#define BOOTLOADER_ADDRESS  0x1000
-#define PARTITION_ADDRESS   0x8000
-#define APPLICATION_ADDRESS 0x10000
+
+// For esp8266, esp32, esp32s2
+#define BOOTLOADER_ADDRESS_V0       0x1000
+// For esp32s3 and later chips
+#define BOOTLOADER_ADDRESS_V1       0x0
+#define PARTITION_ADDRESS           0x8000
+#define APPLICATION_ADDRESS         0x10000
 
 extern const uint8_t  ESP32_bootloader_bin[];
 extern const uint32_t ESP32_bootloader_bin_size;
@@ -48,12 +53,19 @@ extern const uint32_t ESP8266_hello_world_bin_size;
 extern const uint8_t  ESP8266_partition_table_bin[];
 extern const uint32_t ESP8266_partition_table_bin_size;
 
+extern const uint8_t  ESP32_H4_bootloader_bin[];
+extern const uint32_t ESP32_H4_bootloader_bin_size;
+extern const uint8_t  ESP32_H4_hello_world_bin[];
+extern const uint32_t ESP32_H4_hello_world_bin_size;
+extern const uint8_t  ESP32_H4_partition_table_bin[];
+extern const uint32_t ESP32_H4_partition_table_bin_size;
+
 void get_example_binaries(target_chip_t target, example_binaries_t *bins)
 {
     if (target == ESP8266_CHIP) {
         bins->boot.data = ESP8266_bootloader_bin;
         bins->boot.size = ESP8266_bootloader_bin_size;
-        bins->boot.addr = BOOTLOADER_ADDRESS_8266;
+        bins->boot.addr = BOOTLOADER_ADDRESS_V0;
         bins->part.data = ESP8266_partition_table_bin;
         bins->part.size = ESP8266_partition_table_bin_size;
         bins->part.addr = PARTITION_ADDRESS;
@@ -63,23 +75,49 @@ void get_example_binaries(target_chip_t target, example_binaries_t *bins)
     } else if (target == ESP32_CHIP) {
         bins->boot.data = ESP32_bootloader_bin;
         bins->boot.size = ESP32_bootloader_bin_size;
-        bins->boot.addr = BOOTLOADER_ADDRESS;
+        bins->boot.addr = BOOTLOADER_ADDRESS_V0;
         bins->part.data = ESP32_partition_table_bin;
         bins->part.size = ESP32_partition_table_bin_size;
         bins->part.addr = PARTITION_ADDRESS;
         bins->app.data  = ESP32_hello_world_bin;
         bins->app.size  = ESP32_hello_world_bin_size;
         bins->app.addr  = APPLICATION_ADDRESS;
-    } else {
+    } else if (target == ESP32S2_CHIP) {
         bins->boot.data = ESP32_S2_bootloader_bin;
         bins->boot.size = ESP32_S2_bootloader_bin_size;
-        bins->boot.addr = BOOTLOADER_ADDRESS;
+        bins->boot.addr = BOOTLOADER_ADDRESS_V0;
         bins->part.data = ESP32_S2_partition_table_bin;
         bins->part.size = ESP32_S2_partition_table_bin_size;
         bins->part.addr = PARTITION_ADDRESS;
         bins->app.data  = ESP32_S2_hello_world_bin;
         bins->app.size  = ESP32_S2_hello_world_bin_size;
         bins->app.addr  = APPLICATION_ADDRESS;
+    } else if (target == ESP32H4_CHIP){
+        bins->boot.data = ESP32_H4_bootloader_bin;
+        bins->boot.size = ESP32_H4_bootloader_bin_size;
+        bins->boot.addr = BOOTLOADER_ADDRESS_V1;
+        bins->part.data = ESP32_H4_partition_table_bin;
+        bins->part.size = ESP32_H4_partition_table_bin_size;
+        bins->part.addr = PARTITION_ADDRESS;
+        bins->app.data  = ESP32_H4_hello_world_bin;
+        bins->app.size  = ESP32_H4_hello_world_bin_size;
+        bins->app.addr  = APPLICATION_ADDRESS;
+    } else {
+        abort();
+    }
+}
+
+
+extern const uint8_t  ESP32_H4_app_bin[];
+extern const uint32_t ESP32_H4_app_bin_size;
+
+void get_example_ram_app_binary(target_chip_t target, example_ram_app_binary_t *bin)
+{
+    if (target == ESP32H4_CHIP){
+        bin->ram_app.data = ESP32_H4_app_bin;
+        bin->ram_app.size = ESP32_H4_app_bin_size;
+    } else {
+        abort();
     }
 }
 
@@ -169,4 +207,59 @@ esp_loader_error_t flash_binary(const uint8_t *bin, size_t size, size_t address)
 #endif
 
     return ESP_LOADER_SUCCESS;
-}
+}
+
+
+esp_loader_error_t load_ram_binary(const uint8_t *bin)
+{
+    printf("Start loading\n");
+    esp_loader_error_t err;
+    esp_loader_bin_header_t *header = (esp_loader_bin_header_t *)bin;
+    esp_loader_bin_segment_t segments[header->segments];
+
+    // Parse segments
+    uint32_t seg;
+    uint32_t *cur_seg_pos;
+    for (seg=0, cur_seg_pos = (uint32_t *)(&bin[BIN_FIRST_SEGMENT_OFFSET]);
+         seg < header->segments;
+         seg++) {
+        segments[seg].addr = *cur_seg_pos++;
+        segments[seg].size = *cur_seg_pos++;
+        segments[seg].data = (uint8_t *)cur_seg_pos;
+        cur_seg_pos += (segments[seg].size) / 4;
+    }
+
+    // Download segments
+    for (seg=0; seg < header->segments; seg++) {
+        printf("Downloading %"PRIu32" bytes at 0x%08"PRIx32"...\n", segments[seg].size, segments[seg].addr);
+
+        err = esp_loader_mem_start(segments[seg].addr, segments[seg].size, ESP_RAM_BLOCK);
+        if (err != ESP_LOADER_SUCCESS) {
+            printf("Loading ram start with error %d.\n", err);
+            return err;
+        }
+
+        size_t remain_size = segments[seg].size;
+        uint8_t *data_pos = segments[seg].data;
+        while(remain_size > 0) {
+            size_t data_size = MIN(ESP_RAM_BLOCK, remain_size);
+            err = esp_loader_mem_write(data_pos, data_size);
+            if (err != ESP_LOADER_SUCCESS) {
+                printf("\nPacket could not be written! Error %d.\n", err);
+                return err;
+            }
+            data_pos += data_size;
+            remain_size -= data_size;
+        }
+    }
+
+    err = esp_loader_mem_finish(header->entrypoint);
+    if (err != ESP_LOADER_SUCCESS) {
+        printf("\nLoad ram finish with Error %d.\n", err);
+        return err;
+    }
+    printf("\nFinished loading\n");
+
+    return ESP_LOADER_SUCCESS;
+}
+

+ 11 - 1
examples/common/example_common.h

@@ -15,6 +15,10 @@
 
 #pragma once
 
+#define BIN_FIRST_SEGMENT_OFFSET    0x18
+// Maximum block sized for RAM and Flash writes, respectively.
+#define ESP_RAM_BLOCK               0x1800
+
 typedef struct {
     const uint8_t *data;
     uint32_t size;
@@ -27,6 +31,12 @@ typedef struct {
     partition_attr_t app;
 } example_binaries_t;
 
+typedef struct {
+    partition_attr_t ram_app;
+} example_ram_app_binary_t;
+
 void get_example_binaries(target_chip_t target, example_binaries_t *binaries);
+void get_example_ram_app_binary(target_chip_t target, example_ram_app_binary_t *bin);
 esp_loader_error_t connect_to_target(uint32_t higrer_baudrate);
-esp_loader_error_t flash_binary(const uint8_t *bin, size_t size, size_t address);
+esp_loader_error_t flash_binary(const uint8_t *bin, size_t size, size_t address);
+esp_loader_error_t load_ram_binary(const uint8_t *bin);

+ 4 - 0
examples/esp32_example/main/main.c

@@ -41,8 +41,12 @@ void app_main(void)
 
         get_example_binaries(esp_loader_get_target(), &bin);
 
+        printf("\e[1;32mLoading bootloader...\n\e[0m");
         flash_binary(bin.boot.data, bin.boot.size, bin.boot.addr);
+        printf("\e[1;32mLoading partition table...\n\e[0m");
         flash_binary(bin.part.data, bin.part.size, bin.part.addr);
+        printf("\e[1;32mLoading app...\n\e[0m");
         flash_binary(bin.app.data,  bin.app.size,  bin.app.addr);
+        printf("\e[1;32mDone!\n\e[0m");
     }
 }

+ 7 - 0
examples/esp32_load_ram_example/CMakeLists.txt

@@ -0,0 +1,7 @@
+# The following lines of boilerplate have to be in your project's CMakeLists
+# in this exact order for cmake to work correctly
+cmake_minimum_required(VERSION 3.5)
+
+set(EXTRA_COMPONENT_DIRS ../../)
+include($ENV{IDF_PATH}/tools/cmake/project.cmake)
+project(esp-serial-flasher)

+ 14 - 0
examples/esp32_load_ram_example/main/CMakeLists.txt

@@ -0,0 +1,14 @@
+set(srcs main.c ../../common/example_common.c)
+set(include_dirs . ../../common)
+
+idf_component_register(SRCS ${srcs}
+                       INCLUDE_DIRS ${include_dirs})
+set(target ${COMPONENT_LIB})
+
+# Embed binaries into the app.
+# In ESP-IDF this can also be done using EMBED_FILES option of idf_component_register.
+# Here an external tool is used to make file embedding similar with other ports.
+include(${CMAKE_CURRENT_LIST_DIR}/../../common/bin2array.cmake)
+create_resources(${CMAKE_CURRENT_LIST_DIR}/../../binaries/RAM_APP ${CMAKE_BINARY_DIR}/binaries.c)
+set_property(SOURCE ${CMAKE_BINARY_DIR}/binaries.c PROPERTY GENERATED 1)
+target_sources(${target} PRIVATE ${CMAKE_BINARY_DIR}/binaries.c)

+ 77 - 0
examples/esp32_load_ram_example/main/main.c

@@ -0,0 +1,77 @@
+/* Flash multiple partitions example
+
+   This example code is in the Public Domain (or CC0 licensed, at your option.)
+
+   Unless required by applicable law or agreed to in writing, this
+   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+   CONDITIONS OF ANY KIND, either express or implied.
+*/
+
+#include <sys/param.h>
+#include <string.h>
+#include "esp_err.h"
+#include "esp_log.h"
+#include "esp_task_wdt.h"
+#include "driver/uart.h"
+#include "driver/gpio.h"
+#include "esp32_port.h"
+#include "esp_loader.h"
+#include "example_common.h"
+#include "freertos/FreeRTOS.h"
+
+// This can be set to a higher baud rate, but because it takes some time to
+// switch the uart baud rate in SlaveMonitor task, the log at slave starup
+// time will be lost or garbled.
+#define HIGHER_BAUDRATE 115200
+
+// Max line size
+#define BUF_LEN 128
+static char buf[BUF_LEN] = {0};
+
+void SlaveMonitor(){
+#if (HIGHER_BAUDRATE != 115200)
+    uart_flush_input(UART_NUM_1);
+    uart_flush(UART_NUM_1);
+    uart_set_baudrate(UART_NUM_1, 115200);
+#endif
+    while (1) {
+        int rxBytes = uart_read_bytes(UART_NUM_1, buf, BUF_LEN, 100 / portTICK_PERIOD_MS);
+        buf[rxBytes] = '\0';
+        printf("%s", buf);
+    }
+}
+
+void app_main(void)
+{
+    example_ram_app_binary_t bin;
+
+    const loader_esp32_config_t config = {
+        .baud_rate = 115200,
+        .uart_port = UART_NUM_1,
+        .uart_rx_pin = GPIO_NUM_5,
+        .uart_tx_pin = GPIO_NUM_4,
+        .reset_trigger_pin = GPIO_NUM_25,
+        .gpio0_trigger_pin = GPIO_NUM_26,
+    };
+
+    if (loader_port_esp32_init(&config) != ESP_LOADER_SUCCESS) {
+        ESP_LOGE("example", "serial initialization failed.");
+        abort();
+    }
+
+    if (connect_to_target(HIGHER_BAUDRATE) == ESP_LOADER_SUCCESS) {
+        get_example_ram_app_binary(esp_loader_get_target(), &bin);
+        printf("\e[1;32mLoading app to RAM ...\n\e[0m");
+        esp_loader_error_t err = load_ram_binary(bin.ram_app.data);
+        if (err == ESP_LOADER_SUCCESS) {
+            // Forward slave's serial output
+            printf("\e[1;33m********************************************\n\e[0m");
+            printf("\e[1;33m*** Logs below are print from slave .... ***\n\e[0m");
+            printf("\e[1;33m********************************************\n\e[0m");
+            xTaskCreate(SlaveMonitor, "SlaveMonitor", 2048, NULL, configMAX_PRIORITIES, NULL);
+        } else {
+            printf("\e[1;31mLoading to ram failed ...\e[0m\n");
+        }
+    }
+    vTaskDelete(NULL);
+}

+ 72 - 1
include/esp_loader.h

@@ -59,11 +59,32 @@ typedef enum {
     ESP32C3_CHIP = 3,
     ESP32S3_CHIP = 4,
     ESP32C2_CHIP = 5,
-    ESP32H2_CHIP = 6,
+    ESP32H4_CHIP = 6,
     ESP_MAX_CHIP = 7,
     ESP_UNKNOWN_CHIP = 7
 } target_chip_t;
 
+/**
+ * @brief esptool portable bin header format
+ */
+typedef struct esp_loader_bin_header {
+  uint8_t magic;
+  uint8_t segments;
+  uint8_t flash_mode;
+  uint8_t flash_size_freq;
+  uint32_t entrypoint;
+} esp_loader_bin_header_t;
+
+/**
+ * @brief esptool portable bin segment format
+ */
+typedef struct esp_loader_bin_segment {
+  uint32_t addr;
+  uint32_t size;
+  uint8_t *data;
+} esp_loader_bin_segment_t;
+
+
 /**
  * @brief SPI pin configuration arguments
  */
@@ -162,6 +183,56 @@ esp_loader_error_t esp_loader_flash_write(void *payload, uint32_t size);
   */
 esp_loader_error_t esp_loader_flash_finish(bool reboot);
 
+
+/**
+  * @brief Initiates mem operation
+  *
+  * @param offset[in]       Address from which mem operation will be performed.
+  * @param size[in]         Size of the whole binary to be loaded into mem.
+  * @param block_size[in]   Size of buffer used in subsequent calls to esp_loader_mem_write.
+  *
+  * @note  image_size is size of the whole image, whereas, block_size is chunk of data sent
+  *        to the target, each time esp_mem_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_mem_start(uint32_t offset, uint32_t size, uint32_t block_size);
+
+
+/**
+  * @brief Writes supplied data to target's mem memory.
+  *
+  * @param payload[in]      Data to be loaded into target's memory.
+  * @param size[in]         Size of data in bytes.
+  *
+  * @note  size must not be greater that block_size supplied to previously called
+  *        esp_loader_mem_start function. 
+  *        Therefore, size of data 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_mem_write(void *payload, uint32_t size);
+
+
+/**
+  * @brief Ends mem operation.
+  *
+  * @param entrypoint[in]       entrypoint of ram program.
+  *
+  * @return
+  *     - ESP_LOADER_SUCCESS Success
+  *     - ESP_LOADER_ERROR_TIMEOUT Timeout
+  *     - ESP_LOADER_ERROR_INVALID_RESPONSE Internal error
+  */
+esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint);
+
+
 /**
   * @brief Writes register.
   *

+ 6 - 0
private_include/serial_comm.h

@@ -29,6 +29,12 @@ 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_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size);
+
+esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size);
+
+esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint);
+
 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);

+ 10 - 1
private_include/serial_comm_prv.h

@@ -79,7 +79,7 @@ typedef struct __attribute__((packed))
     uint32_t packet_size;
     uint32_t offset;
     uint32_t encrypted;
-} begin_command_t;
+} flash_begin_command_t;
 
 typedef struct __attribute__((packed))
 {
@@ -96,6 +96,15 @@ typedef struct __attribute__((packed))
     uint32_t stay_in_loader;
 } flash_end_command_t;
 
+typedef struct __attribute__((packed))
+{
+    command_common_t common;
+    uint32_t total_size;
+    uint32_t blocks;
+    uint32_t block_size;
+    uint32_t offset;
+} mem_begin_command_t;
+
 typedef struct __attribute__((packed))
 {
     command_common_t common;

+ 28 - 0
src/esp_loader.c

@@ -30,9 +30,14 @@
 #define MIN(a, b) ((a) < (b)) ? (a) : (b)
 #endif
 
+#ifndef ROUNDUP
+#define ROUNDUP(a, b) (((int)a + (int)b - 1) / (int)b)
+#endif
+
 static const uint32_t DEFAULT_TIMEOUT = 1000;
 static const uint32_t DEFAULT_FLASH_TIMEOUT = 3000;       // timeout for most flash operations
 static const uint32_t ERASE_REGION_TIMEOUT_PER_MB = 10000; // timeout (per megabyte) for erasing a region
+static const uint32_t LOAD_RAM_TIMEOUT_PER_MB = 2000000; // timeout (per megabyte) for erasing a region
 static const uint8_t  PADDING_PATTERN = 0xFF;
 
 typedef enum {
@@ -282,6 +287,29 @@ esp_loader_error_t esp_loader_flash_finish(bool reboot)
 }
 
 
+esp_loader_error_t esp_loader_mem_start(uint32_t offset, uint32_t size, uint32_t block_size)
+{
+    uint32_t blocks_to_write = ROUNDUP(size, block_size);
+    loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB));
+    return loader_mem_begin_cmd(offset, size, blocks_to_write, block_size);
+}
+
+
+esp_loader_error_t esp_loader_mem_write(void *payload, uint32_t size)
+{
+    uint8_t *data = (uint8_t *)payload;
+    loader_port_start_timer(timeout_per_mb(size, LOAD_RAM_TIMEOUT_PER_MB));
+    return loader_mem_data_cmd(data, size);
+}
+
+
+esp_loader_error_t esp_loader_mem_finish(uint32_t entrypoint)
+{
+    loader_port_start_timer(DEFAULT_TIMEOUT);
+    return loader_mem_end_cmd(entrypoint);
+}
+
+
 esp_loader_error_t esp_loader_read_register(uint32_t address, uint32_t *reg_value)
 {
     loader_port_start_timer(DEFAULT_TIMEOUT);

+ 2 - 2
src/esp_targets.c

@@ -136,7 +136,7 @@ static const esp_target_t esp_target[ESP_MAX_CHIP] = {
         .chip_magic_value = { 0x6f51306f, 0 },
         .read_spi_config = spi_config_esp32xx,
     },
-    // ESP32H2
+    // ESP32H4
     {
         .regs = {
             .cmd  = ESP32xx_SPI_REG_BASE + 0x00,
@@ -148,7 +148,7 @@ static const esp_target_t esp_target[ESP_MAX_CHIP] = {
             .miso_dlen = ESP32xx_SPI_REG_BASE + 0x28,
         },
         .efuse_base = 0x6001A000,
-        .chip_magic_value = {0xca26cc22, 0x6881b06f}, // ESP32H2-BETA1, ESP32H2-BETA2
+        .chip_magic_value = {0xca26cc22, 0x6881b06f}, // ESP32H4-BETA1, ESP32H4-BETA2
         .read_spi_config = spi_config_esp32xx,
     },
 };

+ 56 - 3
src/serial_comm.c

@@ -244,11 +244,11 @@ esp_loader_error_t loader_flash_begin_cmd(uint32_t offset,
 {
     uint32_t encryption_size = encryption ? sizeof(uint32_t) : 0;
 
-    begin_command_t begin_cmd = {
+    flash_begin_command_t flash_begin_cmd = {
         .common = {
             .direction = WRITE_DIRECTION,
             .command = FLASH_BEGIN,
-            .size = CMD_SIZE(begin_cmd) - encryption_size,
+            .size = CMD_SIZE(flash_begin_cmd) - encryption_size,
             .checksum = 0
         },
         .erase_size = erase_size,
@@ -260,7 +260,7 @@ esp_loader_error_t loader_flash_begin_cmd(uint32_t offset,
 
     s_sequence_number = 0;
 
-    return send_cmd(&begin_cmd, sizeof(begin_cmd) - encryption_size, NULL);
+    return send_cmd(&flash_begin_cmd, sizeof(flash_begin_cmd) - encryption_size, NULL);
 }
 
 
@@ -297,6 +297,59 @@ esp_loader_error_t loader_flash_end_cmd(bool stay_in_loader)
 }
 
 
+esp_loader_error_t loader_mem_begin_cmd(uint32_t offset, uint32_t size, uint32_t blocks_to_write, uint32_t block_size)
+{
+
+    mem_begin_command_t mem_begin_cmd = {
+        .common = {
+            .direction = WRITE_DIRECTION,
+            .command = MEM_BEGIN,
+            .size = CMD_SIZE(mem_begin_cmd),
+            .checksum = 0
+        },
+        .total_size = size,
+        .blocks = blocks_to_write,
+        .block_size = block_size,
+        .offset = offset
+    };
+
+    s_sequence_number = 0;
+
+    return send_cmd(&mem_begin_cmd, sizeof(mem_begin_cmd), NULL);
+}
+
+
+esp_loader_error_t loader_mem_data_cmd(const uint8_t *data, uint32_t size)
+{
+    data_command_t data_cmd = {
+        .common = {
+            .direction = WRITE_DIRECTION,
+            .command = MEM_DATA,
+            .size = CMD_SIZE(data_cmd) + size,
+            .checksum = compute_checksum(data, size)
+        },
+        .data_size = size,
+        .sequence_number = s_sequence_number++,
+    };
+    return send_cmd_with_data(&data_cmd, sizeof(data_cmd), data, size);
+}
+
+esp_loader_error_t loader_mem_end_cmd(uint32_t entrypoint)
+{
+    mem_end_command_t end_cmd = {
+        .common = {
+            .direction = WRITE_DIRECTION,
+            .command = MEM_END,
+            .size = CMD_SIZE(end_cmd),
+        },
+        .stay_in_loader = (entrypoint == 0),
+        .entry_point_address = entrypoint
+    };
+
+    return send_cmd(&end_cmd, sizeof(end_cmd), NULL);
+}
+
+
 esp_loader_error_t loader_sync_cmd(void)
 {
     sync_command_t sync_cmd = {

+ 4 - 4
test/test.cpp

@@ -118,7 +118,7 @@ map<target_chip_t, uint32_t> chip_magic_value = {
     {ESP32C2_CHIP,  0x6f51306f},
     {ESP32C3_CHIP,  0x6921506f},
     {ESP32S3_CHIP,  0x00000009},
-    {ESP32H2_CHIP,  0xca26cc22},
+    {ESP32H4_CHIP,  0xca26cc22},
 };
 
 void queue_connect_response(target_chip_t target = ESP32_CHIP, uint32_t magic_value = 0)
@@ -202,10 +202,10 @@ TEST_CASE( "Can detect attached target" )
         REQUIRE( esp_loader_get_target() == ESP32C2_CHIP );
     }
 
-    SECTION( "Can detect ESP32H2" ) {
-        queue_connect_response(ESP32H2_CHIP);
+    SECTION( "Can detect ESP32H4" ) {
+        queue_connect_response(ESP32H4_CHIP);
         REQUIRE_SUCCESS( esp_loader_connect(&connect_config) );
-        REQUIRE( esp_loader_get_target() == ESP32H2_CHIP );
+        REQUIRE( esp_loader_get_target() == ESP32H4_CHIP );
     }
 
     SECTION( "Can detect ESP32C3" ) {