/* * SPDX-FileCopyrightText: 2010-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include "esp_attr.h" #include "esp_err.h" #include "esp_log.h" #include "ulp.h" #include "ulp_common.h" #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #include "soc/sens_reg.h" #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; #define RELOC_TYPE_LABEL 0 #define RELOC_TYPE_BRANCH 1 #define RELOC_TYPE_LABELPC 2 /* 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 } /* This record means: there is a move instruction at insn_addr, * imm needs to be changed to the program counter of the instruction * at label label_num. */ #define RELOC_INFO_LABELPC(label_num, insn_addr) (reloc_info_t) { \ .label = label_num, \ .addr = insn_addr, \ .unused = 0, \ .type = RELOC_TYPE_LABELPC } /* Comparison function used to sort the relocations array */ static 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; } /* 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 * entries which point to the same label number * are processed. For each entry, correct offset * or absolute address is calculated, depending on * type and subtype, 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 the_reloc) { size_t insn_offset = the_reloc.addr - load_addr; ulp_insn_t* insn = &program[insn_offset]; switch (the_reloc.type) { case RELOC_TYPE_BRANCH: { // B, BS and BX have the same layout of opcode/sub_opcode fields, // and share the same opcode. B and BS also have the same layout of // offset and sign fields. assert(insn->b.opcode == OPCODE_BRANCH && "branch macro was applied to a non-branch instruction"); switch (insn->b.sub_opcode) { case SUB_OPCODE_B: case SUB_OPCODE_BS: { int32_t offset = ((int32_t) label_info.addr) - ((int32_t) the_reloc.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", the_reloc.addr, label_info.addr); return ESP_ERR_ULP_BRANCH_OUT_OF_RANGE; } insn->b.offset = abs_offset; //== insn->bs.offset = abs_offset; insn->b.sign = sign; //== insn->bs.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 branch sub-opcode"); } break; } case RELOC_TYPE_LABELPC: { assert((insn->alu_imm.opcode == OPCODE_ALU && insn->alu_imm.sub_opcode == SUB_OPCODE_ALU_IMM && insn->alu_imm.sel == ALU_SEL_MOV) && "pc macro was applied to an incompatible instruction"); insn->alu_imm.imm = label_info.addr; break; } default: assert(false && "unknown reloc type"); } 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 %"PRIx32", 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; case SUB_OPCODE_MACRO_LABELPC: *cur_reloc = RELOC_INFO_LABELPC(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 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; }