FabGL
ESP32 Display Controller and Graphics Library
ulp_macro_ex.cpp
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 // This file is identical to IDF ulp_macro.c, except for redefinition of CONFIG_ULP_COPROC_RESERVE_MEM to allow
16 // larger ULP programs
17 
18 #include <stdio.h>
19 #include <string.h>
20 #include <stdlib.h>
21 
22 #include "esp_attr.h"
23 #include "esp_err.h"
24 #include "esp_log.h"
25 #include "esp32/ulp.h"
26 
27 #include "soc/soc.h"
28 #include "soc/rtc_cntl_reg.h"
29 #include "soc/sens_reg.h"
30 
31 #include "sdkconfig.h"
32 
33 #include "ulp_macro_ex.h"
34 
35 // fabgl =>
36 #undef CONFIG_ULP_COPROC_RESERVE_MEM
37 #define CONFIG_ULP_COPROC_RESERVE_MEM 2048
38 // <=
39 
40 static const char* TAG = "ulp";
41 
42 typedef struct {
43  uint32_t label : 16;
44  uint32_t addr : 11;
45  uint32_t unused : 1;
46  uint32_t type : 4;
47 } reloc_info_t;
48 
49 #define RELOC_TYPE_LABEL 0
50 #define RELOC_TYPE_BRANCH 1
51 
52 /* This record means: there is a label at address
53  * insn_addr, with number label_num.
54  */
55 #define RELOC_INFO_LABEL(label_num, insn_addr) (reloc_info_t) { \
56  .label = label_num, \
57  .addr = insn_addr, \
58  .unused = 0, \
59  .type = RELOC_TYPE_LABEL }
60 
61 /* This record means: there is a branch instruction at
62  * insn_addr, it needs to be changed to point to address
63  * of label label_num.
64  */
65 #define RELOC_INFO_BRANCH(label_num, insn_addr) (reloc_info_t) { \
66  .label = label_num, \
67  .addr = insn_addr, \
68  .unused = 0, \
69  .type = RELOC_TYPE_BRANCH }
70 
71 /* Comparison function used to sort the relocations array */
72 static int reloc_sort_func(const void* p_lhs, const void* p_rhs)
73 {
74  const reloc_info_t lhs = *(const reloc_info_t*) p_lhs;
75  const reloc_info_t rhs = *(const reloc_info_t*) p_rhs;
76  if (lhs.label < rhs.label) {
77  return -1;
78  } else if (lhs.label > rhs.label) {
79  return 1;
80  }
81  // label numbers are equal
82  if (lhs.type < rhs.type) {
83  return -1;
84  } else if (lhs.type > rhs.type) {
85  return 1;
86  }
87 
88  // both label number and type are equal
89  return 0;
90 }
91 
92 
93 /* Processing branch and label macros involves four steps:
94  *
95  * 1. Iterate over program and count all instructions
96  * with "macro" opcode. Allocate relocations array
97  * with number of entries equal to number of macro
98  * instructions.
99  *
100  * 2. Remove all fake instructions with "macro" opcode
101  * and record their locations into relocations array.
102  * Removal is done using two pointers. Instructions
103  * are read from read_ptr, and written to write_ptr.
104  * When a macro instruction is encountered,
105  * its contents are recorded into the appropriate
106  * table, and then read_ptr is advanced again.
107  * When a real instruction is encountered, it is
108  * read via read_ptr and written to write_ptr.
109  * In the end, all macro instructions are removed,
110  * size of the program (expressed in words) is
111  * reduced by the total number of macro instructions
112  * which were present.
113  *
114  * 3. Sort relocations array by label number, and then
115  * by type ("label" or "branch") if label numbers
116  * match. This is done to simplify lookup on the next
117  * step.
118  *
119  * 4. Iterate over entries of relocations table.
120  * For each label number, label entry comes first
121  * because the array was sorted at the previous step.
122  * Label address is recorded, and all subsequent
123  * "branch" entries which point to the same label number
124  * are processed. For each branch entry, correct offset
125  * or absolute address is calculated, depending on branch
126  * type, and written into the appropriate field of
127  * the instruction.
128  *
129  */
130 
131 static esp_err_t do_single_reloc(ulp_insn_t* program, uint32_t load_addr,
132  reloc_info_t label_info, reloc_info_t branch_info)
133 {
134  size_t insn_offset = branch_info.addr - load_addr;
135  ulp_insn_t* insn = &program[insn_offset];
136  // B and BX have the same layout of opcode/sub_opcode fields,
137  // and share the same opcode
138  assert(insn->b.opcode == OPCODE_BRANCH
139  && "branch macro was applied to a non-branch instruction");
140  switch (insn->b.sub_opcode) {
141  case SUB_OPCODE_STAGEB:
142  case SUB_OPCODE_B: {
143  int32_t offset = ((int32_t) label_info.addr) - ((int32_t) branch_info.addr);
144 ESP_LOGW(TAG, "%d\n", offset);
145  uint32_t abs_offset = abs(offset);
146  uint32_t sign = (offset >= 0) ? 0 : 1;
147  if (abs_offset > 127) {
148  ESP_LOGE(TAG, "target out of range: branch from %x to %x",
149  branch_info.addr, label_info.addr);
150  return ESP_ERR_ULP_BRANCH_OUT_OF_RANGE;
151  }
152  insn->b.offset = abs_offset;
153  insn->b.sign = sign;
154  break;
155  }
156  case SUB_OPCODE_BX: {
157  assert(insn->bx.reg == 0 &&
158  "relocation applied to a jump with offset in register");
159  insn->bx.addr = label_info.addr;
160  break;
161  }
162  default:
163  assert(false && "unexpected sub-opcode");
164  }
165  return ESP_OK;
166 }
167 
168 esp_err_t ulp_process_macros_and_load_ex(uint32_t load_addr, const ulp_insn_t* program, size_t* psize)
169 {
170  const ulp_insn_t* read_ptr = program;
171  const ulp_insn_t* end = program + *psize;
172  size_t macro_count = 0;
173  // step 1: calculate number of macros
174  while (read_ptr < end) {
175  ulp_insn_t r_insn = *read_ptr;
176  if (r_insn.macro.opcode == OPCODE_MACRO) {
177  ++macro_count;
178  }
179  ++read_ptr;
180  }
181  size_t real_program_size = *psize - macro_count;
182  const size_t ulp_mem_end = CONFIG_ULP_COPROC_RESERVE_MEM / sizeof(ulp_insn_t);
183  if (load_addr > ulp_mem_end) {
184  ESP_LOGE(TAG, "invalid load address %x, max is %x",
185  load_addr, ulp_mem_end);
186  return ESP_ERR_ULP_INVALID_LOAD_ADDR;
187  }
188  if (real_program_size + load_addr > ulp_mem_end) {
189  ESP_LOGE(TAG, "program too big: %d words, max is %d words",
190  real_program_size, ulp_mem_end);
191  return ESP_ERR_ULP_SIZE_TOO_BIG;
192  }
193  // If no macros found, copy the program and return.
194  if (macro_count == 0) {
195  memcpy(((ulp_insn_t*) RTC_SLOW_MEM) + load_addr, program, *psize * sizeof(ulp_insn_t));
196  return ESP_OK;
197  }
198  reloc_info_t* reloc_info =
199  (reloc_info_t*) malloc(sizeof(reloc_info_t) * macro_count);
200  if (reloc_info == nullptr) {
201  return ESP_ERR_NO_MEM;
202  }
203 
204  // step 2: record macros into reloc_info array
205  // and remove them from then program
206  read_ptr = program;
207  ulp_insn_t* output_program = ((ulp_insn_t*) RTC_SLOW_MEM) + load_addr;
208  ulp_insn_t* write_ptr = output_program;
209  uint32_t cur_insn_addr = load_addr;
210  reloc_info_t* cur_reloc = reloc_info;
211  while (read_ptr < end) {
212  ulp_insn_t r_insn = *read_ptr;
213  if (r_insn.macro.opcode == OPCODE_MACRO) {
214  switch(r_insn.macro.sub_opcode) {
215  case SUB_OPCODE_MACRO_LABEL:
216  *cur_reloc = RELOC_INFO_LABEL(r_insn.macro.label,
217  cur_insn_addr);
218  break;
219  case SUB_OPCODE_MACRO_BRANCH:
220  *cur_reloc = RELOC_INFO_BRANCH(r_insn.macro.label,
221  cur_insn_addr);
222  break;
223  default:
224  assert(0 && "invalid sub_opcode for macro insn");
225  }
226  ++read_ptr;
227  assert(read_ptr != end && "program can not end with macro insn");
228  ++cur_reloc;
229  } else {
230  // normal instruction (not a macro)
231  *write_ptr = *read_ptr;
232  ++read_ptr;
233  ++write_ptr;
234  ++cur_insn_addr;
235  }
236  }
237 
238  // step 3: sort relocations array
239  qsort(reloc_info, macro_count, sizeof(reloc_info_t),
240  reloc_sort_func);
241 
242  // step 4: walk relocations array and fix instructions
243  reloc_info_t* reloc_end = reloc_info + macro_count;
244  cur_reloc = reloc_info;
245  while(cur_reloc < reloc_end) {
246  reloc_info_t label_info = *cur_reloc;
247  assert(label_info.type == RELOC_TYPE_LABEL);
248  ++cur_reloc;
249  while (cur_reloc < reloc_end) {
250  if (cur_reloc->type == RELOC_TYPE_LABEL) {
251  if(cur_reloc->label == label_info.label) {
252  ESP_LOGE(TAG, "duplicate label definition: %d",
253  label_info.label);
254  free(reloc_info);
255  return ESP_ERR_ULP_DUPLICATE_LABEL;
256  }
257  break;
258  }
259  if (cur_reloc->label != label_info.label) {
260  ESP_LOGE(TAG, "branch to an inexistent label: %d",
261  cur_reloc->label);
262  free(reloc_info);
263  return ESP_ERR_ULP_UNDEFINED_LABEL;
264  }
265  esp_err_t rc = do_single_reloc(output_program, load_addr,
266  label_info, *cur_reloc);
267  if (rc != ESP_OK) {
268  free(reloc_info);
269  return rc;
270  }
271  ++cur_reloc;
272  }
273  }
274  free(reloc_info);
275  *psize = real_program_size;
276  return ESP_OK;
277 }