A null EXCVADDR (0x00000000) is the key diagnostic for null pointer
crashes (LoadProhibited/StoreProhibited). Suppressing it when zero
hides the most useful information.
Add a crash handler for ESP8266 that captures and logs crash data
from previous boots, matching the existing ESP32 and RP2040
implementations. Uses the SDK's rst_info (always available after
reboot) for basic crash info and Arduino's custom_crash_callback
to scan the stack for return addresses stored in RTC user memory.
On RP2040, the core0 stack lives in a 4KB SCRATCH_Y RAM bank. The Noise
protocol handshake calls into curve25519 scalar multiplication which
needs ~2KB+ of stack. When all handshake state branches lived in a
single function, the compiler allocated stack space for all branches
simultaneously (SERVER_HELLO msg[46]+mac[13], HANDSHAKE buffer[65],
NoiseBuffer, etc.), leaving insufficient headroom for the crypto path.
Split state_action_() into per-state methods so each state's locals
occupy separate stack frames. The crypto path (WRITE_MESSAGE) now only
carries its own 85 bytes of locals instead of ~200+ bytes from all
branches combined.
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Switch to blocking mode before writing the feature ACK byte in the
no-password path. lwIP may not transmit data written in non-blocking
mode when the socket immediately switches to blocking and blocks in
recv(). Writing the feature ACK in blocking mode ensures it is flushed
to the wire before entering the blocking data transfer phase.
Add delay(0) after switching to blocking mode in handle_data_() to
flush any pending non-blocking writes (the feature ACK byte) before
entering the blocking read loop. lwIP may not transmit data written
in non-blocking mode until the task yields.
When an expander has only output pins configured, loop() was still
running every iteration to invalidate the read cache. Since no
digital_read() calls will ever be made, this work is wasted.
Now disable_loop() is called unconditionally in setup(). For polling
mode, loop is re-enabled only when pin_mode() registers an input pin.
For interrupt-driven mode, behavior is unchanged as the ISR already
manages loop enablement.