Skip to content

Commit 02b2206

Browse files
committed
pbio/drv/usb_ev3: Fix lockup for stdout.
Receiving a command such as stop while stdout is active locks up Pybricks Code. This makes the logic a bit more similar to the STM32 version to ensure only one thing is happening at once. Fixes pybricks/support#2374
1 parent 46b8766 commit 02b2206

File tree

1 file changed

+135
-120
lines changed

1 file changed

+135
-120
lines changed

lib/pbio/drv/usb/usb_ev3.c

Lines changed: 135 additions & 120 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,18 +858,15 @@ 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

873868
// Multiple TX completions can happen at once
874-
// (since we have up to three descriptors in flight)
869+
// (since we have multiple descriptors in flight)
875870
dma_q_pend_0 = HWREG(USB_0_OTGBASE + CPDMA_PEND_0_REGISTER);
876871
}
877872

@@ -906,122 +901,148 @@ 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+
// True if we are expected to send a response to a recent incoming message.
905+
static bool usb_send_response;
910906

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;
907+
/**
908+
* Non-blocking poll handler to process pending incoming messages.
909+
*/
910+
static void pbdrv_usb_handle_data_in(void) {
911+
if (!usb_rx_is_ready) {
912+
return;
913+
}
917914

918-
PBIO_OS_ASYNC_BEGIN(state);
915+
// This barrier prevents *subsequent* memory reads from being
916+
// speculatively moved *earlier*, outside the if statement
917+
// (which is technically allowed by the as-if rule).
918+
pbdrv_compiler_memory_barrier();
919919

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;
920+
uint32_t usb_rx_sz = PBDRV_UNCACHED(cppi_descriptors[CPPI_DESC_RX].word0).pktLength;
921+
pbio_pybricks_error_t result;
922+
923+
// Skip empty commands.
924+
if (usb_rx_sz) {
925+
pbdrv_cache_prepare_after_dma(ep1_rx_buf, sizeof(ep1_rx_buf));
926+
927+
switch (ep1_rx_buf[0]) {
928+
case PBIO_PYBRICKS_OUT_EP_MSG_SUBSCRIBE:
929+
pbdrv_usb_is_events_subscribed = ep1_rx_buf[1];
930+
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
931+
pbio_set_uint32_le(&ep1_tx_response_buf[1], PBIO_PYBRICKS_ERROR_OK);
932+
usb_send_response = true;
933+
// Request to resend status flags now that the host has subscribed.
934+
// Our response will take priority, status follows right after.
935+
pbdrv_usb_status_data_pending = true;
936+
break;
937+
case PBIO_PYBRICKS_OUT_EP_MSG_COMMAND:
938+
if (!pbdrv_usb_receive_handler) {
939+
break;
940+
}
941+
result = pbdrv_usb_receive_handler(ep1_rx_buf + 1, usb_rx_sz - 1);
942+
ep1_tx_response_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_RESPONSE;
943+
pbio_set_uint32_le(&ep1_tx_response_buf[1], result);
944+
usb_send_response = true;
945+
break;
925946
}
947+
}
926948

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;
957-
}
958-
}
949+
// Re-queue RX buffer after processing is complete
950+
usb_rx_is_ready = false;
951+
usb_setup_rx_dma_desc();
952+
}
959953

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-
}
954+
static pbio_error_t pbdrv_usb_handle_data_out(void) {
955+
956+
static pbio_os_timer_t transmit_timer;
965957

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

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))) {
964+
// Transmission has taken too long, so reset the state to allow
965+
// new transmissions. This can happen if the host stops reading
966+
// data for some reason. This need some time to complete, so delegate
967+
// the reset back to the process.
968+
return PBIO_ERROR_TIMEDOUT;
969+
}
970+
971+
// Transmit. Give priority to response, then status updates, then stdout.
972+
if (usb_send_response) {
973+
usb_send_response = false;
974+
transmitting = true;
975+
pbdrv_cache_prepare_before_dma(ep1_tx_response_buf, sizeof(ep1_tx_response_buf));
976+
usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t)));
977+
} else if (pbdrv_usb_is_events_subscribed) {
978+
if (pbdrv_usb_status_data_pending) {
979+
pbdrv_usb_status_data_pending = false;
974980
ep1_tx_status_buf[0] = PBIO_PYBRICKS_IN_EP_MSG_EVENT;
975981
uint32_t usb_status_sz = PBIO_PYBRICKS_USB_MESSAGE_SIZE(pbsys_status_get_status_report(&ep1_tx_status_buf[1]));
976982

977-
usb_tx_status_is_not_ready = true;
983+
transmitting = true;
978984
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);
980-
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;
985+
usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, ep1_tx_status_buf, usb_status_sz);
986+
} else if (ep1_tx_stdout_sz) {
987+
transmitting = true;
988+
pbdrv_cache_prepare_before_dma(ep1_tx_stdout_buf, sizeof(ep1_tx_stdout_buf));
989+
usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, ep1_tx_stdout_buf, ep1_tx_stdout_sz);
990+
ep1_tx_stdout_sz = 0;
990991
}
992+
}
991993

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);
994+
if (transmitting) {
995+
// If the FIFO isn't emptied quickly, then there probably isn't an
996+
// app anymore. This timer is used to detect such a condition.
997+
pbio_os_timer_set(&transmit_timer, 50);
998+
}
999+
1000+
return PBIO_SUCCESS;
1001+
}
1002+
1003+
static pbio_os_process_t pbdrv_usb_ev3_process;
1004+
1005+
static pbio_error_t pbdrv_usb_ev3_process_thread(pbio_os_state_t *state, void *context) {
1006+
1007+
static pbio_os_timer_t timer;
1008+
1009+
pbdrv_usb_handle_data_in();
1010+
1011+
if (pbdrv_usb_config == 0) {
1012+
pbdrv_usb_is_events_subscribed = false;
1013+
}
1014+
1015+
PBIO_OS_ASYNC_BEGIN(state);
1016+
1017+
// Process pending outgoing data until shutdown requested.
1018+
while (pbdrv_usb_ev3_process.request != PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL) {
1019+
1020+
PBIO_OS_ASYNC_SET_CHECKPOINT(state);
1021+
pbio_error_t err = pbdrv_usb_handle_data_out();
1022+
if (err == PBIO_SUCCESS) {
1023+
return PBIO_ERROR_AGAIN;
10141024
}
1015-
was_transmitting = is_transmitting;
10161025

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;
1026+
transmitting = false;
1027+
ep1_tx_stdout_sz = 0;
1028+
pbdrv_usb_is_events_subscribed = false;
1029+
1030+
// Flush _all_ TX packets
1031+
while (HWREGB(USB0_BASE + USB_O_TXCSRL1) & USB_TXCSRL1_TXRDY) {
1032+
HWREGB(USB0_BASE + USB_O_TXCSRL1) = USB_TXCSRL1_FLUSH;
1033+
// We need to wait a bit until the DMA refills the FIFO.
1034+
// There doesn't seem to be a good way to figure out if
1035+
// there are packets in flight *within* the DMA engine itself
1036+
// (i.e. no longer in the queue but in the transfer engine).
1037+
PBIO_OS_AWAIT_MS(state, &timer, 1);
1038+
}
10221039
}
10231040

1024-
PBIO_OS_ASYNC_END(PBIO_SUCCESS);
1041+
PBIO_OS_ASYNC_END(PBIO_ERROR_CANCELED);
1042+
}
1043+
1044+
void pbdrv_usb_deinit(void) {
1045+
pbio_os_process_make_request(&pbdrv_usb_ev3_process, PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL);
10251046
}
10261047

10271048
void pbdrv_usb_init(void) {
@@ -1110,10 +1131,6 @@ void pbdrv_usb_init(void) {
11101131
pbio_os_process_start(&pbdrv_usb_ev3_process, pbdrv_usb_ev3_process_thread, NULL);
11111132
}
11121133

1113-
void pbdrv_usb_deinit(void) {
1114-
// todo
1115-
}
1116-
11171134
pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) {
11181135
// This function is not used on EV3
11191136
return PBDRV_USB_BCD_NONE;
@@ -1142,7 +1159,7 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
11421159
uint8_t *ptr = ep1_tx_stdout_buf;
11431160
uint32_t ptr_len = pbdrv_usb_is_usb_hs ? PYBRICKS_EP_PKT_SZ_HS : PYBRICKS_EP_PKT_SZ_FS;
11441161

1145-
if (usb_tx_stdout_is_not_ready) {
1162+
if (transmitting) {
11461163
*size = 0;
11471164
return PBIO_ERROR_AGAIN;
11481165
}
@@ -1157,10 +1174,8 @@ pbio_error_t pbdrv_usb_stdout_tx(const uint8_t *data, uint32_t *size) {
11571174
*size = ptr_len;
11581175
}
11591176
memcpy(ptr, data, *size);
1160-
1161-
usb_tx_stdout_is_not_ready = true;
1162-
pbdrv_cache_prepare_before_dma(ep1_tx_stdout_buf, sizeof(ep1_tx_stdout_buf));
1163-
usb_setup_tx_dma_desc(CPPI_DESC_TX_STDOUT, ep1_tx_stdout_buf, 2 + *size);
1177+
ep1_tx_stdout_sz = 2 + *size;
1178+
pbio_os_request_poll();
11641179

11651180
return PBIO_SUCCESS;
11661181
}
@@ -1175,7 +1190,7 @@ uint32_t pbdrv_usb_stdout_tx_available(void) {
11751190
return UINT32_MAX;
11761191
}
11771192

1178-
if (usb_tx_stdout_is_not_ready) {
1193+
if (transmitting) {
11791194
return 0;
11801195
}
11811196

@@ -1187,7 +1202,7 @@ uint32_t pbdrv_usb_stdout_tx_available(void) {
11871202
}
11881203

11891204
bool pbdrv_usb_stdout_tx_is_idle(void) {
1190-
return !usb_tx_stdout_is_not_ready;
1205+
return !transmitting;
11911206
}
11921207

11931208
#endif // PBDRV_CONFIG_USB_EV3

0 commit comments

Comments
 (0)