This is an automated email from Gerrit. "Erhan Kurubas <erhan.kuru...@espressif.com>" just uploaded a new patch set to Gerrit, which you can find at https://review.openocd.org/c/openocd/+/7759
-- gerrit commit 3673a8a6bbe23f8fc00961451e8570223ad874db Author: Erhan Kurubas <erhan.kuru...@espressif.com> Date: Mon Jul 3 11:02:13 2023 +0200 target/espressif: add algorithm support to execute code on target This functionality can be useful for; 1-ESP flashing code to load flasher stub on target and write/read/erase flash. 2-ESP GCOV command uses some of these functions to run onboard routines to dump coverage info. This is high level api for the Espressif xtensa and riscv targets Signed-off-by: Erhan Kurubas <erhan.kuru...@espressif.com> Change-Id: I5e618b960bb6566ee618d4ba261f51af97a7cb0e diff --git a/src/target/espressif/Makefile.am b/src/target/espressif/Makefile.am index 776818ff4d..1fbd926d9d 100644 --- a/src/target/espressif/Makefile.am +++ b/src/target/espressif/Makefile.am @@ -21,4 +21,6 @@ noinst_LTLIBRARIES += %D%/libespressif.la %D%/esp32_sysview.h \ %D%/segger_sysview.h \ %D%/esp_semihosting.c \ - %D%/esp_semihosting.h + %D%/esp_semihosting.h \ + %D%/esp_algorithm.c \ + %D%/esp_algorithm.h diff --git a/src/target/espressif/esp_algorithm.c b/src/target/espressif/esp_algorithm.c new file mode 100644 index 0000000000..9624c4057b --- /dev/null +++ b/src/target/espressif/esp_algorithm.c @@ -0,0 +1,590 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +/*************************************************************************** + * Espressif chips common algorithm API for OpenOCD * + * Copyright (C) 2022 Espressif Systems Ltd. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <helper/align.h> +#include <target/algorithm.h> +#include <target/target.h> +#include "esp_algorithm.h" + +#define ALGO_ALGORITHM_EXIT_TMO 40000 /* ms */ + +static int algorithm_read_stub_logs(struct target *target, struct algorithm_stub *stub) +{ + if (stub->log_buff_addr == 0 || stub->log_buff_size == 0) + return ERROR_FAIL; + + uint32_t len = 0; + int retval = target_read_u32(target, stub->log_buff_addr, &len); + if (retval != ERROR_OK) + return retval; + + /* sanity check. log_buff_size = sizeof(len) + sizeof(log_buff) */ + if (len == 0 || len > stub->log_buff_size - 4) + return ERROR_FAIL; + + uint8_t *log_buff = calloc(1, len); + if (!log_buff) { + LOG_ERROR("Failed to allocate memory for the stub log (%d)!", retval); + return retval; + } + retval = target_read_memory(target, stub->log_buff_addr + 4, 1, len, log_buff); + if (retval == ERROR_OK) + LOG_OUTPUT("%*.*s", len, len, log_buff); + free(log_buff); + return retval; +} + +static int algorithm_run(struct target *target, struct algorithm_image *image, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap) +{ + void **mem_handles = NULL; + + int retval = run->hw->algo_init(target, run, num_args, ap); + if (retval != ERROR_OK) + return retval; + + /* allocate memory arguments and fill respective reg params */ + if (run->mem_args.count > 0) { + mem_handles = calloc(run->mem_args.count, sizeof(void *)); + if (!mem_handles) { + LOG_ERROR("Failed to alloc target mem handles!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _cleanup; + } + /* alloc memory args target buffers */ + for (uint32_t i = 0; i < run->mem_args.count; i++) { + /* small hack: if we need to update some reg param this field holds + * appropriate user argument number, */ + /* otherwise should hold UINT_MAX */ + uint32_t usr_param_num = run->mem_args.params[i].address; + if (image) { + static struct working_area *area; + retval = target_alloc_working_area(target, run->mem_args.params[i].size, &area); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to alloc target buffer!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _cleanup; + } + mem_handles[i] = area; + run->mem_args.params[i].address = area->address; + } else { + struct algorithm_run_data alloc_run; + memset(&alloc_run, 0, sizeof(alloc_run)); + alloc_run.hw = run->hw; + alloc_run.stack_size = run->on_board.min_stack_size; + alloc_run.on_board.min_stack_addr = run->on_board.min_stack_addr; + alloc_run.on_board.code_buf_size = run->on_board.code_buf_size; + alloc_run.on_board.code_buf_addr = run->on_board.code_buf_addr; + retval = algorithm_run_onboard_func(target, + &alloc_run, + run->on_board.alloc_func, + 1, + run->mem_args.params[i].size); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to run mem arg alloc onboard algo (%d)!", retval); + goto _cleanup; + } + if (alloc_run.ret_code == 0) { + LOG_ERROR("Failed to alloc onboard memory (%d)!", retval); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _cleanup; + } + mem_handles[i] = (void *)((long)alloc_run.ret_code); + run->mem_args.params[i].address = alloc_run.ret_code; + } + if (usr_param_num != UINT_MAX) /* if we need update some register param with mem param value */ + algorithm_user_arg_set_uint(run, usr_param_num, run->mem_args.params[i].address); + } + } + + if (run->usr_func_init) { + retval = run->usr_func_init(target, run, run->usr_func_arg); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to prepare algorithm host side args stub (%d)!", retval); + goto _cleanup; + } + } + + LOG_DEBUG("Algorithm start @ " TARGET_ADDR_FMT ", stack %d bytes @ " TARGET_ADDR_FMT, + run->stub.tramp_mapped_addr, run->stack_size, run->stub.stack_addr); + retval = target_start_algorithm(target, + run->mem_args.count, run->mem_args.params, + run->reg_args.count, run->reg_args.params, + run->stub.tramp_mapped_addr, 0, + run->stub.ainfo); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to start algorithm (%d)!", retval); + goto _cleanup; + } + + if (run->usr_func) { + /* give target algorithm stub time to init itself, then user func can communicate to it safely */ + alive_sleep(100); + retval = run->usr_func(target, run->usr_func_arg); + if (retval != ERROR_OK) + LOG_ERROR("Failed to exec algorithm user func (%d)!", retval); + } + uint32_t tmo = 0; /* do not wait if 'usr_func' returned error */ + if (retval == ERROR_OK) + tmo = run->tmo ? run->tmo : ALGO_ALGORITHM_EXIT_TMO; + LOG_DEBUG("Wait algorithm completion"); + retval = target_wait_algorithm(target, + run->mem_args.count, run->mem_args.params, + run->reg_args.count, run->reg_args.params, + 0, tmo, + run->stub.ainfo); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to wait algorithm (%d)!", retval); + /* target has been forced to stop in target_wait_algorithm() */ + } + algorithm_read_stub_logs(target, &run->stub); + + if (run->usr_func_done) + run->usr_func_done(target, run, run->usr_func_arg); + + if (retval != ERROR_OK) { + LOG_ERROR("Algorithm run failed (%d)!", retval); + } else { + run->ret_code = algorithm_user_arg_get_uint(run, 0); + LOG_DEBUG("Got algorithm RC 0x%" PRIx32, run->ret_code); + } + +_cleanup: + /* free memory arguments */ + if (mem_handles) { + for (uint32_t i = 0; i < run->mem_args.count; i++) { + if (mem_handles[i]) { + if (image) { + target_free_working_area(target, mem_handles[i]); + } else { + struct algorithm_run_data free_run; + memset(&free_run, 0, sizeof(free_run)); + free_run.hw = run->hw; + free_run.stack_size = run->on_board.min_stack_size; + free_run.on_board.min_stack_addr = run->on_board.min_stack_addr; + free_run.on_board.code_buf_size = run->on_board.code_buf_size; + free_run.on_board.code_buf_addr = run->on_board.code_buf_addr; + int ret = algorithm_run_onboard_func(target, + &free_run, + run->on_board.free_func, + 1, + mem_handles[i]); + if (ret != ERROR_OK) + LOG_ERROR("Failed to run mem arg free onboard algo (%d)!", ret); + } + } + } + free(mem_handles); + } + run->hw->algo_cleanup(target, run); + + return retval; +} + +static void reverse_binary(const uint8_t *src, uint8_t *dest, size_t length) +{ + size_t i; + + /* Put extra bytes to the beginning with padding */ + size_t remaining = length % 4; + if (remaining > 0) { + memset(dest, 0xFF, 4); + for (i = 0; i < remaining; i++) + dest[i] = src[length - remaining + i]; + } + + for (i = remaining; i < length - remaining; i += 4) { + dest[i + 0] = src[length - i - 4]; + dest[i + 1] = src[length - i - 3]; + dest[i + 2] = src[length - i - 2]; + dest[i + 3] = src[length - i - 1]; + } +} + +static int load_section_from_image(struct target *target, + struct algorithm_run_data *run, int section_num, bool reverse) +{ + struct imagesection *section = &run->image.image.sections[section_num]; + uint32_t sec_wr = 0; + uint8_t buf[1024]; + + assert(sizeof(buf) % 4 == 0); + + while (sec_wr < section->size) { + uint32_t nb = section->size - sec_wr > sizeof(buf) ? sizeof(buf) : section->size - sec_wr; + size_t size_read = 0; + int retval = image_read_section(&run->image.image, section_num, sec_wr, nb, buf, &size_read); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to read stub section (%d)!", retval); + return retval; + } + + if (reverse) { + size_t aligned_len = ALIGN_UP(size_read, 4); + uint8_t reversed_buf[aligned_len]; + + /* Send original size to allow padding */ + reverse_binary(buf, reversed_buf, size_read); + + retval = target_write_buffer(target, run->image.dram_org - sec_wr - size_read, size_read, reversed_buf); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to write stub section!"); + return retval; + } + } else { + retval = target_write_buffer(target, section->base_address + sec_wr, size_read, buf); + if (retval != ERROR_OK) { + LOG_ERROR("Failed to write stub section!"); + return retval; + } + } + + sec_wr += size_read; + } + + return ERROR_OK; +} + +int algorithm_load_func_image(struct target *target, struct algorithm_run_data *run) +{ + int retval; + size_t tramp_sz = 0; + const uint8_t *tramp = NULL; + struct duration algo_time; + bool alloc_working_area = true; + + if (duration_start(&algo_time) != 0) { + LOG_ERROR("Failed to start algo time measurement!"); + return ERROR_FAIL; + } + + if (run->hw->stub_tramp_get) { + tramp = run->hw->stub_tramp_get(target, &tramp_sz); + if (!tramp) + return ERROR_FAIL; + } + + LOG_DEBUG("stub: base 0x%x, start 0x%x, %d sections", + run->image.image.base_address_set ? (unsigned int)run->image.image.base_address : 0, + run->image.image.start_address, + run->image.image.num_sections); + run->stub.entry = run->image.image.start_address; + + /* [code + trampoline] + <padding> + [data] */ + + /* ESP32 has reversed memory region. It will use the last part of DRAM, the others will use the first part. + * To avoid complexity for the backup/restore process, we will allocate a workarea for all IRAM region from + * the beginning. In that case no need to have a padding area. + */ + if (run->image.reverse) { + if (target_alloc_working_area(target, run->image.iram_len, &run->stub.code) != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc space for stub code!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + alloc_working_area = false; + } + + uint32_t code_size = 0; + + /* Load code section */ + for (unsigned int i = 0; i < run->image.image.num_sections; i++) { + struct imagesection *section = &run->image.image.sections[i]; + + if (section->size == 0) + continue; + + if (section->flags & ESP_IMAGE_ELF_PHF_EXEC) { + LOG_DEBUG("addr " TARGET_ADDR_FMT ", sz %d, flags %" PRIx64, + section->base_address, section->size, section->flags); + + if (alloc_working_area) { + retval = target_alloc_working_area(target, section->size, &run->stub.code); + if (retval != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc space for stub code!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + } + + if (section->base_address == 0) { + section->base_address = run->stub.code->address; + /* sanity check, stub is compiled to be run from working area */ + } else if (run->stub.code->address != section->base_address) { + LOG_ERROR("working area " TARGET_ADDR_FMT " and stub code section " TARGET_ADDR_FMT + " address mismatch!", + section->base_address, + run->stub.code->address); + retval = ERROR_FAIL; + goto _on_error; + } + + retval = load_section_from_image(target, run, i, run->image.reverse); + if (retval != ERROR_OK) + goto _on_error; + + code_size += ALIGN_UP(section->size, 4); + break; /* Stub has one executable text section */ + } + } + + /* If exists, load trampoline to the code area */ + if (tramp) { + if (run->stub.tramp_addr == 0) { + if (alloc_working_area) { + /* alloc trampoline in code working area */ + if (target_alloc_working_area(target, tramp_sz, &run->stub.tramp) != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc space for stub jumper!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + run->stub.tramp_addr = run->stub.tramp->address; + } + } + + size_t al_tramp_size = ALIGN_UP(tramp_sz, 4); + + if (run->image.reverse) { + target_addr_t reversed_tramp_addr = run->image.dram_org - code_size; + uint8_t reversed_tramp[al_tramp_size]; + + /* Send original size to allow padding */ + reverse_binary(tramp, reversed_tramp, tramp_sz); + run->stub.tramp_addr = reversed_tramp_addr - al_tramp_size; + LOG_DEBUG("Write reversed tramp to addr " TARGET_ADDR_FMT ", sz %zu", run->stub.tramp_addr, al_tramp_size); + retval = target_write_buffer(target, run->stub.tramp_addr, al_tramp_size, reversed_tramp); + } else { + LOG_DEBUG("Write tramp to addr " TARGET_ADDR_FMT ", sz %zu", run->stub.tramp_addr, tramp_sz); + retval = target_write_buffer(target, run->stub.tramp_addr, tramp_sz, tramp); + } + + if (retval != ERROR_OK) { + LOG_ERROR("Failed to write stub jumper!"); + goto _on_error; + } + + run->stub.tramp_mapped_addr = run->image.iram_org + code_size; + code_size += al_tramp_size; + LOG_DEBUG("Tramp mapped to addr " TARGET_ADDR_FMT, run->stub.tramp_mapped_addr); + } + + /* allocate dummy space until the data address */ + if (alloc_working_area) { + /* we dont need to restore padding area. */ + uint32_t backup_working_area_prev = target->backup_working_area; + target->backup_working_area = 0; + if (target_alloc_working_area(target, run->image.iram_len - code_size, &run->stub.padding) != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc space for stub code!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + target->backup_working_area = backup_working_area_prev; + } + + /* Load the data section */ + for (unsigned int i = 0; i < run->image.image.num_sections; i++) { + struct imagesection *section = &run->image.image.sections[i]; + + if (section->size == 0) + continue; + + if (!(section->flags & ESP_IMAGE_ELF_PHF_EXEC)) { + LOG_DEBUG("addr " TARGET_ADDR_FMT ", sz %d, flags %" PRIx64, section->base_address, section->size, + section->flags); + /* target_alloc_working_area() aligns the whole working area size to 4-byte boundary. + We alloc one area for both DATA and BSS, so align each of them ourselves. */ + uint32_t data_sec_sz = ALIGN_UP(section->size, 4); + LOG_DEBUG("DATA sec size %" PRIu32 " -> %" PRIu32, section->size, data_sec_sz); + uint32_t bss_sec_sz = ALIGN_UP(run->image.bss_size, 4); + LOG_DEBUG("BSS sec size %" PRIu32 " -> %" PRIu32, run->image.bss_size, bss_sec_sz); + if (target_alloc_working_area(target, data_sec_sz + bss_sec_sz, &run->stub.data) != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc space for stub data!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + if (section->base_address == 0) { + section->base_address = run->stub.data->address; + /* sanity check, stub is compiled to be run from working area */ + } else if (run->stub.data->address != section->base_address) { + LOG_ERROR("working area " TARGET_ADDR_FMT + " and stub data section " TARGET_ADDR_FMT + " address mismatch!", + section->base_address, + run->stub.data->address); + retval = ERROR_FAIL; + goto _on_error; + } + + retval = load_section_from_image(target, run, i, false); + if (retval != ERROR_OK) + goto _on_error; + } + } + + /* stack */ + if (run->stub.stack_addr == 0 && run->stack_size > 0) { + /* allocate stack in data working area */ + if (target_alloc_working_area(target, run->stack_size, &run->stub.stack) != ERROR_OK) { + LOG_ERROR("no working area available, can't alloc stub stack!"); + retval = ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + goto _on_error; + } + run->stub.stack_addr = run->stub.stack->address + run->stack_size; + } + + if (duration_measure(&algo_time) != 0) { + LOG_ERROR("Failed to stop algo run measurement!"); + retval = ERROR_FAIL; + goto _on_error; + } + LOG_DEBUG("Stub loaded in %g ms", duration_elapsed(&algo_time) * 1000); + return ERROR_OK; + +_on_error: + algorithm_unload_func_image(target, run); + return retval; +} + +int algorithm_unload_func_image(struct target *target, struct algorithm_run_data *run) +{ + target_free_all_working_areas(target); + run->stub.tramp = NULL; + run->stub.stack = NULL; + run->stub.code = NULL; + run->stub.data = NULL; + run->stub.padding = NULL; + + return ERROR_OK; +} + +int algorithm_exec_func_image_va(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap) +{ + if (!run->image.image.start_address_set || run->image.image.start_address == 0) + return ERROR_FAIL; + + return algorithm_run(target, &run->image, run, num_args, ap); +} + +int algorithm_load_onboard_func(struct target *target, target_addr_t func_addr, struct algorithm_run_data *run) +{ + int res; + const uint8_t *tramp = NULL; + size_t tramp_sz = 0; + struct duration algo_time; + + if (duration_start(&algo_time) != 0) { + LOG_ERROR("Failed to start algo time measurement!"); + return ERROR_FAIL; + } + + if (run->hw->stub_tramp_get) { + tramp = run->hw->stub_tramp_get(target, &tramp_sz); + if (!tramp) + return ERROR_FAIL; + } + + if (tramp_sz > run->on_board.code_buf_size) { + LOG_ERROR("Stub tramp size %u bytes exceeds target buf size %d bytes!", + (uint32_t)tramp_sz, run->on_board.code_buf_size); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + + if (run->stack_size > run->on_board.min_stack_size) { + if (run->on_board.alloc_func == 0 || run->on_board.free_func == 0) { + LOG_ERROR("No stubs memory funcs found!"); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + /* alloc stack */ + struct algorithm_run_data alloc_run; + memset(&alloc_run, 0, sizeof(alloc_run)); + alloc_run.hw = run->hw; + alloc_run.stack_size = run->on_board.min_stack_size; + alloc_run.on_board.min_stack_addr = run->on_board.min_stack_addr; + alloc_run.on_board.code_buf_size = run->on_board.code_buf_size; + alloc_run.on_board.code_buf_addr = run->on_board.code_buf_addr; + res = algorithm_run_onboard_func(target, + &alloc_run, + run->on_board.alloc_func, + 1, + run->stack_size); + if (res != ERROR_OK) { + LOG_ERROR("Failed to run stack alloc onboard algo (%d)!", res); + return res; + } + LOG_DEBUG("RETCODE: 0x%" PRIx32 "!", alloc_run.ret_code); + if (alloc_run.ret_code == 0) { + LOG_ERROR("Failed to alloc onboard stack (%d)!", res); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } + run->stub.stack_addr = alloc_run.ret_code + run->stack_size; + } else { + run->stub.stack_addr = run->on_board.min_stack_addr + run->stack_size; + } + + run->stub.tramp_addr = run->on_board.code_buf_addr; + run->stub.tramp_mapped_addr = run->stub.tramp_addr; + run->stub.entry = func_addr; + + if (tramp) { + res = target_write_buffer(target, run->stub.tramp_addr, tramp_sz, tramp); + if (res != ERROR_OK) { + LOG_ERROR("Failed to write stub jumper!"); + algorithm_unload_onboard_func(target, run); + return res; + } + } + + if (duration_measure(&algo_time) != 0) { + LOG_ERROR("Failed to stop algo run measurement!"); + return ERROR_FAIL; + } + LOG_DEBUG("Stub loaded in %g ms", duration_elapsed(&algo_time) * 1000); + + return ERROR_OK; +} + +int algorithm_unload_onboard_func(struct target *target, struct algorithm_run_data *run) +{ + if (run->stack_size > run->on_board.min_stack_size) { + /* free stack */ + struct algorithm_run_data free_run; + memset(&free_run, 0, sizeof(free_run)); + free_run.hw = run->hw; + free_run.stack_size = run->on_board.min_stack_size; + free_run.on_board.min_stack_addr = run->on_board.min_stack_addr; + free_run.on_board.code_buf_size = run->on_board.code_buf_size; + free_run.on_board.code_buf_addr = run->on_board.code_buf_addr; + int res = algorithm_run_onboard_func(target, + &free_run, + run->on_board.free_func, + 1, + run->stub.stack_addr - run->stack_size); + if (res != ERROR_OK) { + LOG_ERROR("Failed to run stack free onboard algo (%d)!", res); + return res; + } + } + + return ERROR_OK; +} + +int algorithm_exec_onboard_func_va(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap) +{ + return algorithm_run(target, NULL, run, num_args, ap); +} diff --git a/src/target/espressif/esp_algorithm.h b/src/target/espressif/esp_algorithm.h new file mode 100644 index 0000000000..e707fd78da --- /dev/null +++ b/src/target/espressif/esp_algorithm.h @@ -0,0 +1,421 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +/*************************************************************************** + * Espressif chips common algorithm API for OpenOCD * + * Copyright (C) 2022 Espressif Systems Ltd. * + ***************************************************************************/ + +#ifndef OPENOCD_TARGET_ESP_ALGORITHM_H +#define OPENOCD_TARGET_ESP_ALGORITHM_H + +#include "helper/log.h" +#include "helper/binarybuffer.h" +#include <helper/time_support.h> +#include <target/algorithm.h> +#include <target/image.h> + +/** + * API defined below allows executing pieces of code on target without breaking the execution of the running program. + * This functionality can be useful for various debugging and maintenance procedures. + * @note ESP flashing code to load flasher stub on target and write/read/erase flash. + * Also ESP GCOV command uses some of these functions to run onboard routines to dump coverage info. + * Stub entry function can take up to 5 arguments and should be of the following form: + * + * int stub_entry([uint32_t a1 [, uint32_t a2 [, uint32_t a3 [, uint32_t a4 [, uint32_t a5]]]]]); + * + * The general scheme of stub code execution is shown below. + * + * ------- ----------- (initial frame) ---- + * | | -------(registers, stub entry, stub args)------> |trampoline | ---(stub args)---> | | + * | | | | | | + * |OpenOCD| <----------(stub-specific communications)---------------------------------------> |stub| + * | | | | | | + * | | <---------(target halted event, ret code)------- |tramp break| <---(ret code)---- | | + * ------- ----------- ---- + * + * Procedure of executing stub on target includes: + * 1) User prepares struct algorithm_run_data and calls one of algorithm_run_xxx() functions. + * 2) Routine allocates all necessary stub code and data sections. + * 3) If a user specifies an initializer func algorithm_usr_func_init_t it is called just before the stub starts. + * 4) If user specifies stub communication func algorithm_usr_func_t (@see esp_flash_write/read in ESP flash driver) + * it is called just after the stub starts. When communication with stub is finished this function must return. + * 5) OpenOCD waits for the stub to finish (hit exit breakpoint). + * 6) If the user specified arguments cleanup func algorithm_usr_func_done_t it is called just after the stub finishes. + * + * There are two options to run code on target under OpenOCD control: + * - Run externally compiled stub code. + * - Run onboard pre-compiled code. @note For ESP chips debug stubs must be enabled in target code @see ESP IDF docs. + * The main difference between the execution of external stub code and target built-in functions is that + * in the latter case working areas can not be used to allocate target memory for code and data because they can overlap + * with code and data involved in onboard function execution. For example, if memory allocated in the working area + * for the stub stack will overlap with some on-board data used by the stub the stack will get overwritten. + * The same stands for allocations in target code space. + * + * External Code Execution + * ----------------------- + * To run external code on the target user should use algorithm_run_func_image(). + * In this case all necessary memory (code/data) is allocated in working areas that have fixed configuration + * defined in target TCL file. Stub code is actually a standalone program, so all its segments must have known + * addresses due to position-dependent code nature. So stub must be linked in such a way that its code segment + * starts at the beginning of the working area for code space defined in TCL. The same restriction must be applied + * to stub's data segment and base addresses of working area for data space. @see ESP stub flasher LD scripts. + * Also in order to simplify memory allocation BSS section must follow the DATA section in the stub image. + * The size of the BSS section must be specified in the bss_size field of struct algorithm_image. + * Sample stub memory map is shown below. + * ___________________________________________ + * | data space working area start | + * | | + * | <stub .data segment> | + * |___________________________________________| + * | stub .bss start | + * | | + * | <stub .bss segment of size 'bss_size'> | + * |___________________________________________| + * | stub stack base | + * | | + * | <stub stack> | + * |___________________________________________| + * | | + * | <stub mem arg1> | + * |___________________________________________| + * | | + * | <stub mem arg2> | + * |___________________________________________| + * ___________________________________________ + * | code space working area start | + * | | + * | <stub .text segment> | + * |___________________________________________| + * | | + * | <stub trampoline with exit breakpoint> | + * |___________________________________________| + * + * For example on how to execute external code with memory arguments @see esp_algo_flash_blank_check in + * ESP flash driver. + * + * On-Board Code Execution + * ----------------------- + * To run on-board code on the target user should use algorithm_run_onboard_func(). + * On-board code execution process does not need to allocate target memory for stub code and data, + * Because the stub is pre-compiled to the code running on the target. + * But it still needs memory for stub trampoline, stack, and memory arguments. + * Working areas can not be used due to possible memory layout conflicts with on-board stub code and data. + * Debug stubs functionality provided by ESP IDF allows OpenOCD to overcome the above problem. + * It provides a special descriptor which provides info necessary to safely allocate memory on target. + * @see struct esp_dbg_stubs_desc. + * That info is also used to locate memory for stub trampoline code. + * User can execute target function at any address, but @see ESP IDF debug stubs also provide a way to pass to the host + * an entry address of pre-defined registered stub functions. + * For example of an on-board code execution @see esp32_cmd_gcov() in ESP32 apptrace module. +*/ + +/** + * Algorithm image data. + * Helper struct to work with algorithms consisting of code and data segments. + */ +struct algorithm_image { + /** Image. */ + struct image image; + /** BSS section size. */ + uint32_t bss_size; + /** IRAM start address in the linker script */ + uint32_t iram_org; + /** Total reserved IRAM size */ + uint32_t iram_len; + /** DRAM start address in the linker script */ + uint32_t dram_org; + /** Total reserved DRAM size */ + uint32_t dram_len; + /** IRAM DRAM address range reversed or not */ + bool reverse; +}; + +#define ESP_IMAGE_ELF_PHF_EXEC 0x1 + +/** + * Algorithm stub data. + */ +struct algorithm_stub { + /** Entry addr. */ + target_addr_t entry; + /** Working area for code segment. */ + struct working_area *code; + /** Working area for data segment. */ + struct working_area *data; + /** Working area for tramploline. */ + struct working_area *tramp; + /** Working area for padding between code and data area. */ + struct working_area *padding; + /** Address of the target buffer for stub trampoline. If zero tramp->address will be used. */ + target_addr_t tramp_addr; + /* Tramp code area will be filled from dbus. We need to map it to the ibus */ + target_addr_t tramp_mapped_addr; + /** Working area for stack. */ + struct working_area *stack; + /** Address of the target buffer for stack. If zero tramp->address will be used. */ + target_addr_t stack_addr; + /** Address of the log buffer */ + target_addr_t log_buff_addr; + /** Size of the log buffer */ + uint32_t log_buff_size; + /** Algorithm's arch-specific info. */ + void *ainfo; +}; + +/** + * Algorithm stub in-memory arguments. + */ +struct algorithm_mem_args { + /** Memory params. */ + struct mem_param *params; + /** Number of memory params. */ + uint32_t count; +}; + +/** + * Algorithm stub register arguments. + */ +struct algorithm_reg_args { + /** Algorithm register params. User args start from user_first_reg_param */ + struct reg_param *params; + /** Number of register params. */ + uint32_t count; + /** The first several reg_params can be used by stub itself (e.g. for trampoline). + * This is the index of the first reg_param available for user to pass args to algorithm stub. */ + uint32_t first_user_param; +}; + +struct algorithm_run_data; + +/** + * @brief Algorithm run function. + * + * @param target Pointer to target. + * @param run Pointer to algo run data. + * @param arg Function specific argument. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. + */ +typedef int (*algorithm_func_t)(struct target *target, struct algorithm_run_data *run, void *arg); + +/** + * @brief Host part of algorithm. + * This function will be called while stub is running on target. + * It can be used for communication with stub. + * + * @param target Pointer to target. + * @param usr_arg Function specific argument. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. + */ +typedef int (*algorithm_usr_func_t)(struct target *target, void *usr_arg); + +/** + * @brief Algorithm's arguments setup function. + * This function will be called just before stub start. + * It must return when all operations with running stub are completed. + * It can be used to prepare stub memory parameters. + * + * @param target Pointer to target. + * @param run Pointer to algo run data. + * @param usr_arg Function specific argument. The same as for algorithm_usr_func_t. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. + */ +typedef int (*algorithm_usr_func_init_t)(struct target *target, struct algorithm_run_data *run, void *usr_arg); + +/** + * @brief Algorithm's arguments cleanup function. + * This function will be called just after stub exit. + * It can be used to cleanup stub memory parameters. + * + * @param target Pointer to target. + * @param run Pointer to algo run data. + * @param usr_arg Function specific argument. The same as for algorithm_usr_func_t. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. + */ +typedef void (*algorithm_usr_func_done_t)(struct target *target, struct algorithm_run_data *run, void *usr_arg); + +struct algorithm_hw { + int (*algo_init)(struct target *target, struct algorithm_run_data *run, uint32_t num_args, va_list ap); + int (*algo_cleanup)(struct target *target, struct algorithm_run_data *run); + const uint8_t *(*stub_tramp_get)(struct target *target, size_t *size); +}; + +/** + * Algorithm run data. + */ +struct algorithm_run_data { + /** Algorithm completion timeout in ms. If 0, default value will be used */ + uint32_t tmo; + /** Algorithm stack size. */ + uint32_t stack_size; + /** Algorithm register arguments. */ + struct algorithm_reg_args reg_args; + /** Algorithm memory arguments. */ + struct algorithm_mem_args mem_args; + /** Algorithm arch-specific info. For Xtensa this should point to struct xtensa_algorithm. */ + void *arch_info; + /** Algorithm return code. */ + int32_t ret_code; + /** Stub. */ + struct algorithm_stub stub; + union { + struct { + /** Size of the pre-alocated on-board buffer for stub's code. */ + uint32_t code_buf_size; + /** Address of pre-compiled target buffer for stub trampoline. */ + target_addr_t code_buf_addr; + /** Size of the pre-alocated on-board buffer for stub's stack. */ + uint32_t min_stack_size; + /** Pre-compiled target buffer's addr for stack. */ + target_addr_t min_stack_addr; + /** Address of malloc-like function to allocate buffer on target. */ + target_addr_t alloc_func; + /** Address of free-like function to free buffer allocated with on_board_alloc_func. */ + target_addr_t free_func; + } on_board; + struct algorithm_image image; + }; + /** Host side algorithm function argument. */ + void *usr_func_arg; + /** Host side algorithm function. */ + algorithm_usr_func_t usr_func; + /** Host side algorithm function setup routine. */ + algorithm_usr_func_init_t usr_func_init; + /** Host side algorithm function cleanup routine. */ + algorithm_usr_func_done_t usr_func_done; + /** Algorithm run function: see algorithm_run_xxx for example. */ + algorithm_func_t algo_func; + /** HW specific API */ + const struct algorithm_hw *hw; +}; + +int algorithm_load_func_image(struct target *target, struct algorithm_run_data *run); +int algorithm_unload_func_image(struct target *target, struct algorithm_run_data *run); + +int algorithm_exec_func_image_va(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap); + +static inline int algorithm_exec_func_image(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + ...) +{ + va_list ap; + va_start(ap, num_args); + int retval = algorithm_exec_func_image_va(target, run, num_args, ap); + va_end(ap); + return retval; +} + +/** + * @brief Loads and runs stub from specified image. + * This function should be used to run external stub code on target. + * + * @param target Pointer to target. + * @param run Pointer to algo run data. + * @param num_args Number of stub arguments that follow. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. Stub return code is in run->ret_code. + */ +static inline int algorithm_run_func_image_va(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap) +{ + int ret = algorithm_load_func_image(target, run); + if (ret != ERROR_OK) + return ret; + ret = algorithm_exec_func_image_va(target, run, num_args, ap); + int rc = algorithm_unload_func_image(target, run); + return ret != ERROR_OK ? ret : rc; +} + +static inline int algorithm_run_func_image(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + ...) +{ + va_list ap; + va_start(ap, num_args); + int retval = algorithm_run_func_image_va(target, run, num_args, ap); + va_end(ap); + return retval; +} + +int algorithm_load_onboard_func(struct target *target, target_addr_t func_addr, struct algorithm_run_data *run); +int algorithm_unload_onboard_func(struct target *target, struct algorithm_run_data *run); +int algorithm_exec_onboard_func_va(struct target *target, + struct algorithm_run_data *run, + uint32_t num_args, + va_list ap); + +/** + * @brief Runs pre-compiled on-board function. + * This function should be used to run on-board stub code. + * + * @param target Pointer to target. + * @param run Pointer to algo run data. + * @param func_entry Address of the function to run. + * @param num_args Number of function arguments that follow. + * + * @return ERROR_OK on success, otherwise ERROR_XXX. Stub return code is in run->ret_code. + */ +static inline int algorithm_run_onboard_func_va(struct target *target, + struct algorithm_run_data *run, + target_addr_t func_addr, + uint32_t num_args, + va_list ap) +{ + int ret = algorithm_load_onboard_func(target, func_addr, run); + if (ret != ERROR_OK) + return ret; + ret = algorithm_exec_onboard_func_va(target, run, num_args, ap); + if (ret != ERROR_OK) + return ret; + return algorithm_unload_onboard_func(target, run); +} + +static inline int algorithm_run_onboard_func(struct target *target, + struct algorithm_run_data *run, + target_addr_t func_addr, + uint32_t num_args, + ...) +{ + va_list ap; + va_start(ap, num_args); + int retval = algorithm_run_onboard_func_va(target, run, func_addr, num_args, ap); + va_end(ap); + return retval; +} + +static inline void algorithm_user_arg_set_uint(struct algorithm_run_data *run, + int arg_num, + uint64_t val) +{ + struct reg_param *param = &run->reg_args.params[run->reg_args.first_user_param + arg_num]; + + assert(param->size <= 64); + + if (param->size <= 32) + buf_set_u32(param->value, 0, param->size, val); + else + buf_set_u64(param->value, 0, param->size, val); +} + +static inline uint64_t algorithm_user_arg_get_uint(struct algorithm_run_data *run, int arg_num) +{ + struct reg_param *param = &run->reg_args.params[run->reg_args.first_user_param + arg_num]; + + assert(param->size <= 64); + + if (param->size <= 32) + return buf_get_u32(param->value, 0, param->size); + return buf_get_u64(param->value, 0, param->size); +} + +#endif /* OPENOCD_TARGET_ESP_ALGORITHM_H */ diff --git a/src/target/espressif/esp_xtensa_smp.c b/src/target/espressif/esp_xtensa_smp.c index 1d70be9e3c..af06bd68d4 100644 --- a/src/target/espressif/esp_xtensa_smp.c +++ b/src/target/espressif/esp_xtensa_smp.c @@ -16,6 +16,7 @@ #include <target/semihosting_common.h> #include "esp_xtensa_smp.h" #include "esp_xtensa_semihosting.h" +#include "esp_algorithm.h" /* Multiprocessor stuff common: @@ -495,6 +496,83 @@ int esp_xtensa_smp_watchpoint_remove(struct target *target, struct watchpoint *w return ERROR_OK; } +int esp_xtensa_smp_run_func_image(struct target *target, struct algorithm_run_data *run, uint32_t num_args, ...) +{ + struct target *run_target = target; + struct target_list *head; + va_list ap; + uint32_t smp_break = 0; + int res; + + if (target->smp) { + /* find first HALTED and examined core */ + foreach_smp_target(head, target->smp_targets) { + run_target = head->target; + if (target_was_examined(run_target) && run_target->state == TARGET_HALTED) + break; + } + if (!head) { + LOG_ERROR("Failed to find HALTED core!"); + return ERROR_FAIL; + } + + res = esp_xtensa_smp_smpbreak_disable(run_target, &smp_break); + if (res != ERROR_OK) + return res; + } + + va_start(ap, num_args); + int algo_res = algorithm_run_func_image_va(run_target, run, num_args, ap); + va_end(ap); + + if (target->smp) { + res = esp_xtensa_smp_smpbreak_restore(run_target, smp_break); + if (res != ERROR_OK) + return res; + } + return algo_res; +} + +int esp_xtensa_smp_run_onboard_func(struct target *target, + struct algorithm_run_data *run, + uint32_t func_addr, + uint32_t num_args, + ...) +{ + struct target *run_target = target; + struct target_list *head; + va_list ap; + uint32_t smp_break = 0; + int res; + + if (target->smp) { + /* find first HALTED and examined core */ + foreach_smp_target(head, target->smp_targets) { + run_target = head->target; + if (target_was_examined(run_target) && run_target->state == TARGET_HALTED) + break; + } + if (!head) { + LOG_ERROR("Failed to find HALTED core!"); + return ERROR_FAIL; + } + res = esp_xtensa_smp_smpbreak_disable(run_target, &smp_break); + if (res != ERROR_OK) + return res; + } + + va_start(ap, num_args); + int algo_res = algorithm_run_onboard_func_va(run_target, run, func_addr, num_args, ap); + va_end(ap); + + if (target->smp) { + res = esp_xtensa_smp_smpbreak_restore(run_target, smp_break); + if (res != ERROR_OK) + return res; + } + return algo_res; +} + int esp_xtensa_smp_init_arch_info(struct target *target, struct esp_xtensa_smp_common *esp_xtensa_smp, struct xtensa_debug_module_config *dm_cfg, diff --git a/src/target/espressif/esp_xtensa_smp.h b/src/target/espressif/esp_xtensa_smp.h index 4e4f3b3321..6bfc713ffd 100644 --- a/src/target/espressif/esp_xtensa_smp.h +++ b/src/target/espressif/esp_xtensa_smp.h @@ -9,6 +9,7 @@ #define OPENOCD_TARGET_XTENSA_ESP_SMP_H #include "esp_xtensa.h" +#include "esp_algorithm.h" struct esp_xtensa_smp_chip_ops { int (*poll)(struct target *target); @@ -47,7 +48,12 @@ int esp_xtensa_smp_init_arch_info(struct target *target, struct xtensa_debug_module_config *dm_cfg, const struct esp_xtensa_smp_chip_ops *chip_ops, const struct esp_semihost_ops *semihost_ops); - +int esp_xtensa_smp_run_func_image(struct target *target, struct algorithm_run_data *run, uint32_t num_args, ...); +int esp_xtensa_smp_run_onboard_func(struct target *target, + struct algorithm_run_data *run, + uint32_t func_addr, + uint32_t num_args, + ...); extern const struct command_registration esp_xtensa_smp_command_handlers[]; extern const struct command_registration esp_xtensa_smp_xtensa_command_handlers[]; extern const struct command_registration esp_xtensa_smp_esp_command_handlers[]; --