Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion ports/espressif/supervisor/usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,59 @@
#include "hal/usb_serial_jtag_ll.h"
#endif

#if CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC
#include "py/ringbuf.h"
#include "shared-bindings/microcontroller/__init__.h"
#include "shared-module/usb_cdc/__init__.h"

// tud_task() runs in the usbd task below. Reading the TinyUSB fifo from the VM
// task could re-arm the CDC OUT endpoint while tud_task() is still copying the
// previous packet out of it, so console input is staged in this ringbuf on the
// usbd task. Both tasks use it, so every access runs with interrupts disabled.
static uint8_t _cdc_rx_buf[256];
static ringbuf_t _cdc_rx_ringbuf = { .buf = _cdc_rx_buf, .size = sizeof(_cdc_rx_buf) };

void usb_cdc_rx_drain(void) {
if (!usb_cdc_console_enabled()) {
return;
}
uint8_t chunk[64];
while (tud_cdc_available() > 0) {
common_hal_mcu_disable_interrupts();
size_t room = ringbuf_num_empty(&_cdc_rx_ringbuf);
common_hal_mcu_enable_interrupts();
if (room == 0) {
return;
}
// tud_cdc_read() can wait on the TinyUSB fifo mutex, so it runs with interrupts enabled.
uint32_t count = tud_cdc_read(chunk, MIN(room, sizeof(chunk)));
common_hal_mcu_disable_interrupts();
ringbuf_put_n(&_cdc_rx_ringbuf, chunk, count);
common_hal_mcu_enable_interrupts();
}
}

size_t usb_cdc_rx_read(uint8_t *data, size_t len) {
common_hal_mcu_disable_interrupts();
size_t count = ringbuf_get_n(&_cdc_rx_ringbuf, data, len);
common_hal_mcu_enable_interrupts();
return count;
}

size_t usb_cdc_rx_available(void) {
common_hal_mcu_disable_interrupts();
size_t count = ringbuf_num_filled(&_cdc_rx_ringbuf);
common_hal_mcu_enable_interrupts();
return count;
}

void usb_cdc_rx_clear(void) {
common_hal_mcu_disable_interrupts();
ringbuf_clear(&_cdc_rx_ringbuf);
common_hal_mcu_enable_interrupts();
}
#endif // CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC

#if CIRCUITPY_USB_DEVICE
#ifdef CFG_TUSB_DEBUG
#define USBD_STACK_SIZE (3 * configMINIMAL_STACK_SIZE)
Expand All @@ -51,8 +104,12 @@ static void usb_device_task(void *param) {
while (1) {
// tinyusb device task
if (tusb_inited()) {
tud_task();
// Time out so console input left in the fifo while the ringbuf was full is moved in.
tud_task_ext(10, false);
tud_cdc_write_flush();
#if CIRCUITPY_USB_CDC
usb_cdc_rx_drain();
#endif
}
// Yield with zero delay to switch to any other tasks at same priority.
port_task_yield();
Expand Down
24 changes: 22 additions & 2 deletions shared-module/usb_cdc/Serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,25 @@

#include "shared/runtime/interrupt_char.h"
#include "shared-bindings/usb_cdc/Serial.h"
#include "shared-module/usb_cdc/__init__.h"
#include "shared-module/usb_cdc/Serial.h"
#include "supervisor/shared/tick.h"
#include "supervisor/usb.h"

#include "tusb.h"

// Console input goes through usb_cdc_rx_*, which a port may stage elsewhere.
static bool _is_console(usb_cdc_serial_obj_t *self) {
return self->idx == 0 && usb_cdc_console_enabled();
}

static uint32_t _read(usb_cdc_serial_obj_t *self, uint8_t *data, size_t len) {
if (_is_console(self)) {
return usb_cdc_rx_read(data, len);
}
return tud_cdc_n_read(self->idx, data, len);
}

size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data, size_t len, int *errcode) {

const bool wait_forever = self->timeout < 0.0f;
Expand All @@ -19,7 +33,7 @@ size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data,
// Read up to len bytes immediately.
// The number of bytes read will not be larger than what is already in the TinyUSB FIFO.
uint32_t total_num_read = 0;
total_num_read = tud_cdc_n_read(self->idx, data, len);
total_num_read = _read(self, data, len);

if (wait_forever || wait_for_timeout) {
// Continue filling the buffer past what we already read.
Expand All @@ -46,7 +60,7 @@ size_t common_hal_usb_cdc_serial_read(usb_cdc_serial_obj_t *self, uint8_t *data,
data += num_read;

// Try to read another batch of bytes.
num_read = tud_cdc_n_read(self->idx, data, len);
num_read = _read(self, data, len);
total_num_read += num_read;
}
}
Expand Down Expand Up @@ -98,6 +112,9 @@ size_t common_hal_usb_cdc_serial_write(usb_cdc_serial_obj_t *self, const uint8_t
}

uint32_t common_hal_usb_cdc_serial_get_in_waiting(usb_cdc_serial_obj_t *self) {
if (_is_console(self)) {
return usb_cdc_rx_available();
}
return tud_cdc_n_available(self->idx);
}

Expand All @@ -108,6 +125,9 @@ uint32_t common_hal_usb_cdc_serial_get_out_waiting(usb_cdc_serial_obj_t *self) {

void common_hal_usb_cdc_serial_reset_input_buffer(usb_cdc_serial_obj_t *self) {
tud_cdc_n_read_flush(self->idx);
if (_is_console(self)) {
usb_cdc_rx_clear();
}
}

uint32_t common_hal_usb_cdc_serial_reset_output_buffer(usb_cdc_serial_obj_t *self) {
Expand Down
9 changes: 6 additions & 3 deletions supervisor/shared/serial.c
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,11 @@ char serial_read(void) {
return -1;
}
#endif
#if CIRCUITPY_TINYUSB && CIRCUITPY_USB_DEVICE
return (char)tud_cdc_read_char();
#if CIRCUITPY_TINYUSB && CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC
uint8_t c;
if (usb_cdc_rx_read(&c, 1) == 1) {
return c;
}
#endif

return -1;
Expand Down Expand Up @@ -362,7 +365,7 @@ uint32_t serial_bytes_available(void) {

#if CIRCUITPY_TINYUSB && CIRCUITPY_USB_DEVICE && CIRCUITPY_USB_CDC
if (usb_cdc_console_enabled()) {
count += tud_cdc_available();
count += usb_cdc_rx_available();
}
#endif

Expand Down
19 changes: 19 additions & 0 deletions supervisor/shared/usb/usb_device.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,23 @@ bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, tusb_control_requ
#endif // CIRCUITPY_USB_VENDOR


#if CIRCUITPY_USB_CDC
// Defaults: tud_task() runs on the VM task here, so the fifo is read directly.
MP_WEAK void usb_cdc_rx_drain(void) {
}

MP_WEAK size_t usb_cdc_rx_read(uint8_t *data, size_t len) {
return tud_cdc_read(data, len);
}

MP_WEAK size_t usb_cdc_rx_available(void) {
return tud_cdc_available();
}

MP_WEAK void usb_cdc_rx_clear(void) {
}
#endif

#if MICROPY_KBD_EXCEPTION && CIRCUITPY_USB_CDC

// The CDC RX buffer impacts monitoring for ctrl-c. TinyUSB will only ask for
Expand All @@ -173,6 +190,7 @@ void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char) {
// Compare mp_interrupt_char with wanted_char and ignore if not matched
if (mp_interrupt_char == wanted_char) {
tud_cdc_n_read_flush(itf); // flush read fifo
usb_cdc_rx_clear();
mp_sched_keyboard_interrupt();
}
}
Expand All @@ -186,6 +204,7 @@ void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms) {

void tud_cdc_rx_cb(uint8_t itf) {
(void)itf;
usb_cdc_rx_drain();
// Workaround for "press any key to enter REPL" response being delayed on espressif.
// Wake main task when any key is pressed.
port_wake_main_task();
Expand Down
10 changes: 10 additions & 0 deletions supervisor/usb.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ void usb_background(void);
// Schedule usb background
void usb_background_schedule(void);

// Console (CDC interface 0) input. Defaults read the TinyUSB fifo directly; a port
// whose tud_task() runs on another task overrides them and stages input there.
// usb_cdc_rx_drain() runs on that task, the other three on the VM task.
// read() fills *data with up to len bytes and returns the count, available()
// returns the pending count, both 0 when empty. clear() drops staged input only.
void usb_cdc_rx_drain(void);
size_t usb_cdc_rx_read(uint8_t *data, size_t len);
size_t usb_cdc_rx_available(void);
void usb_cdc_rx_clear(void);

// Ports must call this from their particular USB IRQ handler
void usb_irq_handler(int instance);

Expand Down
Loading