1 /*
2  * Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  */
6 
7 #include "pico/flash.h"
8 #include "hardware/sync.h"
9 #if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
10 #include "pico/multicore.h"
11 #endif
12 #include "pico/time.h"
13 #if PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
14 #include "FreeRTOS.h"
15 #include "task.h"
16 // now we have FreeRTOS header we can check core count... we can only use FreeRTOS SMP mechanism
17 // with two cores
18 #if configNUMBER_OF_CORES == 2
19 #if configUSE_CORE_AFFINITY
20 #define PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP 1
21 #else
22 #error configUSE_CORE_AFFINITY is required for PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
23 #endif
24 #endif
25 #endif
26 
27 // There are multiple scenarios:
28 //
29 // 1. No use of core 1 - we just want to disable IRQs and not wait on core 1 to acquiesce
30 // 2. Regular pico_multicore - we need to use multicore lockout.
31 // 3. FreeRTOS on core 0, no use of core 1 - we just want to disable IRQs
32 // 4. FreeRTOS SMP on both cores - we need to schedule a high priority task on the other core to disable IRQs.
33 // 5. FreeRTOS on one core, but application is using the other core. ** WE CANNOT SUPPORT THIS TODAY ** without
34 //    the equivalent PICO_FLASH_ASSUME_COREx_SAFE (i.e. the user making sure the other core is fine)
35 
36 static bool default_core_init_deinit(bool init);
37 static int default_enter_safe_zone_timeout_ms(uint32_t timeout_ms);
38 static int default_exit_safe_zone_timeout_ms(uint32_t timeout_ms);
39 
40 // note the default methods are combined, rather than having a separate helper for
41 // FreeRTOS, as we may support mixed multicore and non SMP FreeRTOS in the future
42 
43 static flash_safety_helper_t default_flash_safety_helper = {
44         .core_init_deinit = default_core_init_deinit,
45         .enter_safe_zone_timeout_ms = default_enter_safe_zone_timeout_ms,
46         .exit_safe_zone_timeout_ms = default_exit_safe_zone_timeout_ms
47 };
48 
49 #if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
50 enum {
51     FREERTOS_LOCKOUT_NONE = 0,
52     FREERTOS_LOCKOUT_LOCKER_WAITING,
53     FREERTOS_LOCKOUT_LOCKEE_READY,
54     FREERTOS_LOCKOUT_LOCKER_DONE,
55     FREERTOS_LOCKOUT_LOCKEE_DONE,
56 };
57 // state for the lockout operation launched from the corresponding core
58 static volatile uint8_t lockout_state[NUM_CORES];
59 #endif
60 
get_flash_safety_helper(void)61 __attribute__((weak)) flash_safety_helper_t *get_flash_safety_helper(void) {
62     return &default_flash_safety_helper;
63 }
64 
flash_safe_execute_core_init(void)65 bool flash_safe_execute_core_init(void) {
66     flash_safety_helper_t *helper = get_flash_safety_helper();
67     return helper ? helper->core_init_deinit(true) : false;
68 }
69 
flash_safe_execute_core_deinit(void)70 bool flash_safe_execute_core_deinit(void) {
71     flash_safety_helper_t *helper = get_flash_safety_helper();
72     return helper ? helper->core_init_deinit(false) : false;
73 }
74 
flash_safe_execute(void (* func)(void *),void * param,uint32_t enter_exit_timeout_ms)75 int flash_safe_execute(void (*func)(void *), void *param, uint32_t enter_exit_timeout_ms) {
76     flash_safety_helper_t *helper = get_flash_safety_helper();
77     if (!helper) return PICO_ERROR_NOT_PERMITTED;
78     int rc = helper->enter_safe_zone_timeout_ms(enter_exit_timeout_ms);
79     if (!rc) {
80         func(param);
81         rc = helper->exit_safe_zone_timeout_ms(enter_exit_timeout_ms);
82     }
83     return rc;
84 }
85 
default_core_init_deinit(__unused bool init)86 static bool default_core_init_deinit(__unused bool init) {
87 #if PICO_FLASH_ASSUME_CORE0_SAFE
88     if (!get_core_num()) return true;
89 #endif
90 #if PICO_FLASH_ASSUME_CORE1_SAFE
91     if (get_core_num()) return true;
92 #endif
93 #if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
94     return true;
95 #endif
96 #if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
97     if (!init) {
98         return false;
99     }
100     multicore_lockout_victim_init();
101 #endif
102     return true;
103 }
104 
105 // irq_state for the lockout operation launched from the corresponding core
106 static uint32_t irq_state[NUM_CORES];
107 
use_irq_only(void)108 static bool use_irq_only(void) {
109 #if PICO_FLASH_ASSUME_CORE0_SAFE
110     if (get_core_num()) return true;
111 #endif
112 #if PICO_FLASH_ASSUME_CORE1_SAFE
113     if (!get_core_num()) return true;
114 #endif
115     return false;
116 }
117 
118 #if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
__not_in_flash_func(flash_lockout_task)119 static void __not_in_flash_func(flash_lockout_task)(__unused void *vother_core_num) {
120     uint other_core_num = (uintptr_t)vother_core_num;
121     while (lockout_state[other_core_num] != FREERTOS_LOCKOUT_LOCKER_WAITING) {
122         __wfe(); // we don't bother to try to let lower priority tasks run
123     }
124     uint32_t save = save_and_disable_interrupts();
125     lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_READY;
126     __sev();
127     while (lockout_state[other_core_num] == FREERTOS_LOCKOUT_LOCKEE_READY) {
128         __wfe(); // we don't bother to try to let lower priority tasks run
129     }
130     restore_interrupts(save);
131     lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_DONE;
132     __sev();
133     // bye bye
134     vTaskDelete(NULL);
135 }
136 #endif
137 
default_enter_safe_zone_timeout_ms(__unused uint32_t timeout_ms)138 static int default_enter_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
139     int rc = PICO_OK;
140     if (!use_irq_only()) {
141 #if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
142         // Note that whilst taskENTER_CRITICAL sounds promising (and on non SMP it disabled IRQs), on SMP
143         // it only prevents the other core from also entering a critical section.
144         // Therefore, we must do our own handshake which starts a task on the other core and have it disable interrupts
145         uint core_num = get_core_num();
146         // create at low priority on other core
147         TaskHandle_t task_handle;
148         if (pdPASS != xTaskCreateAffinitySet(flash_lockout_task, "flash lockout", configMINIMAL_STACK_SIZE, (void *)core_num, 0, 1u << (core_num ^ 1), &task_handle)) {
149             return PICO_ERROR_INSUFFICIENT_RESOURCES;
150         }
151         lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_WAITING;
152         __sev();
153         // make it super high priority
154         vTaskPrioritySet(task_handle, configMAX_PRIORITIES -1);
155         absolute_time_t until = make_timeout_time_ms(timeout_ms);
156         while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY && !time_reached(until)) {
157             __wfe(); // we don't bother to try to let lower priority tasks run
158         }
159         if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY) {
160             lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
161             rc = PICO_ERROR_TIMEOUT;
162         }
163         // todo we may get preempted here, but I think that is OK unless what is pre-empts requires
164         //      the other core to be running.
165 #elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
166         // we cannot mix multicore_lockout and FreeRTOS as they both use the multicore FIFO...
167         // the user, will have to roll their own mechanism in this case.
168 #if LIB_FREERTOS_KERNEL
169 #if PICO_FLASH_ASSERT_ON_UNSAFE
170         assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
171                        // unless PICO_FLASH_ASSUME_COREX_SAFE is set
172 #endif
173         rc = PICO_ERROR_NOT_PERMITTED;
174 #else // !LIB_FREERTOS_KERNEL
175         if (multicore_lockout_victim_is_initialized(get_core_num()^1)) {
176             if (!multicore_lockout_start_timeout_us(timeout_ms * 1000ull)) {
177                 rc = PICO_ERROR_TIMEOUT;
178             }
179         } else {
180 #if PICO_FLASH_ASSERT_ON_UNSAFE
181             assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
182                            // unless PICO_FLASH_ASSUME_COREX_SAFE is set
183 #endif
184             rc = PICO_ERROR_NOT_PERMITTED;
185         }
186 #endif // !LIB_FREERTOS_KERNEL
187 #else
188         // no support for making other core safe provided, so fall through to irq
189         // note this is the case for a regular single core program
190 #endif
191     }
192     if (rc == PICO_OK) {
193         // we always want to disable IRQs on our core
194         irq_state[get_core_num()] = save_and_disable_interrupts();
195     }
196     return rc;
197 }
198 
default_exit_safe_zone_timeout_ms(__unused uint32_t timeout_ms)199 static int default_exit_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
200     // assume if we're exiting we're called then entry happened successfully
201     restore_interrupts_from_disabled(irq_state[get_core_num()]);
202     if (!use_irq_only()) {
203 #if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
204         uint core_num = get_core_num();
205         lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
206         __sev();
207         absolute_time_t until = make_timeout_time_ms(timeout_ms);
208         while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE && !time_reached(until)) {
209             __wfe(); // we don't bother to try to let lower priority tasks run
210         }
211         if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE) {
212             return PICO_ERROR_TIMEOUT;
213         }
214 #elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
215         return multicore_lockout_end_timeout_us(timeout_ms * 1000ull) ? PICO_OK : PICO_ERROR_TIMEOUT;
216 #endif
217     }
218     return PICO_OK;
219 }