Skip to content

Commit d33e81f

Browse files
committed
!wip: pbio/drv/usb_ev3: Fix lockup for stdout.
Receiving a command such as stop while stdout is active locks up Pybricks Code. This is a work-in-progress fix that makes the logic a bit more similar to the STM32 version. Fixes pybricks/support#2374
1 parent b385288 commit d33e81f

File tree

1 file changed

+127
-116
lines changed

1 file changed

+127
-116
lines changed

lib/pbio/drv/usb/usb_ev3.c

Lines changed: 127 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,11 @@ static uint8_t ep1_rx_buf[PYBRICKS_EP_PKT_SZ_HS] PBDRV_DMA_BUF;
225225
static uint8_t ep1_tx_response_buf[PYBRICKS_EP_PKT_SZ_HS];
226226
static uint8_t ep1_tx_status_buf[PYBRICKS_EP_PKT_SZ_HS];
227227
static uint8_t ep1_tx_stdout_buf[PYBRICKS_EP_PKT_SZ_HS];
228+
static uint32_t ep1_tx_stdout_sz;
228229

229230
// Buffer status flags
230231
static volatile bool usb_rx_is_ready;
231-
static volatile bool usb_tx_response_is_not_ready;
232-
static volatile bool usb_tx_status_is_not_ready;
233-
static volatile bool usb_tx_stdout_is_not_ready;
232+
static volatile bool transmitting;
234233

235234
// CPPI DMA support code
236235

@@ -261,8 +260,7 @@ _Static_assert(sizeof(usb_cppi_hpd_t) <= CPPI_DESCRIPTOR_ALIGN);
261260
enum {
262261
CPPI_DESC_RX,
263262
CPPI_DESC_TX_RESPONSE,
264-
CPPI_DESC_TX_STATUS,
265-
CPPI_DESC_TX_STDOUT,
263+
CPPI_DESC_TX_PYBRICKS_EVENT,
266264
// the minimum number of descriptors we can allocate is 32,
267265
// even though we do not use nearly all of them
268266
CPPI_DESC_COUNT = 32,
@@ -860,13 +858,10 @@ static void usb_device_intr(void) {
860858
uint32_t qctrld = HWREG(USB_0_OTGBASE + CPDMA_QUEUE_REGISTER_D + TX_COMPQ1 * 16) & ~CPDMA_QUEUE_REGISTER_DESC_SIZE_MASK;
861859

862860
if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_RESPONSE])) {
863-
usb_tx_response_is_not_ready = false;
861+
transmitting = false;
864862
pbio_os_request_poll();
865-
} else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_STATUS])) {
866-
usb_tx_status_is_not_ready = false;
867-
pbio_os_request_poll();
868-
} else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_STDOUT])) {
869-
usb_tx_stdout_is_not_ready = false;
863+
} else if (qctrld == (uint32_t)(&cppi_descriptors[CPPI_DESC_TX_PYBRICKS_EVENT])) {
864+
transmitting = false;
870865
pbio_os_request_poll();
871866
}
872867

@@ -906,124 +901,142 @@ void pbdrv_usb_schedule_status_update(const uint8_t *status_msg) {
906901
pbio_os_request_poll();
907902
}
908903

909-
static pbio_os_process_t pbdrv_usb_ev3_process;
904+
static bool usb_send_response;
910905

911-
static pbio_error_t pbdrv_usb_ev3_process_thread(pbio_os_state_t *state, void *context) {
912-
static pbio_os_timer_t delay_timer;
913-
static pbio_os_timer_t tx_timeout_timer;
914-
static pbio_os_timer_t keepalive_timer;
915-
static bool was_transmitting = false;
916-
static bool is_transmitting = false;
906+
/**
907+
* Non-blocking poll handler to process pending incoming messages.
908+
*/
909+
static void pbdrv_usb_handle_data_in(void) {
910+
if (!usb_rx_is_ready) {
911+
return;
912+
}
913+
// This barrier prevents *subsequent* memory reads from being
914+
// speculatively moved *earlier*, outside the if statement
915+
// (which is technically allowed by the as-if rule).
916+
pbdrv_compiler_memory_barrier();
917917

918-
PBIO_OS_ASYNC_BEGIN(state);
918+
uint32_t usb_rx_sz = PBDRV_UNCACHED(cppi_descriptors[CPPI_DESC_RX].word0).pktLength;
919+
pbio_pybricks_error_t result;
919920

920-
for (;;) {
921-
if (pbdrv_usb_config == 0) {
922-
pbdrv_usb_is_events_subscribed = false;
923-
// Resend status flags when host subscribes
924-
pbdrv_usb_status_data_pending = true;
925-
}
921+
// Skip empty commands
922+
if (usb_rx_sz) {
923+
pbdrv_cache_prepare_after_dma(ep1_rx_buf, sizeof(ep1_rx_buf));
926924

927-
if (usb_rx_is_ready) {
928-
// This barrier prevents *subsequent* memory reads from being
929-
// speculatively moved *earlier*, outside the if statement
930-
// (which is technically allowed by the as-if rule).
931-
pbdrv_compiler_memory_barrier();
932-
933-
uint32_t usb_rx_sz = PBDRV_UNCACHED(cppi_descriptors[CPPI_DESC_RX].word0).pktLength;
934-
pbio_pybricks_error_t result;
935-
bool usb_send_response = false;
936-
// Skip empty commands, or commands sent when previous response is still busy
937-
if (usb_rx_sz && !usb_tx_response_is_not_ready) {
938-
pbdrv_cache_prepare_after_dma(ep1_rx_buf, sizeof(ep1_rx_buf));
939-
940-
switch (ep1_rx_buf[0]) {
941-
case PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE:
942-
pbio_os_timer_set(&keepalive_timer, 1000);
943-
pbdrv_usb_is_events_subscribed = ep1_rx_buf[1];
944-
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
945-
pbio_set_uint32_le(&ep1_tx_response_buf[1], PBIO_PYBRICKS_ERROR_OK);
946-
usb_send_response = true;
947-
break;
948-
case PBIO_PYBRICKS_OUT_EP_MSG_COMMAND:
949-
if (!pbdrv_usb_receive_handler) {
950-
break;
951-
}
952-
result = pbdrv_usb_receive_handler(ep1_rx_buf + 1, usb_rx_sz - 1);
953-
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
954-
pbio_set_uint32_le(&ep1_tx_response_buf[1], result);
955-
usb_send_response = true;
956-
break;
925+
switch (ep1_rx_buf[0]) {
926+
case PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE:
927+
pbdrv_usb_is_events_subscribed = ep1_rx_buf[1];
928+
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
929+
pbio_set_uint32_le(&ep1_tx_response_buf[1], PBIO_PYBRICKS_ERROR_OK);
930+
usb_send_response = true;
931+
// Resend status flags when host subscribes
932+
pbdrv_usb_status_data_pending = true;
933+
break;
934+
case PBIO_PYBRICKS_OUT_EP_MSG_COMMAND:
935+
if (!pbdrv_usb_receive_handler) {
936+
break;
957937
}
958-
}
938+
result = pbdrv_usb_receive_handler(ep1_rx_buf + 1, usb_rx_sz - 1);
939+
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
940+
pbio_set_uint32_le(&ep1_tx_response_buf[1], result);
941+
usb_send_response = true;
942+
break;
943+
}
944+
}
959945

960-
if (usb_send_response) {
961-
usb_tx_response_is_not_ready = true;
962-
pbdrv_cache_prepare_before_dma(ep1_tx_response_buf, sizeof(ep1_tx_response_buf));
963-
usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t)));
964-
}
946+
// Re-queue RX buffer after processing is complete
947+
usb_rx_is_ready = false;
948+
usb_setup_rx_dma_desc();
949+
}
950+
951+
static pbio_error_t pbdrv_usb_handle_data_out(void) {
952+
953+
static pbio_os_timer_t transmit_timer;
965954

966-
// Re-queue RX buffer after processing is complete
967-
usb_rx_is_ready = false;
968-
usb_setup_rx_dma_desc();
955+
if (transmitting) {
956+
if (!pbio_os_timer_is_expired(&transmit_timer)) {
957+
// Still transmitting, can't do anything for now.
958+
return PBIO_SUCCESS;
969959
}
970960

971-
// Send status flags if they've changed (and we can)
972-
if (pbdrv_usb_is_events_subscribed && !usb_tx_status_is_not_ready &&
973-
(pbdrv_usb_status_data_pending || pbio_os_timer_is_expired(&keepalive_timer))) {
974-
ep1_tx_status_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_EVENT;
975-
uint32_t usb_status_sz = PBIO_PYBRICKS_USB_MESSAGE_SIZE(pbsys_status_get_status_report(&ep1_tx_status_buf[1]));
961+
// Transmission has taken too long, so reset the state to allow
962+
// new transmissions. This can happen if the host stops reading
963+
// data for some reason.
964+
return PBIO_ERROR_TIMEDOUT;
965+
}
976966

977-
usb_tx_status_is_not_ready = true;
978-
pbdrv_cache_prepare_before_dma(ep1_tx_status_buf, sizeof(ep1_tx_status_buf));
979-
usb_setup_tx_dma_desc(CPPI_DESC_TX_STATUS, ep1_tx_status_buf, usb_status_sz);
967+
// Transmit. Give priority to response, then status updates, then stdout.
968+
if (usb_send_response) {
969+
usb_send_response = false;
970+
transmitting = true;
971+
pbdrv_cache_prepare_before_dma(ep1_tx_response_buf, sizeof(ep1_tx_response_buf));
972+
usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t)));
973+
} else if (pbdrv_usb_is_events_subscribed && pbdrv_usb_status_data_pending) {
974+
pbdrv_usb_status_data_pending = false;
975+
ep1_tx_status_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_EVENT;
976+
uint32_t usb_status_sz = PBIO_PYBRICKS_USB_MESSAGE_SIZE(pbsys_status_get_status_report(&ep1_tx_status_buf[1]));
977+
978+
transmitting = true;
979+
pbdrv_cache_prepare_before_dma(ep1_tx_status_buf, sizeof(ep1_tx_status_buf));
980+
usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, ep1_tx_status_buf, usb_status_sz);
981+
} else if (ep1_tx_stdout_sz) {
982+
transmitting = true;
983+
pbdrv_cache_prepare_before_dma(ep1_tx_stdout_buf, sizeof(ep1_tx_stdout_buf));
984+
usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, ep1_tx_stdout_buf, ep1_tx_stdout_sz);
985+
ep1_tx_stdout_sz = 0;
986+
}
980987

981-
if (pbdrv_usb_status_data_pending) {
982-
// If we are sending a status because the flags have changed,
983-
// we can bump out the keepalive timer.
984-
pbio_os_timer_set(&keepalive_timer, 1000);
985-
} else {
986-
// Otherwise, we want to send keepalives at a particular rate.
987-
pbio_os_timer_extend(&keepalive_timer);
988-
}
989-
pbdrv_usb_status_data_pending = false;
988+
if (transmitting) {
989+
// If the FIFO isn't emptied quickly, then there probably isn't an
990+
// app anymore. This timer is used to detect such a condition.
991+
pbio_os_timer_set(&transmit_timer, 50);
992+
}
993+
return PBIO_SUCCESS;
994+
}
995+
996+
static pbio_os_process_t pbdrv_usb_ev3_process;
997+
998+
static pbio_error_t pbdrv_usb_ev3_process_thread(pbio_os_state_t *state, void *context) {
999+
1000+
static pbio_os_timer_t timer;
1001+
1002+
pbdrv_usb_handle_data_in();
1003+
1004+
if (pbdrv_usb_config == 0) {
1005+
pbdrv_usb_is_events_subscribed = false;
1006+
}
1007+
1008+
PBIO_OS_ASYNC_BEGIN(state);
1009+
1010+
// REVISIT: Exit on pbdrv_usb_deinit
1011+
for (;;) {
1012+
1013+
PBIO_OS_ASYNC_SET_CHECKPOINT(state);
1014+
pbio_error_t err = pbdrv_usb_handle_data_out();
1015+
if (err == PBIO_SUCCESS) {
1016+
return PBIO_ERROR_AGAIN;
9901017
}
9911018

992-
// Handle timeouts
993-
is_transmitting = usb_tx_response_is_not_ready || usb_tx_status_is_not_ready || usb_tx_stdout_is_not_ready;
994-
if (was_transmitting && is_transmitting) {
995-
if (pbio_os_timer_is_expired(&tx_timeout_timer)) {
996-
// Flush _all_ TX packets
997-
while (HWREGB(USB0_BASE + USB_O_TXCSRL1) & USB_TXCSRL1_TXRDY) {
998-
HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_FLUSH;
999-
// We need to wait a bit until the DMA refills the FIFO.
1000-
// There doesn't seem to be a good way to figure out if
1001-
// there are packets in flight *within* the DMA engine itself
1002-
// (i.e. no longer in the queue but in the transfer engine).
1003-
PBIO_OS_AWAIT_MS(state, &delay_timer, 1);
1004-
}
1005-
usb_tx_response_is_not_ready = false;
1006-
usb_tx_status_is_not_ready = false;
1007-
usb_tx_stdout_is_not_ready = false;
1008-
pbdrv_usb_is_events_subscribed = false;
1009-
// Resend status flags when host subscribes
1010-
pbdrv_usb_status_data_pending = true;
1011-
}
1012-
} else if (!was_transmitting && is_transmitting) {
1013-
pbio_os_timer_set(&tx_timeout_timer, 50);
1019+
// Flush _all_ TX packets
1020+
while (HWREGB(USB0_BASE + USB_O_TXCSRL1) & USB_TXCSRL1_TXRDY) {
1021+
HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_FLUSH;
1022+
// We need to wait a bit until the DMA refills the FIFO.
1023+
// There doesn't seem to be a good way to figure out if
1024+
// there are packets in flight *within* the DMA engine itself
1025+
// (i.e. no longer in the queue but in the transfer engine).
1026+
PBIO_OS_AWAIT_MS(state, &timer, 1);
10141027
}
1015-
was_transmitting = is_transmitting;
1028+
transmitting = false;
1029+
pbdrv_usb_is_events_subscribed = false;
10161030

1017-
// Make this process loop run from the beginning.
1018-
// Because we check so many different conditions,
1019-
// we don't use the normal await macros.
1020-
PBIO_OS_ASYNC_RESET(state);
1021-
return PBIO_ERROR_AGAIN;
10221031
}
10231032

10241033
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
10251034
}
10261035

1036+
void pbdrv_usb_deinit(void) {
1037+
// todo
1038+
}
1039+
10271040
void pbdrv_usb_init(void) {
10281041
// If we came straight from a firmware update, we need to send a disconnect
10291042
// to the host, then reset the USB controller.
@@ -1138,7 +1151,7 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
11381151
uint8_t *ptr = ep1_tx_stdout_buf;
11391152
uint32_t ptr_len = pbdrv_usb_is_usb_hs ? PYBRICKS_EP_PKT_SZ_HS : PYBRICKS_EP_PKT_SZ_FS;
11401153

1141-
if (usb_tx_stdout_is_not_ready) {
1154+
if (transmitting) {
11421155
*size = 0;
11431156
return PBIO_ERROR_AGAIN;
11441157
}
@@ -1153,10 +1166,8 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
11531166
*size = ptr_len;
11541167
}
11551168
memcpy(ptr, data, *size);
1156-
1157-
usb_tx_stdout_is_not_ready = true;
1158-
pbdrv_cache_prepare_before_dma(ep1_tx_stdout_buf, sizeof(ep1_tx_stdout_buf));
1159-
usb_setup_tx_dma_desc(CPPI_DESC_TX_STDOUT, ep1_tx_stdout_buf, 2 + *size);
1169+
ep1_tx_stdout_sz = 2 + *size;
1170+
pbio_os_request_poll();
11601171

11611172
return PBIO_SUCCESS;
11621173
}
@@ -1171,7 +1182,7 @@ uint32_t pbdrv_usb_stdout_tx_available(void) {
11711182
return UINT32_MAX;
11721183
}
11731184

1174-
if (usb_tx_stdout_is_not_ready) {
1185+
if (transmitting) {
11751186
return 0;
11761187
}
11771188

@@ -1183,7 +1194,7 @@ uint32_t pbdrv_usb_stdout_tx_available(void) {
11831194
}
11841195

11851196
bool pbdrv_usb_stdout_tx_is_idle(void) {
1186-
return !usb_tx_stdout_is_not_ready;
1197+
return !transmitting;
11871198
}
11881199

11891200
#endif // PBDRV_CONFIG_USB_EV3

0 commit comments

Comments
 (0)