|
|
@@ -0,0 +1,229 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
|
|
|
+ *
|
|
|
+ * SPDX-License-Identifier: BSD-3-Clause
|
|
|
+ */
|
|
|
+
|
|
|
+#include "pico/flash.h"
|
|
|
+#include "hardware/exception.h"
|
|
|
+#include "hardware/sync.h"
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
|
|
|
+#include "pico/multicore.h"
|
|
|
+#endif
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
|
|
|
+#include "FreeRTOS.h"
|
|
|
+#include "task.h"
|
|
|
+// now we have FreeRTOS header we can check core count... we can only use FreeRTOS SMP mechanism
|
|
|
+// with two cores
|
|
|
+#if configNUM_CORES == 2
|
|
|
+#if configUSE_CORE_AFFINITY
|
|
|
+#define PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP 1
|
|
|
+#else
|
|
|
+#error configUSE_CORE_AFFINITY is required for PICO_FLASH_SAFE_EXECUTE_SUPPORT_FREERTOS_SMP
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+
|
|
|
+// There are multiple scenarios:
|
|
|
+//
|
|
|
+// 1. No use of core 1 - we just want to disable IRQs and not wait on core 1 to acquiesce
|
|
|
+// 2. Regular pico_multicore - we need to use multicore lockout.
|
|
|
+// 3. FreeRTOS on core 0, no use of core 1 - we just want to disable IRQs
|
|
|
+// 4. FreeRTOS SMP on both cores - we need to schedule a high priority task on the other core to disable IRQs.
|
|
|
+// 5. FreeRTOS on one core, but application is using the other core. ** WE CANNOT SUPPORT THIS TODAY ** without
|
|
|
+// the equivalent PICO_FLASH_ASSUME_COREx_SAFE (i.e. the user mkaing sure the other core is fine)
|
|
|
+
|
|
|
+static bool default_core_init_deinit(bool init);
|
|
|
+static int default_enter_safe_zone_timeout_ms(uint32_t timeout_ms);
|
|
|
+static int default_exit_safe_zone_timeout_ms(uint32_t timeout_ms);
|
|
|
+
|
|
|
+// note the default methods are combined, rather than having a separate helper for
|
|
|
+// FreeRTOS, as we may support mixed multicore and non SMP FreeRTOS in the future
|
|
|
+
|
|
|
+static flash_safety_helper_t default_flash_safety_helper = {
|
|
|
+ .core_init_deinit = default_core_init_deinit,
|
|
|
+ .enter_safe_zone_timeout_ms = default_enter_safe_zone_timeout_ms,
|
|
|
+ .exit_safe_zone_timeout_ms = default_exit_safe_zone_timeout_ms
|
|
|
+};
|
|
|
+
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
|
|
|
+// note that these are not reset by core reset, however for now, I think people resetting cores
|
|
|
+// and then doing this again without re-initializing pico_flash for that core, is probably
|
|
|
+// something we can live with breaking.
|
|
|
+static bool core_initialized[NUM_CORES];
|
|
|
+#endif
|
|
|
+
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
|
|
|
+enum {
|
|
|
+ FREERTOS_LOCKOUT_NONE = 0,
|
|
|
+ FREERTOS_LOCKOUT_LOCKER_WAITING,
|
|
|
+ FREERTOS_LOCKOUT_LOCKEE_READY,
|
|
|
+ FREERTOS_LOCKOUT_LOCKER_DONE,
|
|
|
+ FREERTOS_LOCKOUT_LOCKEE_DONE,
|
|
|
+};
|
|
|
+// state for the lockout operation launched from the corresponding core
|
|
|
+static volatile uint8_t lockout_state[NUM_CORES];
|
|
|
+#endif
|
|
|
+
|
|
|
+__attribute__((weak)) flash_safety_helper_t *get_flash_safety_helper(void) {
|
|
|
+ return &default_flash_safety_helper;
|
|
|
+}
|
|
|
+
|
|
|
+bool flash_safe_execute_core_init(void) {
|
|
|
+ flash_safety_helper_t *helper = get_flash_safety_helper();
|
|
|
+ return helper ? helper->core_init_deinit(true) : false;
|
|
|
+}
|
|
|
+
|
|
|
+bool flash_safe_execute_core_deinit(void) {
|
|
|
+ flash_safety_helper_t *helper = get_flash_safety_helper();
|
|
|
+ return helper ? helper->core_init_deinit(false) : false;
|
|
|
+}
|
|
|
+
|
|
|
+int flash_safe_execute(void (*func)(void *), void *param, uint32_t enter_exit_timeout_ms) {
|
|
|
+ flash_safety_helper_t *helper = get_flash_safety_helper();
|
|
|
+ if (!helper) return PICO_ERROR_NOT_PERMITTED;
|
|
|
+ int rc = helper->enter_safe_zone_timeout_ms(enter_exit_timeout_ms);
|
|
|
+ if (!rc) {
|
|
|
+ func(param);
|
|
|
+ rc = helper->exit_safe_zone_timeout_ms(enter_exit_timeout_ms);
|
|
|
+ }
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static bool default_core_init_deinit(__unused bool init) {
|
|
|
+#if PICO_FLASH_ASSUME_CORE0_SAFE
|
|
|
+ if (!get_core_num()) return true;
|
|
|
+#endif
|
|
|
+#if PICO_FLASH_ASSUME_CORE1_SAFE
|
|
|
+ if (get_core_num()) return true;
|
|
|
+#endif
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
|
|
|
+ return true;
|
|
|
+#endif
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
|
|
|
+ if (!init) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ multicore_lockout_victim_init();
|
|
|
+ core_initialized[get_core_num()] = init;
|
|
|
+#endif
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+// irq_state for the lockout operation launched from the corresponding core
|
|
|
+static uint32_t irq_state[NUM_CORES];
|
|
|
+
|
|
|
+static bool use_irq_only(void) {
|
|
|
+#if PICO_FLASH_ASSUME_CORE0_SAFE
|
|
|
+ if (get_core_num()) return true;
|
|
|
+#endif
|
|
|
+#if PICO_FLASH_ASSUME_CORE1_SAFE
|
|
|
+ if (!get_core_num()) return true;
|
|
|
+#endif
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
|
|
|
+static void __not_in_flash_func(flash_lockout_task)(__unused void *vother_core_num) {
|
|
|
+ uint other_core_num = (uintptr_t)vother_core_num;
|
|
|
+ while (lockout_state[other_core_num] != FREERTOS_LOCKOUT_LOCKER_WAITING) {
|
|
|
+ __wfe(); // we don't bother to try to let lower priority tasks run
|
|
|
+ }
|
|
|
+ uint32_t save = save_and_disable_interrupts();
|
|
|
+ lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_READY;
|
|
|
+ __sev();
|
|
|
+ while (lockout_state[other_core_num] == FREERTOS_LOCKOUT_LOCKEE_READY) {
|
|
|
+ __wfe(); // we don't bother to try to let lower priority tasks run
|
|
|
+ }
|
|
|
+ restore_interrupts(save);
|
|
|
+ lockout_state[other_core_num] = FREERTOS_LOCKOUT_LOCKEE_DONE;
|
|
|
+ __sev();
|
|
|
+ // bye bye
|
|
|
+ vTaskDelete(NULL);
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+static int default_enter_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
|
|
|
+ int rc = PICO_OK;
|
|
|
+ if (!use_irq_only()) {
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
|
|
|
+ // Note that whilst taskENTER_CRITICAL sounds promising (and on non SMP it disabled IRQs), on SMP
|
|
|
+ // it only prevents the other core from also entering a critical section.
|
|
|
+ // Therefore, we must do our own handshake which starts a task on the other core and have it disable interrupts
|
|
|
+ uint core_num = get_core_num();
|
|
|
+ // create at low priority
|
|
|
+ TaskHandle_t task_handle;
|
|
|
+ if (pdPASS != xTaskCreate(flash_lockout_task, "flash lockout", configMINIMAL_STACK_SIZE, (void *)core_num, 0, &task_handle)) {
|
|
|
+ return PICO_ERROR_INSUFFICIENT_RESOURCES;
|
|
|
+ }
|
|
|
+ lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_WAITING;
|
|
|
+ __sev();
|
|
|
+ // bind to other core
|
|
|
+ vTaskCoreAffinitySet(task_handle, 1u << (core_num ^ 1));
|
|
|
+ // and make it super high priority
|
|
|
+ vTaskPrioritySet(task_handle, configMAX_PRIORITIES -1);
|
|
|
+ absolute_time_t until = make_timeout_time_ms(timeout_ms);
|
|
|
+ while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY && !time_reached(until)) {
|
|
|
+ __wfe(); // we don't bother to try to let lower priority tasks run
|
|
|
+ }
|
|
|
+ if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_READY) {
|
|
|
+ lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
|
|
|
+ rc = PICO_ERROR_TIMEOUT;
|
|
|
+ }
|
|
|
+ // todo we may get preempted here, but I think that is OK unless what is pre-empts requires
|
|
|
+ // the other core to be running.
|
|
|
+#elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
|
|
|
+ // we cannot mix multicore_lockout and FreeRTOS as they both use the multicore FIFO...
|
|
|
+ // the user, will have to roll their own mechanism in this case.
|
|
|
+#if LIB_FREERTOS_KERNEL
|
|
|
+#if PICO_FLASH_ASSERT_ON_UNSAFE
|
|
|
+ assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
|
|
|
+ // unless PICO_FLASH_ASSUME_COREX_SAFE is set
|
|
|
+#endif
|
|
|
+ rc = PICO_ERROR_NOT_PERMITTED;
|
|
|
+#else // !LIB_FREERTOS_KERNEL
|
|
|
+ if (core_initialized[get_core_num()^1]) {
|
|
|
+ if (!multicore_lockout_start_timeout_us(timeout_ms * 1000ull)) {
|
|
|
+ rc = PICO_ERROR_TIMEOUT;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+#if PICO_FLASH_ASSERT_ON_UNSAFE
|
|
|
+ assert(false); // we expect the other core to have been initialized via flash_safe_execute_core_init()
|
|
|
+ // unless PICO_FLASH_ASSUME_COREX_SAFE is set
|
|
|
+#endif
|
|
|
+ rc = PICO_ERROR_NOT_PERMITTED;
|
|
|
+ }
|
|
|
+#endif // !LIB_FREERTOS_KERNEL
|
|
|
+#else
|
|
|
+ // no support for making other core safe provided, so fall through to irq
|
|
|
+ // note this is the case for a regular single core program
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ if (rc == PICO_OK) {
|
|
|
+ // we always want to disable IRQs on our core
|
|
|
+ irq_state[get_core_num()] = save_and_disable_interrupts();
|
|
|
+ }
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int default_exit_safe_zone_timeout_ms(__unused uint32_t timeout_ms) {
|
|
|
+ // assume if we're exiting we're called then entry happened successfully
|
|
|
+ restore_interrupts(irq_state[get_core_num()]);
|
|
|
+ if (!use_irq_only()) {
|
|
|
+#if PICO_FLASH_SAFE_EXECUTE_USE_FREERTOS_SMP
|
|
|
+ uint core_num = get_core_num();
|
|
|
+ lockout_state[core_num] = FREERTOS_LOCKOUT_LOCKER_DONE;
|
|
|
+ __sev();
|
|
|
+ absolute_time_t until = make_timeout_time_ms(timeout_ms);
|
|
|
+ while (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE && !time_reached(until)) {
|
|
|
+ __wfe(); // we don't bother to try to let lower priority tasks run
|
|
|
+ }
|
|
|
+ if (lockout_state[core_num] != FREERTOS_LOCKOUT_LOCKEE_DONE) {
|
|
|
+ return PICO_ERROR_TIMEOUT;
|
|
|
+ }
|
|
|
+#elif PICO_FLASH_SAFE_EXECUTE_PICO_SUPPORT_MULTICORE_LOCKOUT
|
|
|
+ return multicore_lockout_end_timeout_us(timeout_ms * 1000ull) ? PICO_OK : PICO_ERROR_TIMEOUT;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+ return PICO_OK;
|
|
|
+}
|