From 17876bb3a1f5bb02d951460feef3dc87bd32aa0c Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 24 Jan 2026 18:17:44 -0600 Subject: [PATCH 1/2] drv/usb: Remove USB tx timeout. Remove the USB tx timeout from all drivers. There currently isn't a way to recover from a timeout, so it just breaks the connection. Instead, now, e.g. print statements will block forever if an application disappears without unsubscribing first. This can happen, e.g. if someone closes the browser tab running Pybricks Code without disconnecting USB first (either the cable or using the button in Pybricks Code). This isn't ideal behavior, but we will address what to do about that at a later time. --- lib/pbio/drv/usb/usb.c | 4 ++-- lib/pbio/drv/usb/usb.h | 14 ++++++------ lib/pbio/drv/usb/usb_ev3.c | 27 +++++++---------------- lib/pbio/drv/usb/usb_nxt.c | 22 +++++++------------ lib/pbio/drv/usb/usb_simulation.c | 4 ++-- lib/pbio/drv/usb/usb_simulation_pico.c | 9 +++++--- lib/pbio/drv/usb/usb_stm32.c | 30 ++++++++++++++------------ 7 files changed, 50 insertions(+), 60 deletions(-) diff --git a/lib/pbio/drv/usb/usb.c b/lib/pbio/drv/usb/usb.c index fda7e63fd..52b93417c 100644 --- a/lib/pbio/drv/usb/usb.c +++ b/lib/pbio/drv/usb/usb.c @@ -294,14 +294,14 @@ static pbio_error_t pbdrv_usb_process_thread(pbio_os_state_t *state, void *conte pbdrv_usb_respond_soon = false; // Send the response. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_response(&sub, error_code)); + PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_response(&sub, error_code, pbdrv_usb_process.request == PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL)); if (err != PBIO_SUCCESS) { pbdrv_usb_reset_state(); PBIO_OS_AWAIT(state, &sub, pbdrv_usb_tx_reset(&sub)); } } else if (pbdrv_usb_connection_is_active() && update_and_get_event_buffer(¬i_buf, ¬i_size)) { // Send out pending event if any. - PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_event(&sub, noti_buf, *noti_size)); + PBIO_OS_AWAIT(state, &sub, err = pbdrv_usb_tx_event(&sub, noti_buf, *noti_size, pbdrv_usb_process.request == PBIO_OS_PROCESS_REQUEST_TYPE_CANCEL)); *noti_size = 0; if (err != PBIO_SUCCESS) { pbdrv_usb_reset_state(); diff --git a/lib/pbio/drv/usb/usb.h b/lib/pbio/drv/usb/usb.h index 81a4863b5..1bf67a440 100644 --- a/lib/pbio/drv/usb/usb.h +++ b/lib/pbio/drv/usb/usb.h @@ -51,7 +51,7 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data); /** * Sends and awaits event message from hub to host via the Pybricks USB interface OUT endpoint. * - * Driver-specific implementation. Must return within ::PBDRV_USB_TRANSMIT_TIMEOUT. + * Driver-specific implementation. Must support being canceled via @p cancel. * * The USB process ensures that only one call is made at once. * @@ -60,31 +60,33 @@ uint32_t pbdrv_usb_get_data_and_start_receive(uint8_t *data); * @param [in] state Protothread state. * @param [in] data Data to send. * @param [in] size Data size. + * @param [in] cancel If true, stop waiting for completion and return error. * @return ::PBIO_SUCCESS on completion. * ::PBIO_ERROR_INVALID_OP if there is no connection. * ::PBIO_ERROR_AGAIN while awaiting. * ::PBIO_ERROR_BUSY if this operation is already ongoing. * ::PBIO_ERROR_INVALID_ARG if @p size is too large. - * ::PBIO_ERROR_TIMEDOUT if the operation was started but could not complete. + * ::PBIO_ERROR_CANCELED if the operation was canceled. */ -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size); +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel); /** * Sends and awaits response to an earlier incoming message. * - * Driver-specific implementation. Must return within ::PBDRV_USB_TRANSMIT_TIMEOUT. + * Driver-specific implementation. Must support being canceled via @p cancel. * * The USB process ensures that only one call is made at once. * * @param [in] state Protothread state. * @param [in] code Error code to send. + * @param [in] cancel If true, stop waiting for completion and return error. * @return ::PBIO_SUCCESS on completion. * ::PBIO_ERROR_INVALID_OP if there is no connection. * ::PBIO_ERROR_AGAIN while awaiting. * ::PBIO_ERROR_BUSY if this operation is already ongoing. - * ::PBIO_ERROR_TIMEDOUT if the operation was started but could not complete. + * ::PBIO_ERROR_CANCELED if the operation was canceled. */ -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code); +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel); /** * Resets the driver transmission state. diff --git a/lib/pbio/drv/usb/usb_ev3.c b/lib/pbio/drv/usb/usb_ev3.c index 868c64531..85c7d8318 100644 --- a/lib/pbio/drv/usb/usb_ev3.c +++ b/lib/pbio/drv/usb/usb_ev3.c @@ -1023,9 +1023,7 @@ pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); @@ -1034,28 +1032,20 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin } transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); // Transmit event. pbdrv_cache_prepare_before_dma(data, size); usb_setup_tx_dma_desc(CPPI_DESC_TX_PYBRICKS_EVENT, (uint8_t *)data, size); - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); - - if (pbio_os_timer_is_expired(&timer)) { - // Transmission has taken too long, so reset the state to allow - // new transmissions. This can happen if the host stops reading - // data for some reason. This need some time to complete, so delegate - // the reset back to the process. - return PBIO_ERROR_TIMEDOUT; + PBIO_OS_AWAIT_UNTIL(state, !transmitting || cancel); + if (transmitting && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel) { static uint8_t ep1_tx_response_buf[1 + sizeof(uint32_t)] __aligned(4) = { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; @@ -1066,7 +1056,6 @@ pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t } transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); // Response is just the error code. pbio_set_uint32_le(&ep1_tx_response_buf[1], code); @@ -1076,9 +1065,9 @@ pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t usb_setup_tx_dma_desc(CPPI_DESC_TX_RESPONSE, ep1_tx_response_buf, sizeof(ep1_tx_response_buf)); // Wait until complete or trigger reset on timeout. - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + PBIO_OS_AWAIT_UNTIL(state, !transmitting || cancel); + if (transmitting && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); diff --git a/lib/pbio/drv/usb/usb_nxt.c b/lib/pbio/drv/usb/usb_nxt.c index 6cfe22dfa..b9678900f 100644 --- a/lib/pbio/drv/usb/usb_nxt.c +++ b/lib/pbio/drv/usb/usb_nxt.c @@ -853,9 +853,7 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); @@ -864,20 +862,17 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin // return PBIO_ERROR_BUSY; // } - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); pbdrv_usb_nxt_write_data(2, data, size); - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || cancel); + if (pbdrv_usb_nxt_state.status != USB_READY && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel) { static uint8_t usb_response_buf[PBIO_PYBRICKS_USB_MESSAGE_SIZE(sizeof(uint32_t))] __aligned(4) = { PBIO_PYBRICKS_IN_EP_MSG_RESPONSE }; @@ -888,13 +883,12 @@ pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t // return PBIO_ERROR_BUSY; // } - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); pbio_set_uint32_le(&usb_response_buf[1], code); pbdrv_usb_nxt_write_data(2, usb_response_buf, sizeof(usb_response_buf)); - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_nxt_state.status == USB_READY || cancel); + if (pbdrv_usb_nxt_state.status != USB_READY && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); diff --git a/lib/pbio/drv/usb/usb_simulation.c b/lib/pbio/drv/usb/usb_simulation.c index cc2b2a466..e7f43072b 100644 --- a/lib/pbio/drv/usb/usb_simulation.c +++ b/lib/pbio/drv/usb/usb_simulation.c @@ -29,7 +29,7 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel) { static pbio_os_timer_t timer; @@ -53,7 +53,7 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel) { static pbio_os_timer_t timer; diff --git a/lib/pbio/drv/usb/usb_simulation_pico.c b/lib/pbio/drv/usb/usb_simulation_pico.c index 0ec579501..d4703f847 100644 --- a/lib/pbio/drv/usb/usb_simulation_pico.c +++ b/lib/pbio/drv/usb/usb_simulation_pico.c @@ -38,7 +38,7 @@ pbdrv_usb_bcd_t pbdrv_usb_get_bcd(void) { return PBDRV_USB_BCD_NONE; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); @@ -54,7 +54,10 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin pbdrv_usb_simulation_tx_ready = false; // Enable TX interrupt to be notified when ready. hw_set_bits(&uart_get_hw(uart_default)->imsc, 1 << UART_UARTIMSC_TXIM_LSB); - PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_simulation_tx_ready); + PBIO_OS_AWAIT_UNTIL(state, pbdrv_usb_simulation_tx_ready || cancel); + if (!pbdrv_usb_simulation_tx_ready && cancel) { + return PBIO_ERROR_CANCELED; + } } uart_get_hw(uart_default)->dr = data[i]; @@ -63,7 +66,7 @@ pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uin PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); diff --git a/lib/pbio/drv/usb/usb_stm32.c b/lib/pbio/drv/usb/usb_stm32.c index b474d3438..d045ecc71 100644 --- a/lib/pbio/drv/usb/usb_stm32.c +++ b/lib/pbio/drv/usb/usb_stm32.c @@ -292,48 +292,50 @@ pbio_error_t pbdrv_usb_tx_reset(pbio_os_state_t *state) { return PBIO_SUCCESS; } -pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_event(pbio_os_state_t *state, const uint8_t *data, uint32_t size, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); + if (cancel) { + return PBIO_ERROR_CANCELED; + } + if (transmitting) { return PBIO_ERROR_BUSY; } transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); USBD_Pybricks_TransmitPacket(&husbd, (uint8_t *)data, size); - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); + PBIO_OS_AWAIT_UNTIL(state, !transmitting || cancel); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + if (transmitting && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); } -pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code) { - - static pbio_os_timer_t timer; +pbio_error_t pbdrv_usb_tx_response(pbio_os_state_t *state, pbio_pybricks_error_t code, bool cancel) { PBIO_OS_ASYNC_BEGIN(state); + if (cancel) { + return PBIO_ERROR_CANCELED; + } + if (transmitting) { return PBIO_ERROR_BUSY; } transmitting = true; - pbio_os_timer_set(&timer, PBDRV_USB_TRANSMIT_TIMEOUT); pbio_set_uint32_le(&usb_response_buf[1], code); USBD_Pybricks_TransmitPacket(&husbd, usb_response_buf, sizeof(usb_response_buf)); - PBIO_OS_AWAIT_UNTIL(state, !transmitting || pbio_os_timer_is_expired(&timer)); - if (pbio_os_timer_is_expired(&timer)) { - return PBIO_ERROR_TIMEDOUT; + PBIO_OS_AWAIT_UNTIL(state, !transmitting || cancel); + if (transmitting && cancel) { + return PBIO_ERROR_CANCELED; } PBIO_OS_ASYNC_END(PBIO_SUCCESS); From c8f67542590d9e433e89c5413c8476b751db752a Mon Sep 17 00:00:00 2001 From: David Lechner Date: Sat, 24 Jan 2026 18:21:05 -0600 Subject: [PATCH 2/2] DO NOT MERGE: platform/prime_hub: enable USB Enable USB for testing. --- lib/pbio/platform/prime_hub/pbdrvconfig.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pbio/platform/prime_hub/pbdrvconfig.h b/lib/pbio/platform/prime_hub/pbdrvconfig.h index f29a1532d..ea16ea21e 100644 --- a/lib/pbio/platform/prime_hub/pbdrvconfig.h +++ b/lib/pbio/platform/prime_hub/pbdrvconfig.h @@ -128,7 +128,7 @@ #define PBDRV_CONFIG_USB_PROD_STR LEGO_USB_PROD_STR_TECHNIC_LARGE_HUB " + Pybricks" #define PBDRV_CONFIG_USB_STM32F4 (1) #define PBDRV_CONFIG_USB_STM32F4_HUB_VARIANT_ADDR 0x08007d80 -#define PBDRV_CONFIG_USB_CHARGE_ONLY (1) +#define PBDRV_CONFIG_USB_CHARGE_ONLY (0) #define PBDRV_CONFIG_STACK (1) #define PBDRV_CONFIG_STACK_EMBEDDED (1)