• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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