rp2/mphalport: Fix USB CDC RX handling to not block when unprocessed.

Prior to this commit, the USB CDC OUT endpoint got NACK'd if a character
was received but not consumed by the application, e.g. via
sys.stdin.read().  This meant that USB CDC was blocked and no additional
characters could be sent from the host.  In particular a ctrl-C could not
interrupt the application if another character was pending.

To fix the issue, the approach in this commit uses a callback tud_cdc_rx_cb
which is called by the TinyUSB stack on reception of new CDC data.  By
consuming the data immediately, the endpoint does not stall anymore.  The
previous handler tud_cdc_rx_wanted_cb was made obsolete and removed.

In addition some cleanup was done along the way: by adding interrupt_char.c
and removing the existing code mp_hal_set_interrupt_char().  Also, there is
now only one (stdin) ringbuffer.

Fixes issue #7996.
This commit is contained in:
Martin Fischer 2022-03-30 23:00:46 +02:00 committed by Damien George
parent 5682595d75
commit 5873390226
2 changed files with 46 additions and 35 deletions

View File

@ -72,6 +72,7 @@ set(MICROPY_SOURCE_LIB
${MICROPY_DIR}/shared/readline/readline.c
${MICROPY_DIR}/shared/runtime/gchelper_m0.s
${MICROPY_DIR}/shared/runtime/gchelper_native.c
${MICROPY_DIR}/shared/runtime/interrupt_char.c
${MICROPY_DIR}/shared/runtime/mpirq.c
${MICROPY_DIR}/shared/runtime/pyexec.c
${MICROPY_DIR}/shared/runtime/stdout_helpers.c

View File

@ -28,50 +28,69 @@
#include "py/stream.h"
#include "py/mphal.h"
#include "extmod/misc.h"
#include "shared/runtime/interrupt_char.h"
#include "shared/timeutils/timeutils.h"
#include "tusb.h"
#include "uart.h"
#include "hardware/rtc.h"
#if MICROPY_HW_ENABLE_UART_REPL
#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_ENABLE_USBDEV
#ifndef UART_BUFFER_LEN
// reasonably big so we can paste
#define UART_BUFFER_LEN 256
#ifndef MICROPY_HW_STDIN_BUFFER_LEN
#define MICROPY_HW_STDIN_BUFFER_LEN 512
#endif
STATIC uint8_t stdin_ringbuf_array[UART_BUFFER_LEN];
STATIC uint8_t stdin_ringbuf_array[MICROPY_HW_STDIN_BUFFER_LEN];
ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array) };
#endif
#if MICROPY_KBD_EXCEPTION
#if MICROPY_HW_ENABLE_USBDEV
int mp_interrupt_char = -1;
uint8_t cdc_itf_pending; // keep track of cdc interfaces which need attention to poll
void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) {
(void)itf;
(void)wanted_char;
tud_cdc_read_char(); // discard interrupt char
mp_sched_keyboard_interrupt();
void poll_cdc_interfaces(void) {
// any CDC interfaces left to poll?
if (cdc_itf_pending && ringbuf_free(&stdin_ringbuf)) {
for (uint8_t itf = 0; itf < 8; ++itf) {
if (cdc_itf_pending & (1 << itf)) {
tud_cdc_rx_cb(itf);
if (!cdc_itf_pending) {
break;
}
}
}
}
}
void mp_hal_set_interrupt_char(int c) {
mp_interrupt_char = c;
tud_cdc_set_wanted_char(c);
void tud_cdc_rx_cb(uint8_t itf) {
// consume pending USB data immediately to free usb buffer and keep the endpoint from stalling.
// in case the ringbuffer is full, mark the CDC interface that need attention later on for polling
cdc_itf_pending &= ~(1 << itf);
for (uint32_t bytes_avail = tud_cdc_n_available(itf); bytes_avail > 0; --bytes_avail) {
if (ringbuf_free(&stdin_ringbuf)) {
int data_char = tud_cdc_read_char();
if (data_char == mp_interrupt_char) {
mp_sched_keyboard_interrupt();
} else {
ringbuf_put(&stdin_ringbuf, data_char);
}
} else {
cdc_itf_pending |= (1 << itf);
return;
}
}
}
#endif
uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
uintptr_t ret = 0;
#if MICROPY_HW_ENABLE_UART_REPL
if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) {
ret |= MP_STREAM_POLL_RD;
}
#endif
#if MICROPY_HW_ENABLE_USBDEV
if (tud_cdc_connected() && tud_cdc_available()) {
poll_cdc_interfaces();
#endif
#if MICROPY_HW_ENABLE_UART_REPL || MICROPY_HW_ENABLE_USBDEV
if ((poll_flags & MP_STREAM_POLL_RD) && ringbuf_peek(&stdin_ringbuf) != -1) {
ret |= MP_STREAM_POLL_RD;
}
#endif
@ -84,21 +103,14 @@ uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
// Receive single character
int mp_hal_stdin_rx_chr(void) {
for (;;) {
#if MICROPY_HW_ENABLE_UART_REPL
#if MICROPY_HW_ENABLE_USBDEV
poll_cdc_interfaces();
#endif
int c = ringbuf_get(&stdin_ringbuf);
if (c != -1) {
return c;
}
#endif
#if MICROPY_HW_ENABLE_USBDEV
if (tud_cdc_connected() && tud_cdc_available()) {
uint8_t buf[1];
uint32_t count = tud_cdc_read(buf, sizeof(buf));
if (count) {
return buf[0];
}
}
#endif
#if MICROPY_PY_OS_DUPTERM
int dupterm_c = mp_uos_dupterm_rx_chr();
if (dupterm_c >= 0) {
@ -123,11 +135,9 @@ void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
n = CFG_TUD_CDC_EP_BUFSIZE;
}
while (n > tud_cdc_write_available()) {
tud_task();
tud_cdc_write_flush();
MICROPY_EVENT_POLL_HOOK
}
uint32_t n2 = tud_cdc_write(str + i, n);
tud_task();
tud_cdc_write_flush();
i += n2;
}