1 // Copyright 2010-2017 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 /*
16 * DPORT access is used for do protection when dual core access DPORT internal register and APB register via DPORT simultaneously
17 * This function will be initialize after FreeRTOS startup.
18 * When cpu0 want to access DPORT register, it should notify cpu1 enter in high-priority interrupt for be mute. When cpu1 already in high-priority interrupt,
19 * cpu0 can access DPORT register. Currently, cpu1 will wait for cpu0 finish access and exit high-priority interrupt.
20 */
21
22 #include <stdint.h>
23 #include <string.h>
24
25 #include "esp_attr.h"
26 #include "esp_err.h"
27 #include "esp_intr_alloc.h"
28
29 #include "soc/cpu.h"
30 #include "soc/dport_reg.h"
31 #include "soc/spi_periph.h"
32 #include "hal/cpu_hal.h"
33
34 #include "esp_osal/esp_osal.h"
35 #include "esp_osal/task.h"
36 #include "esp_osal/semphr.h"
37 #include "esp_osal/queue.h"
38
39 #include "sdkconfig.h"
40
41 #ifndef CONFIG_FREERTOS_UNICORE
42 static portMUX_TYPE g_dport_mux = portMUX_INITIALIZER_UNLOCKED;
43
44 #define DPORT_CORE_STATE_IDLE 0
45 #define DPORT_CORE_STATE_RUNNING 1
46 static uint32_t volatile dport_core_state[portNUM_PROCESSORS]; //cpu is already run
47
48 /* these global variables are accessed from interrupt vector, hence not declared as static */
49 uint32_t volatile dport_access_start[portNUM_PROCESSORS]; //dport register could be accessed
50 uint32_t volatile dport_access_end[portNUM_PROCESSORS]; //dport register is accessed over
51
52 static uint32_t volatile dport_access_ref[portNUM_PROCESSORS]; //dport access reference
53
54 #ifdef DPORT_ACCESS_BENCHMARK
55 #define DPORT_ACCESS_BENCHMARK_STORE_NUM
56 static uint32_t ccount_start[portNUM_PROCESSORS];
57 static uint32_t ccount_end[portNUM_PROCESSORS];
58 static uint32_t ccount_margin[portNUM_PROCESSORS][DPORT_ACCESS_BENCHMARK_STORE_NUM];
59 static uint32_t ccount_margin_cnt;
60 #endif
61
62
63 static BaseType_t oldInterruptLevel[2];
64 #endif // CONFIG_FREERTOS_UNICORE
65
66 /* stall other cpu that this cpu is pending to access dport register start */
esp_dport_access_stall_other_cpu_start(void)67 void IRAM_ATTR esp_dport_access_stall_other_cpu_start(void)
68 {
69 #ifndef CONFIG_FREERTOS_UNICORE
70 if (dport_core_state[0] == DPORT_CORE_STATE_IDLE
71 || dport_core_state[1] == DPORT_CORE_STATE_IDLE) {
72 return;
73 }
74
75 BaseType_t intLvl = portENTER_CRITICAL_NESTED();
76
77 int cpu_id = xPortGetCoreID();
78
79 #ifdef DPORT_ACCESS_BENCHMARK
80 ccount_start[cpu_id] = cpu_hal_get_cycle_count();
81 #endif
82
83 if (dport_access_ref[cpu_id] == 0) {
84 portENTER_CRITICAL_ISR(&g_dport_mux);
85
86 oldInterruptLevel[cpu_id]=intLvl;
87
88 dport_access_start[cpu_id] = 0;
89 dport_access_end[cpu_id] = 0;
90
91 if (cpu_id == 0) {
92 _DPORT_REG_WRITE(DPORT_CPU_INTR_FROM_CPU_3_REG, DPORT_CPU_INTR_FROM_CPU_3); //interrupt on cpu1
93 } else {
94 _DPORT_REG_WRITE(DPORT_CPU_INTR_FROM_CPU_2_REG, DPORT_CPU_INTR_FROM_CPU_2); //interrupt on cpu0
95 }
96
97 while (!dport_access_start[cpu_id]) {};
98
99 REG_READ(SPI_DATE_REG(3)); //just read a APB register sure that the APB-bus is idle
100 }
101
102 dport_access_ref[cpu_id]++;
103
104 if (dport_access_ref[cpu_id] > 1) {
105 /* Interrupts are already disabled by the parent, we're nested here. */
106 portEXIT_CRITICAL_NESTED(intLvl);
107 }
108 #endif /* CONFIG_FREERTOS_UNICORE */
109 }
110
111 /* stall other cpu that this cpu is pending to access dport register end */
esp_dport_access_stall_other_cpu_end(void)112 void IRAM_ATTR esp_dport_access_stall_other_cpu_end(void)
113 {
114 #ifndef CONFIG_FREERTOS_UNICORE
115 int cpu_id = xPortGetCoreID();
116
117 if (dport_core_state[0] == DPORT_CORE_STATE_IDLE
118 || dport_core_state[1] == DPORT_CORE_STATE_IDLE) {
119 return;
120 }
121
122 if (dport_access_ref[cpu_id] == 0) {
123 assert(0);
124 }
125
126 dport_access_ref[cpu_id]--;
127
128 if (dport_access_ref[cpu_id] == 0) {
129 dport_access_end[cpu_id] = 1;
130
131 portEXIT_CRITICAL_ISR(&g_dport_mux);
132
133 portEXIT_CRITICAL_NESTED(oldInterruptLevel[cpu_id]);
134 }
135
136 #ifdef DPORT_ACCESS_BENCHMARK
137 ccount_end[cpu_id] = cpu_hal_get_cycle_count();
138 ccount_margin[cpu_id][ccount_margin_cnt] = ccount_end[cpu_id] - ccount_start[cpu_id];
139 ccount_margin_cnt = (ccount_margin_cnt + 1)&(DPORT_ACCESS_BENCHMARK_STORE_NUM - 1);
140 #endif
141 #endif /* CONFIG_FREERTOS_UNICORE */
142 }
143
144
145 #ifndef CONFIG_FREERTOS_UNICORE
dport_access_init_core(void * arg)146 static void dport_access_init_core(void *arg)
147 {
148 int core_id = 0;
149 uint32_t intr_source = ETS_FROM_CPU_INTR2_SOURCE;
150
151
152 core_id = xPortGetCoreID();
153 if (core_id == 1) {
154 intr_source = ETS_FROM_CPU_INTR3_SOURCE;
155 }
156
157 ESP_INTR_DISABLE(ETS_DPORT_INUM);
158 intr_matrix_set(core_id, intr_source, ETS_DPORT_INUM);
159 ESP_INTR_ENABLE(ETS_DPORT_INUM);
160
161 dport_access_ref[core_id] = 0;
162 dport_access_start[core_id] = 0;
163 dport_access_end[core_id] = 0;
164 dport_core_state[core_id] = DPORT_CORE_STATE_RUNNING;
165
166 /* If this fails then the minimum stack size for this config is too close to running out */
167 assert(uxTaskGetStackHighWaterMark(NULL) > 128);
168
169 vTaskDelete(NULL);
170 }
171 #endif
172
173 /* Defer initialisation until after scheduler is running */
esp_dport_access_int_init(void)174 void esp_dport_access_int_init(void)
175 {
176 #ifndef CONFIG_FREERTOS_UNICORE
177 portBASE_TYPE res = xTaskCreatePinnedToCore(&dport_access_init_core, "dport", configMINIMAL_STACK_SIZE, NULL, 5, NULL, xPortGetCoreID());
178 assert(res == pdTRUE);
179 #endif
180 }
181
esp_dport_access_int_pause(void)182 void IRAM_ATTR esp_dport_access_int_pause(void)
183 {
184 #ifndef CONFIG_FREERTOS_UNICORE
185 portENTER_CRITICAL_ISR(&g_dport_mux);
186 dport_core_state[0] = DPORT_CORE_STATE_IDLE;
187 dport_core_state[1] = DPORT_CORE_STATE_IDLE;
188 portEXIT_CRITICAL_ISR(&g_dport_mux);
189 #endif
190 }
191
192 //Used in panic code: the enter_critical stuff may be messed up so we just stop everything without checking the mux.
esp_dport_access_int_abort(void)193 void IRAM_ATTR esp_dport_access_int_abort(void)
194 {
195 #ifndef CONFIG_FREERTOS_UNICORE
196 dport_core_state[0] = DPORT_CORE_STATE_IDLE;
197 dport_core_state[1] = DPORT_CORE_STATE_IDLE;
198 #endif
199 }
200
esp_dport_access_int_resume(void)201 void IRAM_ATTR esp_dport_access_int_resume(void)
202 {
203 #ifndef CONFIG_FREERTOS_UNICORE
204 portENTER_CRITICAL_ISR(&g_dport_mux);
205 dport_core_state[0] = DPORT_CORE_STATE_RUNNING;
206 dport_core_state[1] = DPORT_CORE_STATE_RUNNING;
207 portEXIT_CRITICAL_ISR(&g_dport_mux);
208 #endif
209 }
210
211 /**
212 * @brief Read a sequence of DPORT registers to the buffer, SMP-safe version.
213 *
214 * This implementation uses a method of the pre-reading of the APB register
215 * before reading the register of the DPORT, without stall other CPU.
216 * There is disable/enable interrupt.
217 *
218 * @param[out] buff_out Contains the read data.
219 * @param[in] address Initial address for reading registers.
220 * @param[in] num_words The number of words.
221 */
esp_dport_access_read_buffer(uint32_t * buff_out,uint32_t address,uint32_t num_words)222 void IRAM_ATTR esp_dport_access_read_buffer(uint32_t *buff_out, uint32_t address, uint32_t num_words)
223 {
224 DPORT_INTERRUPT_DISABLE();
225 for (uint32_t i = 0; i < num_words; ++i) {
226 buff_out[i] = DPORT_SEQUENCE_REG_READ(address + i * 4);
227 }
228 DPORT_INTERRUPT_RESTORE();
229 }
230
231 /**
232 * @brief Read value from register, SMP-safe version.
233 *
234 * This method uses the pre-reading of the APB register before reading the register of the DPORT.
235 * This implementation is useful for reading DORT registers for single reading without stall other CPU.
236 * There is disable/enable interrupt.
237 *
238 * @param reg Register address
239 * @return Value
240 */
esp_dport_access_reg_read(uint32_t reg)241 uint32_t IRAM_ATTR esp_dport_access_reg_read(uint32_t reg)
242 {
243 #if defined(BOOTLOADER_BUILD) || !defined(CONFIG_ESP32_DPORT_WORKAROUND) || !defined(ESP_PLATFORM)
244 return _DPORT_REG_READ(reg);
245 #else
246 uint32_t apb;
247 unsigned int intLvl;
248 __asm__ __volatile__ (\
249 "rsil %[LVL], "XTSTR(CONFIG_ESP32_DPORT_DIS_INTERRUPT_LVL)"\n"\
250 "movi %[APB], "XTSTR(0x3ff40078)"\n"\
251 "l32i %[APB], %[APB], 0\n"\
252 "l32i %[REG], %[REG], 0\n"\
253 "wsr %[LVL], "XTSTR(PS)"\n"\
254 "rsync\n"\
255 : [APB]"=a"(apb), [REG]"+a"(reg), [LVL]"=a"(intLvl)\
256 : \
257 : "memory" \
258 );
259 return reg;
260 #endif
261 }
262
263 /**
264 * @brief Read value from register, NOT SMP-safe version.
265 *
266 * This method uses the pre-reading of the APB register before reading the register of the DPORT.
267 * There is not disable/enable interrupt.
268 * The difference from DPORT_REG_READ() is that the user himself must disable interrupts while DPORT reading.
269 * This implementation is useful for reading DORT registers in loop without stall other CPU. Note the usage example.
270 * The recommended way to read registers sequentially without stall other CPU
271 * is to use the method esp_dport_read_buffer(buff_out, address, num_words). It allows you to read registers in the buffer.
272 *
273 * \code{c}
274 * // This example shows how to use it.
275 * { // Use curly brackets to limit the visibility of variables in macros DPORT_INTERRUPT_DISABLE/RESTORE.
276 * DPORT_INTERRUPT_DISABLE(); // Disable interrupt only on current CPU.
277 * for (i = 0; i < max; ++i) {
278 * array[i] = esp_dport_access_sequence_reg_read(Address + i * 4); // reading DPORT registers
279 * }
280 * DPORT_INTERRUPT_RESTORE(); // restore the previous interrupt level
281 * }
282 * \endcode
283 *
284 * @param reg Register address
285 * @return Value
286 */
esp_dport_access_sequence_reg_read(uint32_t reg)287 uint32_t IRAM_ATTR esp_dport_access_sequence_reg_read(uint32_t reg)
288 {
289 #if defined(BOOTLOADER_BUILD) || !defined(CONFIG_ESP32_DPORT_WORKAROUND) || !defined(ESP_PLATFORM)
290 return _DPORT_REG_READ(reg);
291 #else
292 uint32_t apb;
293 __asm__ __volatile__ (\
294 "movi %[APB], "XTSTR(0x3ff40078)"\n"\
295 "l32i %[APB], %[APB], 0\n"\
296 "l32i %[REG], %[REG], 0\n"\
297 : [APB]"=a"(apb), [REG]"+a"(reg)\
298 : \
299 : "memory" \
300 );
301 return reg;
302 #endif
303 }
304