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