Browse Source

ESP32: pin all tskNO_AFFINITY tasks to core 0 via linker wrap

On dual-core ESP32-S3 (ARDUINO_RUNNING_CORE=1), the Arduino loop and
LoRa processing run exclusively on core 1.  ESP-IDF v4 creates mqtt_task
with tskNO_AFFINITY, meaning FreeRTOS may schedule them on core 1 under
load, preempting the LoRa loop.

ESP-IDF v4 provides no public API to change a task's core affinity after
creation (vTaskCoreAffinitySet is IDF v5+ only), and the esp_mqtt_client
config struct has no task_core_id field.  The precompiled Arduino-ESP32
framework cannot be patched via sdkconfig.

Instead, use the GCC/LD --wrap linker mechanism to intercept every call
to xTaskCreatePinnedToCore.  Any task created with tskNO_AFFINITY is
redirected to core 0.  Tasks that are already explicitly pinned (Wi-Fi
driver, LwIP, httpd, esp_timer, ipc0/ipc1) are passed through unchanged.

We intentionally do not filter by task name.  Pinning all unpinned tasks
makes the approach robust against internal ESP-IDF task name changes and
catches any future tasks that may be added with tskNO_AFFINITY.

Verified task layout after the change:
  loopTask  pri=1  core=1   (Arduino loop / LoRa — unchanged)
  mqtt_task pri=5  core=0   (was tskNO_AFFINITY, now pinned)
  httpd     pri=2  core=0   (core_id set explicitly in WebPanelServer)
  tiT       pri=18 core=0   (LwIP, already pinned by ESP-IDF)
  wifi      pri=23 core=0   (Wi-Fi driver, already pinned)
  esp_timer pri=22 core=0   (already pinned)
  ipc0/ipc1 pri=24 core=0/1 (IPC, already pinned per-core)

The -Wl,--wrap flag and arch/esp32/task_pinning.c are added only to
[esp32_base] (IDF v4).  The ESP32-C6 pioarduino target (IDF v5) is
unaffected and can use vTaskCoreAffinitySet() if needed in the future.
Valentin V. Bartenev 3 tháng trước cách đây
mục cha
commit
eb2c6d0bb8
2 tập tin đã thay đổi với 69 bổ sung0 xóa
  1. 67 0
      arch/esp32/task_pinning.c
  2. 2 0
      platformio.ini

+ 67 - 0
arch/esp32/task_pinning.c

@@ -0,0 +1,67 @@
+/*
+ * arch/esp32/task_pinning.c
+ *
+ * Pins all FreeRTOS tasks with no explicit core affinity (tskNO_AFFINITY) to
+ * core 0, keeping core 1 exclusively for the Arduino loop (loopTask) where
+ * LoRa packet processing runs.
+ *
+ * Background:
+ *   On ESP32-S3 (dual-core), ARDUINO_RUNNING_CORE=1 pins loopTask to core 1.
+ *   However, ESP-IDF v4 creates MQTT task with tskNO_AFFINITY, so FreeRTOS
+ *   may schedule them on core 1 under load, preempting LoRa processing.
+ *
+ *   ESP-IDF v4 provides no public API to change a task's core affinity after
+ *   creation (vTaskCoreAffinitySet is IDF v5+ only), and esp_mqtt_client_config_t
+ *   has no core selection field in IDF v4.  The linker --wrap mechanism is the
+ *   only way to intercept xTaskCreatePinnedToCore at the call site inside
+ *   precompiled library code.
+ *
+ *   It's intentionally do not filtered by task name — this keeps the approach
+ *   robust against internal ESP-IDF task name changes and also catches any
+ *   other library tasks that may happen to be created with tskNO_AFFINITY.
+ *   Core 1 is reserved exclusively for loopTask (LoRa / application logic).
+ *
+ * Mechanism:
+ *   -Wl,--wrap=xTaskCreatePinnedToCore redirects every call to
+ *   xTaskCreatePinnedToCore (including the xTaskCreate macro which expands to
+ *   it with tskNO_AFFINITY) through __wrap_xTaskCreatePinnedToCore below.
+ *   Tasks that are already explicitly pinned to a core are passed through
+ *   unchanged.
+ *
+ * Verified task layout after this patch:
+ *   core 0: mqtt_task(5), httpd(2), tiT/LwIP(18), wifi(23), esp_timer(22), ipc0(24)
+ *   core 1: loopTask(1), ipc1(24), IDLE1(0)
+ *
+ * Note: this file is compiled only for [esp32_base] targets (IDF v4).
+ * ESP32-C6 uses pioarduino (IDF v5) where vTaskCoreAffinitySet() is available.
+ */
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+/* The real function, renamed by the linker to __real_xTaskCreatePinnedToCore. */
+extern BaseType_t __real_xTaskCreatePinnedToCore(
+  TaskFunction_t pvTaskCode,
+  const char * const pcName,
+  const uint32_t usStackDepth,
+  void * const pvParameters,
+  UBaseType_t uxPriority,
+  TaskHandle_t * const pvCreatedTask,
+  const BaseType_t xCoreID
+);
+
+BaseType_t __wrap_xTaskCreatePinnedToCore(
+  TaskFunction_t pvTaskCode,
+  const char * const pcName,
+  const uint32_t usStackDepth,
+  void * const pvParameters,
+  UBaseType_t uxPriority,
+  TaskHandle_t * const pvCreatedTask,
+  const BaseType_t xCoreID
+) {
+  /* Pin all tasks with no affinity to core 0. */
+  BaseType_t core = (xCoreID == tskNO_AFFINITY) ? 0 : xCoreID;
+  return __real_xTaskCreatePinnedToCore(
+    pvTaskCode, pcName, usStackDepth, pvParameters, uxPriority, pvCreatedTask, core
+  );
+}

+ 2 - 0
platformio.ini

@@ -64,7 +64,9 @@ extra_scripts =
 build_flags = ${arduino_base.build_flags}
   -D ESP32_PLATFORM
 ;  -D ESP32_CPU_FREQ=80          ; change it to your need
+  -Wl,--wrap=xTaskCreatePinnedToCore
 build_src_filter = ${arduino_base.build_src_filter}
+  +<../arch/esp32/task_pinning.c>
 
 [esp32_ota]
 lib_deps =