1 // Copyright 2010-2016 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <stdio.h>
16 #include <string.h>
17 #include <stdlib.h>
18 
19 #include "esp_attr.h"
20 #include "esp_err.h"
21 #include "esp_log.h"
22 #include "esp32/ulp.h"
23 #include "ulp_private.h"
24 
25 #include "soc/soc.h"
26 #include "soc/rtc_cntl_reg.h"
27 #include "soc/sens_reg.h"
28 
29 #include "sdkconfig.h"
30 
31 static const char* TAG = "ulp";
32 
33 typedef struct {
34     uint32_t label : 16;
35     uint32_t addr : 11;
36     uint32_t unused : 1;
37     uint32_t type : 4;
38 } reloc_info_t;
39 
40 #define RELOC_TYPE_LABEL   0
41 #define RELOC_TYPE_BRANCH  1
42 #define RELOC_TYPE_LABELPC 2
43 
44 /* This record means: there is a label at address
45  * insn_addr, with number label_num.
46  */
47 #define RELOC_INFO_LABEL(label_num, insn_addr) (reloc_info_t) { \
48     .label = label_num, \
49     .addr = insn_addr, \
50     .unused = 0, \
51     .type = RELOC_TYPE_LABEL }
52 
53 /* This record means: there is a branch instruction at
54  * insn_addr, it needs to be changed to point to address
55  * of label label_num.
56  */
57 #define RELOC_INFO_BRANCH(label_num, insn_addr) (reloc_info_t) { \
58     .label = label_num, \
59     .addr = insn_addr, \
60     .unused = 0, \
61     .type = RELOC_TYPE_BRANCH }
62 
63 /* This record means: there is a move instruction at insn_addr,
64  * imm needs to be changed to the program counter of the instruction
65  * at label label_num.
66  */
67 #define RELOC_INFO_LABELPC(label_num, insn_addr) (reloc_info_t) { \
68     .label = label_num, \
69     .addr = insn_addr, \
70     .unused = 0, \
71     .type = RELOC_TYPE_LABELPC }
72 
73 /* Comparison function used to sort the relocations array */
reloc_sort_func(const void * p_lhs,const void * p_rhs)74 static int reloc_sort_func(const void* p_lhs, const void* p_rhs)
75 {
76     const reloc_info_t lhs = *(const reloc_info_t*) p_lhs;
77     const reloc_info_t rhs = *(const reloc_info_t*) p_rhs;
78     if (lhs.label < rhs.label) {
79         return -1;
80     } else if (lhs.label > rhs.label) {
81         return 1;
82     }
83     // label numbers are equal
84     if (lhs.type < rhs.type) {
85         return -1;
86     } else if (lhs.type > rhs.type) {
87         return 1;
88     }
89 
90     // both label number and type are equal
91     return 0;
92 }
93 
94 
95 /* Processing branch and label macros involves four steps:
96  *
97  * 1. Iterate over program and count all instructions
98  *    with "macro" opcode. Allocate relocations array
99  *    with number of entries equal to number of macro
100  *    instructions.
101  *
102  * 2. Remove all fake instructions with "macro" opcode
103  *    and record their locations into relocations array.
104  *    Removal is done using two pointers. Instructions
105  *    are read from read_ptr, and written to write_ptr.
106  *    When a macro instruction is encountered,
107  *    its contents are recorded into the appropriate
108  *    table, and then read_ptr is advanced again.
109  *    When a real instruction is encountered, it is
110  *    read via read_ptr and written to write_ptr.
111  *    In the end, all macro instructions are removed,
112  *    size of the program (expressed in words) is
113  *    reduced by the total number of macro instructions
114  *    which were present.
115  *
116  * 3. Sort relocations array by label number, and then
117  *    by type ("label" or "branch") if label numbers
118  *    match. This is done to simplify lookup on the next
119  *    step.
120  *
121  * 4. Iterate over entries of relocations table.
122  *    For each label number, label entry comes first
123  *    because the array was sorted at the previous step.
124  *    Label address is recorded, and all subsequent
125  *    entries which point to the same label number
126  *    are processed. For each entry, correct offset
127  *    or absolute address is calculated, depending on
128  *    type and subtype, and written into the appropriate
129  *    field of the instruction.
130  *
131  */
132 
do_single_reloc(ulp_insn_t * program,uint32_t load_addr,reloc_info_t label_info,reloc_info_t the_reloc)133 static esp_err_t do_single_reloc(ulp_insn_t* program, uint32_t load_addr,
134         reloc_info_t label_info, reloc_info_t the_reloc)
135 {
136     size_t insn_offset = the_reloc.addr - load_addr;
137     ulp_insn_t* insn = &program[insn_offset];
138 
139     switch (the_reloc.type) {
140         case RELOC_TYPE_BRANCH: {
141             // B, BS and BX have the same layout of opcode/sub_opcode fields,
142             // and share the same opcode. B and BS also have the same layout of
143             // offset and sign fields.
144             assert(insn->b.opcode == OPCODE_BRANCH
145                     && "branch macro was applied to a non-branch instruction");
146             switch (insn->b.sub_opcode) {
147                 case SUB_OPCODE_B:
148                 case SUB_OPCODE_BS:{
149                     int32_t offset = ((int32_t) label_info.addr) - ((int32_t) the_reloc.addr);
150                     uint32_t abs_offset = abs(offset);
151                     uint32_t sign = (offset >= 0) ? 0 : 1;
152                     if (abs_offset > 127) {
153                         ESP_LOGW(TAG, "target out of range: branch from %x to %x",
154                                 the_reloc.addr, label_info.addr);
155                         return ESP_ERR_ULP_BRANCH_OUT_OF_RANGE;
156                     }
157                     insn->b.offset = abs_offset; //== insn->bs.offset = abs_offset;
158                     insn->b.sign = sign;         //== insn->bs.sign = sign;
159                     break;
160                 }
161                 case SUB_OPCODE_BX:{
162                     assert(insn->bx.reg == 0 &&
163                             "relocation applied to a jump with offset in register");
164                     insn->bx.addr = label_info.addr;
165                     break;
166                 }
167                 default:
168                     assert(false && "unexpected branch sub-opcode");
169             }
170             break;
171         }
172         case RELOC_TYPE_LABELPC: {
173             assert((insn->alu_imm.opcode == OPCODE_ALU && insn->alu_imm.sub_opcode == SUB_OPCODE_ALU_IMM && insn->alu_imm.sel == ALU_SEL_MOV)
174                         && "pc macro was applied to an incompatible instruction");
175             insn->alu_imm.imm = label_info.addr;
176             break;
177         }
178         default:
179             assert(false && "unknown reloc type");
180     }
181     return ESP_OK;
182 }
183 
ulp_process_macros_and_load(uint32_t load_addr,const ulp_insn_t * program,size_t * psize)184 esp_err_t ulp_process_macros_and_load(uint32_t load_addr, const ulp_insn_t* program, size_t* psize)
185 {
186     const ulp_insn_t* read_ptr = program;
187     const ulp_insn_t* end = program + *psize;
188     size_t macro_count = 0;
189     // step 1: calculate number of macros
190     while (read_ptr < end) {
191         ulp_insn_t r_insn = *read_ptr;
192         if (r_insn.macro.opcode == OPCODE_MACRO) {
193             ++macro_count;
194         }
195         ++read_ptr;
196     }
197     size_t real_program_size = *psize - macro_count;
198     const size_t ulp_mem_end = ULP_RESERVE_MEM / sizeof(ulp_insn_t);
199     if (load_addr > ulp_mem_end) {
200         ESP_LOGW(TAG, "invalid load address %x, max is %x",
201                 load_addr, ulp_mem_end);
202         return ESP_ERR_ULP_INVALID_LOAD_ADDR;
203     }
204     if (real_program_size + load_addr > ulp_mem_end) {
205         ESP_LOGE(TAG, "program too big: %d words, max is %d words",
206                 real_program_size, ulp_mem_end);
207         return ESP_ERR_ULP_SIZE_TOO_BIG;
208     }
209     // If no macros found, copy the program and return.
210     if (macro_count == 0) {
211         memcpy(((ulp_insn_t*) RTC_SLOW_MEM) + load_addr, program, *psize * sizeof(ulp_insn_t));
212         return ESP_OK;
213     }
214     reloc_info_t* reloc_info =
215             (reloc_info_t*) malloc(sizeof(reloc_info_t) * macro_count);
216     if (reloc_info == NULL) {
217         return ESP_ERR_NO_MEM;
218     }
219 
220     // step 2: record macros into reloc_info array
221     // and remove them from then program
222     read_ptr = program;
223     ulp_insn_t* output_program = ((ulp_insn_t*) RTC_SLOW_MEM) + load_addr;
224     ulp_insn_t* write_ptr = output_program;
225     uint32_t cur_insn_addr = load_addr;
226     reloc_info_t* cur_reloc = reloc_info;
227     while (read_ptr < end) {
228         ulp_insn_t r_insn = *read_ptr;
229         if (r_insn.macro.opcode == OPCODE_MACRO) {
230             switch (r_insn.macro.sub_opcode) {
231                 case SUB_OPCODE_MACRO_LABEL:
232                     *cur_reloc = RELOC_INFO_LABEL(r_insn.macro.label,
233                             cur_insn_addr);
234                     break;
235                 case SUB_OPCODE_MACRO_BRANCH:
236                     *cur_reloc = RELOC_INFO_BRANCH(r_insn.macro.label,
237                             cur_insn_addr);
238                     break;
239                 case SUB_OPCODE_MACRO_LABELPC:
240                     *cur_reloc = RELOC_INFO_LABELPC(r_insn.macro.label,
241                             cur_insn_addr);
242                     break;
243                 default:
244                     assert(0 && "invalid sub_opcode for macro insn");
245             }
246             ++read_ptr;
247             assert(read_ptr != end && "program can not end with macro insn");
248             ++cur_reloc;
249         } else {
250             // normal instruction (not a macro)
251             *write_ptr = *read_ptr;
252             ++read_ptr;
253             ++write_ptr;
254             ++cur_insn_addr;
255         }
256     }
257 
258     // step 3: sort relocations array
259     qsort(reloc_info, macro_count, sizeof(reloc_info_t),
260             reloc_sort_func);
261 
262     // step 4: walk relocations array and fix instructions
263     reloc_info_t* reloc_end = reloc_info + macro_count;
264     cur_reloc = reloc_info;
265     while(cur_reloc < reloc_end) {
266         reloc_info_t label_info = *cur_reloc;
267         assert(label_info.type == RELOC_TYPE_LABEL);
268         ++cur_reloc;
269         while (cur_reloc < reloc_end) {
270             if (cur_reloc->type == RELOC_TYPE_LABEL) {
271                 if(cur_reloc->label == label_info.label) {
272                     ESP_LOGE(TAG, "duplicate label definition: %d",
273                             label_info.label);
274                     free(reloc_info);
275                     return ESP_ERR_ULP_DUPLICATE_LABEL;
276                 }
277                 break;
278             }
279             if (cur_reloc->label != label_info.label) {
280                 ESP_LOGE(TAG, "branch to an inexistent label: %d",
281                         cur_reloc->label);
282                 free(reloc_info);
283                 return ESP_ERR_ULP_UNDEFINED_LABEL;
284             }
285             esp_err_t rc = do_single_reloc(output_program, load_addr,
286                     label_info, *cur_reloc);
287             if (rc != ESP_OK) {
288                 free(reloc_info);
289                 return rc;
290             }
291             ++cur_reloc;
292         }
293     }
294     free(reloc_info);
295     *psize = real_program_size;
296     return ESP_OK;
297 }
298