mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-09 20:41:14 +00:00
ulp: add build system integration and example
This commit is contained in:

committed by
Ivan Grokhotkov

parent
573cc7d36f
commit
a6e4e89592
@@ -27,239 +27,17 @@
|
||||
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char* TAG = "ulp";
|
||||
|
||||
typedef struct {
|
||||
uint32_t label : 16;
|
||||
uint32_t addr : 11;
|
||||
uint32_t unused : 1;
|
||||
uint32_t type : 4;
|
||||
} reloc_info_t;
|
||||
uint32_t magic;
|
||||
uint16_t text_offset;
|
||||
uint16_t text_size;
|
||||
uint16_t data_size;
|
||||
uint16_t bss_size;
|
||||
} ulp_binary_header_t;
|
||||
|
||||
#define RELOC_TYPE_LABEL 0
|
||||
#define RELOC_TYPE_BRANCH 1
|
||||
#define ULP_BINARY_MAGIC_ESP32 (0x00706c75)
|
||||
|
||||
/* This record means: there is a label at address
|
||||
* insn_addr, with number label_num.
|
||||
*/
|
||||
#define RELOC_INFO_LABEL(label_num, insn_addr) (reloc_info_t) { \
|
||||
.label = label_num, \
|
||||
.addr = insn_addr, \
|
||||
.unused = 0, \
|
||||
.type = RELOC_TYPE_LABEL }
|
||||
|
||||
/* This record means: there is a branch instruction at
|
||||
* insn_addr, it needs to be changed to point to address
|
||||
* of label label_num.
|
||||
*/
|
||||
#define RELOC_INFO_BRANCH(label_num, insn_addr) (reloc_info_t) { \
|
||||
.label = label_num, \
|
||||
.addr = insn_addr, \
|
||||
.unused = 0, \
|
||||
.type = RELOC_TYPE_BRANCH }
|
||||
|
||||
|
||||
/* Processing branch and label macros involves four steps:
|
||||
*
|
||||
* 1. Iterate over program and count all instructions
|
||||
* with "macro" opcode. Allocate relocations array
|
||||
* with number of entries equal to number of macro
|
||||
* instructions.
|
||||
*
|
||||
* 2. Remove all fake instructions with "macro" opcode
|
||||
* and record their locations into relocations array.
|
||||
* Removal is done using two pointers. Instructions
|
||||
* are read from read_ptr, and written to write_ptr.
|
||||
* When a macro instruction is encountered,
|
||||
* its contents are recorded into the appropriate
|
||||
* table, and then read_ptr is advanced again.
|
||||
* When a real instruction is encountered, it is
|
||||
* read via read_ptr and written to write_ptr.
|
||||
* In the end, all macro instructions are removed,
|
||||
* size of the program (expressed in words) is
|
||||
* reduced by the total number of macro instructions
|
||||
* which were present.
|
||||
*
|
||||
* 3. Sort relocations array by label number, and then
|
||||
* by type ("label" or "branch") if label numbers
|
||||
* match. This is done to simplify lookup on the next
|
||||
* step.
|
||||
*
|
||||
* 4. Iterate over entries of relocations table.
|
||||
* For each label number, label entry comes first
|
||||
* because the array was sorted at the previous step.
|
||||
* Label address is recorded, and all subsequent
|
||||
* "branch" entries which point to the same label number
|
||||
* are processed. For each branch entry, correct offset
|
||||
* or absolute address is calculated, depending on branch
|
||||
* type, and written into the appropriate field of
|
||||
* the instruction.
|
||||
*
|
||||
*/
|
||||
|
||||
static esp_err_t do_single_reloc(ulp_insn_t* program, uint32_t load_addr,
|
||||
reloc_info_t label_info, reloc_info_t branch_info)
|
||||
{
|
||||
size_t insn_offset = branch_info.addr - load_addr;
|
||||
ulp_insn_t* insn = &program[insn_offset];
|
||||
// B and BX have the same layout of opcode/sub_opcode fields,
|
||||
// and share the same opcode
|
||||
assert(insn->b.opcode == OPCODE_BRANCH
|
||||
&& "branch macro was applied to a non-branch instruction");
|
||||
switch (insn->b.sub_opcode) {
|
||||
case SUB_OPCODE_B: {
|
||||
int32_t offset = ((int32_t) label_info.addr) - ((int32_t) branch_info.addr);
|
||||
uint32_t abs_offset = abs(offset);
|
||||
uint32_t sign = (offset >= 0) ? 0 : 1;
|
||||
if (abs_offset > 127) {
|
||||
ESP_LOGW(TAG, "target out of range: branch from %x to %x",
|
||||
branch_info.addr, label_info.addr);
|
||||
return ESP_ERR_ULP_BRANCH_OUT_OF_RANGE;
|
||||
}
|
||||
insn->b.offset = abs_offset;
|
||||
insn->b.sign = sign;
|
||||
break;
|
||||
}
|
||||
case SUB_OPCODE_BX: {
|
||||
assert(insn->bx.reg == 0 &&
|
||||
"relocation applied to a jump with offset in register");
|
||||
insn->bx.addr = label_info.addr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(false && "unexpected sub-opcode");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_process_macros_and_load(uint32_t load_addr, const ulp_insn_t* program, size_t* psize)
|
||||
{
|
||||
const ulp_insn_t* read_ptr = program;
|
||||
const ulp_insn_t* end = program + *psize;
|
||||
size_t macro_count = 0;
|
||||
// step 1: calculate number of macros
|
||||
while (read_ptr < end) {
|
||||
ulp_insn_t r_insn = *read_ptr;
|
||||
if (r_insn.macro.opcode == OPCODE_MACRO) {
|
||||
++macro_count;
|
||||
}
|
||||
++read_ptr;
|
||||
}
|
||||
size_t real_program_size = *psize - macro_count;
|
||||
const size_t ulp_mem_end = CONFIG_ULP_COPROC_RESERVE_MEM / sizeof(ulp_insn_t);
|
||||
if (load_addr > ulp_mem_end) {
|
||||
ESP_LOGW(TAG, "invalid load address %x, max is %x",
|
||||
load_addr, ulp_mem_end);
|
||||
return ESP_ERR_ULP_INVALID_LOAD_ADDR;
|
||||
}
|
||||
if (real_program_size + load_addr > ulp_mem_end) {
|
||||
ESP_LOGE(TAG, "program too big: %d words, max is %d words",
|
||||
real_program_size, ulp_mem_end);
|
||||
return ESP_ERR_ULP_SIZE_TOO_BIG;
|
||||
}
|
||||
// If no macros found, copy the program and return.
|
||||
if (macro_count == 0) {
|
||||
memcpy(((ulp_insn_t*) RTC_SLOW_MEM) + load_addr, program, *psize * sizeof(ulp_insn_t));
|
||||
return ESP_OK;
|
||||
}
|
||||
reloc_info_t* reloc_info =
|
||||
(reloc_info_t*) malloc(sizeof(reloc_info_t) * macro_count);
|
||||
if (reloc_info == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// step 2: record macros into reloc_info array
|
||||
// and remove them from then program
|
||||
read_ptr = program;
|
||||
ulp_insn_t* output_program = ((ulp_insn_t*) RTC_SLOW_MEM) + load_addr;
|
||||
ulp_insn_t* write_ptr = output_program;
|
||||
uint32_t cur_insn_addr = load_addr;
|
||||
reloc_info_t* cur_reloc = reloc_info;
|
||||
while (read_ptr < end) {
|
||||
ulp_insn_t r_insn = *read_ptr;
|
||||
if (r_insn.macro.opcode == OPCODE_MACRO) {
|
||||
switch(r_insn.macro.sub_opcode) {
|
||||
case SUB_OPCODE_MACRO_LABEL:
|
||||
*cur_reloc = RELOC_INFO_LABEL(r_insn.macro.label,
|
||||
cur_insn_addr);
|
||||
break;
|
||||
case SUB_OPCODE_MACRO_BRANCH:
|
||||
*cur_reloc = RELOC_INFO_BRANCH(r_insn.macro.label,
|
||||
cur_insn_addr);
|
||||
break;
|
||||
default:
|
||||
assert(0 && "invalid sub_opcode for macro insn");
|
||||
}
|
||||
++read_ptr;
|
||||
assert(read_ptr != end && "program can not end with macro insn");
|
||||
++cur_reloc;
|
||||
} else {
|
||||
// normal instruction (not a macro)
|
||||
*write_ptr = *read_ptr;
|
||||
++read_ptr;
|
||||
++write_ptr;
|
||||
++cur_insn_addr;
|
||||
}
|
||||
}
|
||||
|
||||
// step 3: sort relocations array
|
||||
int reloc_sort_func(const void* p_lhs, const void* p_rhs) {
|
||||
const reloc_info_t lhs = *(const reloc_info_t*) p_lhs;
|
||||
const reloc_info_t rhs = *(const reloc_info_t*) p_rhs;
|
||||
if (lhs.label < rhs.label) {
|
||||
return -1;
|
||||
} else if (lhs.label > rhs.label) {
|
||||
return 1;
|
||||
}
|
||||
// label numbers are equal
|
||||
if (lhs.type < rhs.type) {
|
||||
return -1;
|
||||
} else if (lhs.type > rhs.type) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// both label number and type are equal
|
||||
return 0;
|
||||
}
|
||||
qsort(reloc_info, macro_count, sizeof(reloc_info_t),
|
||||
reloc_sort_func);
|
||||
|
||||
// step 4: walk relocations array and fix instructions
|
||||
reloc_info_t* reloc_end = reloc_info + macro_count;
|
||||
cur_reloc = reloc_info;
|
||||
while(cur_reloc < reloc_end) {
|
||||
reloc_info_t label_info = *cur_reloc;
|
||||
assert(label_info.type == RELOC_TYPE_LABEL);
|
||||
++cur_reloc;
|
||||
while (cur_reloc < reloc_end) {
|
||||
if (cur_reloc->type == RELOC_TYPE_LABEL) {
|
||||
if(cur_reloc->label == label_info.label) {
|
||||
ESP_LOGE(TAG, "duplicate label definition: %d",
|
||||
label_info.label);
|
||||
free(reloc_info);
|
||||
return ESP_ERR_ULP_DUPLICATE_LABEL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cur_reloc->label != label_info.label) {
|
||||
ESP_LOGE(TAG, "branch to an inexistent label: %d",
|
||||
cur_reloc->label);
|
||||
free(reloc_info);
|
||||
return ESP_ERR_ULP_UNDEFINED_LABEL;
|
||||
}
|
||||
esp_err_t rc = do_single_reloc(output_program, load_addr,
|
||||
label_info, *cur_reloc);
|
||||
if (rc != ESP_OK) {
|
||||
free(reloc_info);
|
||||
return rc;
|
||||
}
|
||||
++cur_reloc;
|
||||
}
|
||||
}
|
||||
free(reloc_info);
|
||||
*psize = real_program_size;
|
||||
return ESP_OK;
|
||||
}
|
||||
static const char* TAG = "ulp";
|
||||
|
||||
esp_err_t ulp_run(uint32_t entry_point)
|
||||
{
|
||||
@@ -279,3 +57,46 @@ esp_err_t ulp_run(uint32_t entry_point)
|
||||
SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ulp_load_binary(uint32_t load_addr, const uint8_t* program_binary, size_t program_size)
|
||||
{
|
||||
size_t program_size_bytes = program_size * sizeof(uint32_t);
|
||||
size_t load_addr_bytes = load_addr * sizeof(uint32_t);
|
||||
|
||||
if (program_size_bytes < sizeof(ulp_binary_header_t)) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
if (load_addr_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
if (load_addr_bytes + program_size_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
// Make a copy of a header in case program_binary isn't aligned
|
||||
ulp_binary_header_t header;
|
||||
memcpy(&header, program_binary, sizeof(header));
|
||||
|
||||
if (header.magic != ULP_BINARY_MAGIC_ESP32) {
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
size_t total_size = (size_t) header.text_offset + (size_t) header.text_size +
|
||||
(size_t) header.data_size;
|
||||
|
||||
ESP_LOGD(TAG, "program_size_bytes: %d total_size: %d offset: %d .text: %d, .data: %d, .bss: %d",
|
||||
program_size_bytes, total_size, header.text_offset,
|
||||
header.text_size, header.data_size, header.bss_size);
|
||||
|
||||
if (total_size != program_size_bytes) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
size_t text_data_size = header.text_size + header.data_size;
|
||||
uint8_t* base = (uint8_t*) RTC_SLOW_MEM;
|
||||
|
||||
memcpy(base + load_addr_bytes, program_binary + header.text_offset, text_data_size);
|
||||
memset(base + load_addr_bytes + text_data_size, 0, header.bss_size);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
Reference in New Issue
Block a user