p4: stop retrying a C6 that is never coming back
7ce1b3fturned off CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE so a wrong- firmware C6 could not reboot the board. It stopped the boot loop and I called it fixed on a 45 s capture. That was the wrong test. Disabling the restart did not stop esp-hosted, it only removed its exit: sdio_read_task posts ESP_HOSTED_EVENT_TRANSPORT_FAILURE and then `continue`s straight back into a register read that is going to fail again. Measured on the same board: 731 failed register reads in 45 s, ~2.2 KB/s of log, forever A boot loop and an unbounded retry storm are the same mistake: an unreachable co-processor is a PERMANENT condition and the driver treats it as transient. * The rx task now parks after the failure event is posted (build-time patch to the esp-hosted fork, same idempotent mechanism build.sh already uses for libsodium and the SD_MMC pull-ups; it fails loudly if upstream moves that code rather than silently not applying). No reboot, no retry: Wi-Fi and BLE are unavailable and everything else carries on. * hostedInitBLE() no longer runs unconditionally. It is a full esp-hosted handshake and it ran on every boot even though the banner advertises lazy initialisation -- so on a mismatched unit it started a transport that can never come up, for a radio the user may have had switched off. It now honours the same stored intent beginBle() already checked. * The keyboard hot-plug probe backs off from 1.5 s to 15 s once the expansion has been absent a few times, and returns to the fast rate the moment one succeeds. The probe is a real I2C transaction and on an empty bus Arduino's Wire layer logs two [E] lines per attempt: a permanent drip down the USB-CDC the companion app talks over, for an accessory most people do not own. Measured on an ESP-AT unit running the HOSTED image, 150 s captures: shipped7ce1b3fonly this resets 5 0 0 host restarts yes yes 0 failed reg reads - 731/45s 1 steady log rate - 2.2 KB/s ~14 B/s UI never appears stays up The remaining ~14 B/s is the backed-off keyboard probe on a board with no expansion attached, which is the expected floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7ce1b3f48c
commit
b52c71f4a5
3 files changed
+75
-6
No files matched your search
@@ -107,6 +107,53 @@ if [ -f "$SDMMC_C" ] && ! grep -q 'wadamesh P4: internal pullups' "$SDMMC_C"; th
|
||||
sed -i '' 's|sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();|sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();\n slot_config.flags \|= SDMMC_SLOT_FLAG_INTERNAL_PULLUP; // wadamesh P4: internal pullups (no external ones on the SD lines)|' "$SDMMC_C"
|
||||
echo "[build.sh] patched arduino SD_MMC.cpp (internal pull-ups on the SD slot)"
|
||||
fi
|
||||
# --- Build-time patch: a dead C6 must not spin the SDIO rx task -----------------------------------
|
||||
# With CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE=n (see sdkconfigs/tdisplay_p4 for why it is
|
||||
# off), sdio_read_task posts ESP_HOSTED_EVENT_TRANSPORT_FAILURE and then `continue`s — straight back
|
||||
# into a register read that is going to fail again. On a unit whose C6 runs factory ESP-AT the
|
||||
# transport NEVER comes up, so that is an unbounded retry storm: measured at ~16 failed reads per
|
||||
# second, forever, with the whole transport re-initialising on top of it. It floods the USB-CDC the
|
||||
# companion app talks over and keeps a task busy for the life of the session.
|
||||
#
|
||||
# Turning the restart back on is not the answer — that is the boot loop this board had before.
|
||||
# Both are the same mistake: a co-processor that cannot be reached is a permanent condition, and
|
||||
# the driver treats it as transient. Park the task instead. The failure event has already been
|
||||
# posted, Wi-Fi and BLE are simply unavailable, and everything else on the device keeps running.
|
||||
# Idempotent.
|
||||
HOSTED_SDIO="managed_components/nicolaielectronics__esp-hosted-tanmatsu/host/drivers/transport/sdio/sdio_drv.c"
|
||||
if [ -f "$HOSTED_SDIO" ] && ! grep -q 'wadamesh: park instead of spinning' "$HOSTED_SDIO"; then
|
||||
python3 - "$HOSTED_SDIO" <<'PYEOF'
|
||||
import re, sys
|
||||
p = sys.argv[1]
|
||||
s = open(p).read()
|
||||
# Both failure arms look identical apart from the log line: event post, optional restart, continue.
|
||||
old = """#if H_TRANSPORT_RESTART_ON_FAILURE
|
||||
ESP_LOGI(TAG, "Host is resetting itself, to avoid any sdio race condition");
|
||||
g_h.funcs->_h_restart_host();
|
||||
#endif
|
||||
continue;"""
|
||||
new = """#if H_TRANSPORT_RESTART_ON_FAILURE
|
||||
ESP_LOGI(TAG, "Host is resetting itself, to avoid any sdio race condition");
|
||||
g_h.funcs->_h_restart_host();
|
||||
#else
|
||||
/* wadamesh: park instead of spinning. The transport is down and we are
|
||||
* not allowed to reboot the host, so retrying can only fail again at
|
||||
* full speed. The failure event is already posted; stop here and let the
|
||||
* rest of the device run without Wi-Fi/BLE. */
|
||||
ESP_LOGE(TAG, "transport down, host restart disabled: parking rx task (Wi-Fi/BLE unavailable)");
|
||||
while (1) g_h.funcs->_h_msleep(60000);
|
||||
#endif
|
||||
continue;"""
|
||||
n = s.count(old)
|
||||
if n == 0:
|
||||
sys.exit("esp-hosted sdio_drv.c: failure arm not found; patch needs updating")
|
||||
s = s.replace(old, new)
|
||||
open(p, "w").write(s)
|
||||
print(" patched %d failure arm(s)" % n)
|
||||
PYEOF
|
||||
echo "[build.sh] patched esp-hosted sdio_drv.c (park the rx task on a dead transport)"
|
||||
fi
|
||||
|
||||
# ⚠️ The P4/slot-0 branch REBUILDS slot_config from scratch with `.flags = 0`, silently discarding
|
||||
# the flag added above (that's the branch that actually runs with BOARD_SDMMC_SLOT=0) — so patch that
|
||||
# struct literal too. Without the flag the IDF host explicitly FLOATS the pads -> deterministic 0x109
|
||||
|
||||
@@ -411,13 +411,22 @@ static void wadameshSetup() {
|
||||
c6at_worker_start();
|
||||
#else
|
||||
#if defined(BLE_PIN_CODE) && TDP4_C6_READY && TDP4_BLE_READY
|
||||
if (hostedInitBLE()) {
|
||||
char* nm = the_mesh.getNodePrefs()->node_name;
|
||||
serial_interface.prepareBle("wadamesh-", nm, the_mesh.getBLEPin());
|
||||
if (wifiConfigGetBleEnabled())
|
||||
// Only bring the C6 up for BLE when BLE is actually wanted. hostedInitBLE()
|
||||
// is a full esp-hosted handshake, and it ran unconditionally here even though
|
||||
// the boot banner says "lazy Wi-Fi/BLE initialization" -- so every boot paid
|
||||
// for it, and on a unit whose C6 speaks ESP-AT it started a transport that
|
||||
// can never come up for a radio the user had switched off. Honour the stored
|
||||
// intent the way beginBle() below already did.
|
||||
if (wifiConfigGetBleEnabled()) {
|
||||
if (hostedInitBLE()) {
|
||||
char* nm = the_mesh.getNodePrefs()->node_name;
|
||||
serial_interface.prepareBle("wadamesh-", nm, the_mesh.getBLEPin());
|
||||
serial_interface.beginBle("wadamesh-", nm, the_mesh.getBLEPin());
|
||||
} else {
|
||||
printf("[BOOT] hostedInitBLE FAILED\n");
|
||||
}
|
||||
} else {
|
||||
printf("[BOOT] hostedInitBLE FAILED\n");
|
||||
printf("[BOOT] BLE off in settings: C6 not started for it\n");
|
||||
}
|
||||
#endif
|
||||
// Cold-boot clock sync over SAVED Wi-Fi (#383, BootTimeSync.h) -- the same opt-in
|
||||
|
||||
@@ -35,6 +35,9 @@ namespace {
|
||||
|
||||
constexpr uint32_t kHotplugMs = 1500; // how often the expansion is looked for
|
||||
static bool s_scanned = false; // one-shot bus scan (see p4KeyboardPoll)
|
||||
constexpr uint32_t kHotplugIdleMs = 15000; // probe rate once the expansion looks absent
|
||||
constexpr uint8_t kMissesBeforeBackoff = 4;
|
||||
static uint8_t s_misses = 0; // consecutive probes that found nothing
|
||||
constexpr uint32_t kIdlePollMs = 120; // backstop drain when INT says nothing is waiting
|
||||
constexpr uint32_t kModTimeoutMs = 1500; // a one-shot modifier lapses after this
|
||||
constexpr uint32_t kRepeatDelayMs = 450;
|
||||
@@ -310,9 +313,19 @@ void p4KeyboardPoll() {
|
||||
if (probe || drain_due) {
|
||||
if (TwoWire* w = p4I2c1Acquire(P4_I2C1_KEYBOARD, kBusWaitMs)) {
|
||||
if (probe) {
|
||||
s_next_probe_ms = now + kHotplugMs;
|
||||
// Back off while nothing is there. The probe is a real I2C transaction,
|
||||
// and on an empty bus Arduino's Wire layer logs TWO [E] lines for every
|
||||
// attempt -- at 1.5 s that is a permanent drip down the same USB-CDC the
|
||||
// companion app talks over, for a board most people have never clipped
|
||||
// on. After a few misses, look every kHotplugIdleMs instead; a hit puts
|
||||
// it straight back to the responsive rate, so hot-plug still works, it
|
||||
// just takes up to that long to notice.
|
||||
s_next_probe_ms = now + (s_misses >= kMissesBeforeBackoff ? kHotplugIdleMs
|
||||
: kHotplugMs);
|
||||
uint8_t cfg = 0;
|
||||
const bool there = regRead(w, P4KB_EXP_ADDR, XL_CFG0, cfg);
|
||||
if (there) s_misses = 0;
|
||||
else if (s_misses < kMissesBeforeBackoff) ++s_misses;
|
||||
// One-shot bus scan on the first probe. "It does not work" on this
|
||||
// expansion has three very different causes and they are impossible to
|
||||
// tell apart from the outside: the board is not clipped on, it is
|
||||
|
||||
Reference in new issue
Block a user