From 0377cc49e19e22ea04936a088b9d8273ea1d7b25 Mon Sep 17 00:00:00 2001
From: Serge Bazanski <q3k@q3k.org>
Date: Thu, 3 Aug 2023 01:34:54 +0200
Subject: [PATCH] main: move interrupts around, explain reasoning
---
main/main.c | 102 ++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 76 insertions(+), 26 deletions(-)
diff --git a/main/main.c b/main/main.c
index 12a0e09571..3b68057263 100644
--- a/main/main.c
+++ b/main/main.c
@@ -15,27 +15,45 @@
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
+#include "freertos/semphr.h"
#include "freertos/task.h"
#include "nvs_flash.h"
-// Called by micropython via MICROPY_BOARD_STARTUP.
-void flow3r_startup(void) {
- // Initialize display first as that gives us a nice splash screen.
- st3m_gfx_init();
- // Submit splash a couple of times to make sure we've fully flushed out the
- // initial framebuffer (both on-ESP and in-screen) noise before we turn on
- // the backlight.
- for (int i = 0; i < 4; i++) {
- st3m_gfx_splash("");
- }
- // Display should've flushed by now. Turn on backlight.
- flow3r_bsp_display_set_backlight(100);
+// Initialization is unfortunately quite complicated.
+//
+// In a perfect world, we'd just init everything in the main function in order.
+// Unfortunately, the ESP32's interrupt routing is per-core, and the ESP-IDF has
+// no API to configure interrupt handling to be performed by a core different
+// than the one which performed the interrupt setup.
+//
+// Since calls to flow3r_bsp_*/st3m_* perform calls to initialize various ESP32
+// peripherals via the ESP-IDF, it means these calls set up interrupts to be
+// handled by whatever core is executing the initialization code.
+//
+// Thus, we must perform a complicated dance in which we set up parts of
+// st3m/flow3r_bsp from one core, and part from another. Since flow3r_startup is
+// called on core0, we set up another task whose only job is to initialize
+// interrupts from core1.
+//
+// In the future we might refactor the codebase to use core pinning for all
+// tasks, and then initiazlize interrupts from within tasks, effectively keeping
+// interrupts local to handler tasks. But for now, we employ the strategy of
+// generally trusting the scheduler and instead selecting which interrupts is
+// handled by which core by performing initialization here.
+//
+// And we have to actually distribute these interrupts because each Xtensa core
+// simply doesn't have enough interrupt slots to easily handle all of our
+// external peripherals. Especially with the way the ESP-IDF interrupt
+// allocation algorithm works. Core0 is already quite polluted by micropython
+// doing its own initialization there, so we effectively push most interrupts to
+// core1.
+//
+// TODO(q3k): revisit this mess.
- st3m_mode_init();
- st3m_mode_set(st3m_mode_kind_starting, "st3m");
- st3m_mode_update_display(NULL);
+// Populated by the core1 init task whenever core1 init is done.
+static QueueHandle_t _core1_init_done_q;
- // Initialize USB and console so that we get logging as early as possible.
+void _init_core1(void *unused) {
st3m_usb_init();
st3m_console_init();
st3m_usb_app_conf_t app = {
@@ -59,21 +77,12 @@ void flow3r_startup(void) {
vTaskDelay(100 / portTICK_PERIOD_MS);
flow3r_bsp_i2c_init();
- st3m_mode_set(st3m_mode_kind_starting, "fs");
- st3m_mode_update_display(NULL);
+
flow3r_fs_init();
- st3m_mode_set(st3m_mode_kind_starting, "audio");
- st3m_mode_update_display(NULL);
- st3m_scope_init();
- st3m_audio_init();
- st3m_audio_set_player_function(bl00mbox_player_function);
- st3m_mode_set(st3m_mode_kind_starting, "io");
- st3m_mode_update_display(NULL);
st3m_leds_init();
st3m_io_init();
st3m_captouch_init();
st3m_imu_init();
- st3m_mode_set(st3m_mode_kind_starting, "nvs");
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES ||
ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
@@ -81,6 +90,47 @@ void flow3r_startup(void) {
nvs_flash_init();
}
+ // Let the main startup code know that core1 init is done. Then sleep
+ // forever. What a waste of stack space.
+ bool done = true;
+ xQueueSend(_core1_init_done_q, &done, portMAX_DELAY);
+ for (;;) {
+ vTaskDelay(10000 / portTICK_PERIOD_MS);
+ }
+}
+
+// Called by micropython via MICROPY_BOARD_STARTUP.
+void flow3r_startup(void) {
+ _core1_init_done_q = xQueueCreate(1, sizeof(bool));
+
+ // Initialize display first as that gives us a nice splash screen.
+ st3m_gfx_init();
+ // Submit splash a couple of times to make sure we've fully flushed out the
+ // initial framebuffer (both on-ESP and in-screen) noise before we turn on
+ // the backlight.
+ for (int i = 0; i < 4; i++) {
+ st3m_gfx_splash("");
+ }
+ // Display should've flushed by now. Turn on backlight.
+ flow3r_bsp_display_set_backlight(100);
+
+ st3m_mode_init();
+ st3m_mode_set(st3m_mode_kind_starting, "core1");
+ st3m_mode_update_display(NULL);
+
+ xTaskCreatePinnedToCore(_init_core1, "core1-init", 4096, NULL,
+ configMAX_PRIORITIES - 1, NULL, 1);
+
+ bool done;
+ xQueueReceive(_core1_init_done_q, &done, portMAX_DELAY);
+
+ st3m_mode_set(st3m_mode_kind_starting, "core0");
+ st3m_mode_update_display(NULL);
+
+ st3m_scope_init();
+ st3m_audio_init();
+ st3m_audio_set_player_function(bl00mbox_player_function);
+
st3m_mode_set(st3m_mode_kind_starting, "micropython");
st3m_mode_update_display(NULL);
}
--
GitLab