1 /*
2  * SPDX-FileCopyrightText: 2017-2022 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <stddef.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <assert.h>
11 #include "esp_err.h"
12 #include "esp_attr.h"
13 #include "soc/soc.h"
14 #include "soc/dport_reg.h"
15 #ifndef CONFIG_IDF_TARGET_ESP32
16 #include "soc/periph_defs.h"
17 #include "soc/system_reg.h"
18 #endif
19 #include "freertos/FreeRTOS.h"
20 #include "freertos/task.h"
21 #include "freertos/portmacro.h"
22 #include "esp_intr_alloc.h"
23 #include "esp_private/esp_ipc_isr.h"
24 #include "esp_ipc_isr.h"
25 #include "xtensa/core-macros.h"
26 #include "sdkconfig.h"
27 
28 static portMUX_TYPE s_ipc_isr_mux = portMUX_INITIALIZER_UNLOCKED;
29 uint32_t volatile esp_ipc_isr_start_fl;        // the flag shows that it is about to run esp_ipc_func()
30 uint32_t volatile esp_ipc_isr_end_fl = 1;      // the flag shows that esp_ipc_func() is done
31 esp_ipc_isr_func_t volatile esp_ipc_func;      // the function which will be run in the ipc_isr context
32 void * volatile esp_ipc_func_arg;              // the argument of esp_ipc_func()
33 
34 typedef enum {
35     STALL_STATE_IDLE      = 0,
36     STALL_STATE_RUNNING   = 1,
37 } stall_state_t;
38 
39 static stall_state_t volatile s_stall_state = STALL_STATE_IDLE;
40 static int32_t volatile s_count_of_nested_calls[portNUM_PROCESSORS] = { 0 };
41 static BaseType_t s_stored_interrupt_level;
42 static uint32_t volatile esp_ipc_isr_finish_cmd;
43 
44 
45 /**
46  * @brief Type of calling
47  */
48 typedef enum {
49     IPC_ISR_WAIT_FOR_START = 0,   /*!< The caller is waiting for the start */
50     IPC_ISR_WAIT_FOR_END   = 1,   /*!< The caller is waiting for the end */
51 } esp_ipc_isr_wait_t;
52 
53 #define IPC_ISR_ENTER_CRITICAL() portENTER_CRITICAL_SAFE(&s_ipc_isr_mux)
54 #define IPC_ISR_EXIT_CRITICAL()  portEXIT_CRITICAL_SAFE(&s_ipc_isr_mux)
55 
56 static void esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for);
57 
58 
59 /* Initializing IPC_ISR */
60 
esp_ipc_isr_init(void)61 void esp_ipc_isr_init(void)
62 {
63     const uint32_t cpuid = xPortGetCoreID();
64     uint32_t intr_source = ETS_FROM_CPU_INTR2_SOURCE + cpuid; // ETS_FROM_CPU_INTR2_SOURCE and ETS_FROM_CPU_INTR3_SOURCE
65     ESP_INTR_DISABLE(ETS_IPC_ISR_INUM);
66     esp_rom_route_intr_matrix(cpuid, intr_source, ETS_IPC_ISR_INUM);
67     ESP_INTR_ENABLE(ETS_IPC_ISR_INUM);
68 
69     if (cpuid != 0) {
70         s_stall_state = STALL_STATE_RUNNING;
71     }
72 }
73 
74 /* End initializing IPC_ISR */
75 
76 
77 /* Public API functions */
78 
esp_ipc_isr_asm_call(esp_ipc_isr_func_t func,void * arg)79 void IRAM_ATTR esp_ipc_isr_asm_call(esp_ipc_isr_func_t func, void* arg)
80 {
81     IPC_ISR_ENTER_CRITICAL();
82     esp_ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_START);
83     IPC_ISR_EXIT_CRITICAL();
84 }
85 
esp_ipc_isr_asm_call_blocking(esp_ipc_isr_func_t func,void * arg)86 void IRAM_ATTR esp_ipc_isr_asm_call_blocking(esp_ipc_isr_func_t func, void* arg)
87 {
88     IPC_ISR_ENTER_CRITICAL();
89     esp_ipc_isr_call_and_wait(func, arg, IPC_ISR_WAIT_FOR_END);
90     IPC_ISR_EXIT_CRITICAL();
91 }
92 
93 // This asm function is from esp_ipc_isr_routines.S.
94 // It is waiting for the finish_cmd command in a loop.
95 void esp_ipc_isr_waiting_for_finish_cmd(void* finish_cmd);
96 
97 /*
98  * esp_ipc_isr_stall_other_cpu is used for:
99  *  - stall other CPU,
100  *  - do protection when dual core access DPORT internal register and APB register via DPORT simultaneously.
101  * This function will be initialize after FreeRTOS startup.
102  * When cpu0 wants to access DPORT register, it should notify cpu1 enter in high-priority interrupt for be mute.
103  * When cpu1 already in high-priority interrupt, cpu0 can access DPORT register.
104  * Currently, cpu1 will wait for cpu0 finish access and exit high-priority interrupt.
105  */
esp_ipc_isr_stall_other_cpu(void)106 void IRAM_ATTR esp_ipc_isr_stall_other_cpu(void)
107 {
108 #if CONFIG_FREERTOS_SMP
109     /*
110     Temporary workaround to prevent deadlocking on the SMP FreeRTOS kernel lock after stalling the other CPU.
111     See IDF-5257
112     */
113     taskENTER_CRITICAL();
114 #endif
115     if (s_stall_state == STALL_STATE_RUNNING) {
116 #if CONFIG_FREERTOS_SMP
117         BaseType_t intLvl = portDISABLE_INTERRUPTS();
118 #else
119         BaseType_t intLvl = portSET_INTERRUPT_MASK_FROM_ISR();
120 #endif
121         const uint32_t cpu_id = xPortGetCoreID();
122         if (s_count_of_nested_calls[cpu_id]++ == 0) {
123             IPC_ISR_ENTER_CRITICAL();
124             s_stored_interrupt_level = intLvl;
125             esp_ipc_isr_finish_cmd = 0;
126             esp_ipc_isr_call_and_wait(&esp_ipc_isr_waiting_for_finish_cmd, (void*)&esp_ipc_isr_finish_cmd, IPC_ISR_WAIT_FOR_START);
127             return;
128         }
129 
130         /* Interrupts are already disabled by the parent, we're nested here. */
131 #if CONFIG_FREERTOS_SMP
132         portRESTORE_INTERRUPTS(intLvl);
133 #else
134         portCLEAR_INTERRUPT_MASK_FROM_ISR(intLvl);
135 #endif
136     }
137 }
138 
esp_ipc_isr_release_other_cpu(void)139 void IRAM_ATTR esp_ipc_isr_release_other_cpu(void)
140 {
141     if (s_stall_state == STALL_STATE_RUNNING) {
142         const uint32_t cpu_id = xPortGetCoreID();
143         if (--s_count_of_nested_calls[cpu_id] == 0) {
144             esp_ipc_isr_finish_cmd = 1;
145             // Make sure end flag is cleared and esp_ipc_isr_waiting_for_finish_cmd is done.
146             while (!esp_ipc_isr_end_fl) {};
147             IPC_ISR_EXIT_CRITICAL();
148 #if CONFIG_FREERTOS_SMP
149             portRESTORE_INTERRUPTS(s_stored_interrupt_level);
150 #else
151             portCLEAR_INTERRUPT_MASK_FROM_ISR(s_stored_interrupt_level);
152 #endif
153         } else if (s_count_of_nested_calls[cpu_id] < 0) {
154             assert(0);
155         }
156     }
157 #if CONFIG_FREERTOS_SMP
158     /*
159     Temporary workaround to prevent deadlocking on the SMP FreeRTOS kernel lock after stalling the other CPU.
160     See IDF-5257
161     */
162     taskEXIT_CRITICAL();
163 #endif
164 }
165 
esp_ipc_isr_stall_pause(void)166 void IRAM_ATTR esp_ipc_isr_stall_pause(void)
167 {
168     IPC_ISR_ENTER_CRITICAL();
169     s_stall_state = STALL_STATE_IDLE;
170     IPC_ISR_EXIT_CRITICAL();
171 }
172 
esp_ipc_isr_stall_abort(void)173 void IRAM_ATTR esp_ipc_isr_stall_abort(void)
174 {
175     //Note: We don't enter a critical section here as we are calling this from a panic.
176     s_stall_state = STALL_STATE_IDLE;
177 }
178 
esp_ipc_isr_stall_resume(void)179 void IRAM_ATTR esp_ipc_isr_stall_resume(void)
180 {
181     IPC_ISR_ENTER_CRITICAL();
182     s_stall_state = STALL_STATE_RUNNING;
183     IPC_ISR_EXIT_CRITICAL();
184 }
185 
186 /* End public API functions */
187 
188 
189 /* Private functions*/
190 
esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t func,void * arg,esp_ipc_isr_wait_t wait_for)191 static void IRAM_ATTR esp_ipc_isr_call_and_wait(esp_ipc_isr_func_t func, void* arg, esp_ipc_isr_wait_t wait_for)
192 {
193     const uint32_t cpu_id = xPortGetCoreID();
194 
195     // waiting for the end of the previous call
196     while (!esp_ipc_isr_end_fl) {};
197 
198     esp_ipc_func = func;
199     esp_ipc_func_arg = arg;
200 
201     esp_ipc_isr_start_fl = 0;
202     esp_ipc_isr_end_fl = 0;
203 
204     if (cpu_id == 0) {
205         // it runs an interrupt on cpu1
206         DPORT_REG_WRITE(SYSTEM_CPU_INTR_FROM_CPU_3_REG, SYSTEM_CPU_INTR_FROM_CPU_3);
207     } else {
208         // it runs an interrupt on cpu0
209         DPORT_REG_WRITE(SYSTEM_CPU_INTR_FROM_CPU_2_REG, SYSTEM_CPU_INTR_FROM_CPU_2);
210     }
211 
212     // IPC_ISR handler will be called and `...isr_start` and `...isr_end` will be updated there
213 
214     if (wait_for == IPC_ISR_WAIT_FOR_START) {
215         while (!esp_ipc_isr_start_fl) {};
216     } else {
217         // IPC_ISR_WAIT_FOR_END
218         while (!esp_ipc_isr_end_fl) {};
219     }
220 }
221 
222 /* End private functions*/
223