Skip to content

esp32/machine_uart: Correctly manage UART queue and event task. #17135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

DvdGiessen
Copy link
Contributor

Summary

If the driver was reinitialised while there was already an event task running the queue that task is trying to receive from would be deleted, causing it to try to take a lock that no longer existed and deadlocking the CPU.

This change ensures the task is always shut down before recreating the queue and recreates the task afterwards.

It also:

  • Removes the task when the UART is deinitialized (which was previously missing).
  • Adds a check that no event task can be started when no queue exists (i.e. when the UART is already deinitialized or it's the built-in REPL UART which doesn't have a queue configured).
  • Adds a check to prevent reinitialising the UART driver unnecessarily (we only need to reinitialize it when the buffer sizes change).

Testing

Tested on three custom boards (1x original ESP32, 2x ESP32S3) to talk to three modem chips (1x Sequans, 2x different SIMCOM) all using the UART IRQ's to trigger the PPP.poll() method.

If the driver was reinitialised while there was
already an event task running the queue that task
is trying to receive from would be deleted,
causing it to try to take a lock that no longer
existed and deadlocking the CPU.

This change ensures the task is always shut down
before recreating the queue and recreates the task
afterwards.

It also removes the task when the UART is
deinitialized (which was previously missing), adds
a check that no event task can be started when no
queue exists, and adds a check to prevent
reinitialising the UART driver unnecessarily.

Signed-off-by: Daniël van de Giessen <daniel@dvdgiessen.nl>
@@ -59,6 +59,7 @@
#define UART_IRQ_BREAK (1 << UART_BREAK)
#define MP_UART_ALLOWED_FLAGS (UART_IRQ_RX | UART_IRQ_RXIDLE | UART_IRQ_BREAK)
#define RXIDLE_TIMER_MIN (5000) // 500 us
#define UART_QUEUE_SIZE (3)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This number 3 is the exact same magic number that was previously used when IRQ RXIDLE support was introduced in #14041. I only moved it to a define since it's now used in two places. I don't know why it's 3 and didn't see any mention in the previous MR about that, but decided to not change it since it seems to work fine.

mp_raise_ValueError(MP_ERROR_TEXT("UART does not support IRQs"));
}
#endif
if (self->uart_queue == NULL && args[MP_IRQ_ARG_INIT_handler].u_obj != mp_const_none) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does allow setting a None IRQ handler, which is to prevent cleanup code from raising errors needlessly.

For example if used with PPP when closing the PPP connection the cleanup will happen asynchronously triggered by a callback from the lwIP TCP/IP thread, which would normally unset the IRQ it previously configured on it's UART object. If the UART object was already cleaned up as well (deinited or GC collected) that cleanup callback would encounter a ValueError and subsequently crash because the lwIP thread can't handle a non-linear return. (The fact PPP/lwIP breaks like that is a separate issue this PR is not addressing.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant