From 2948cc05443ca910beb47c70079746e7645475c4 Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Tue, 23 Sep 2025 10:11:39 +0200 Subject: [PATCH 1/2] simulator: fix task poll speed The simulator didn't behave like the real firmware: it only polled/spun the async tasks when there was USB input available, which happens at the client poll interval during a long-running workflow. Instead, usb_packet_process should be called if there is USB input, but the async tasks should be polled independently at max speed. We noticed this issue when making bip39-unlock async, and it would only advance by one bip39 stretch iteration every few hundred milliseconds. --- CHANGELOG.md | 1 + test/simulator/simulator.c | 42 ++++++++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7e00c3c6..3356586b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ customers cannot upgrade their bootloader, its changes are recorded separately. - simulator: simulate a Nova device - Add API call to fetch multiple xpubs at once - Add the option for the simulator to write its memory to file. +- simulator: spin tasks at max speed instead of at the client poll interval ### 9.23.1 - EVM: add HyperEVM (HYPE) and SONIC (S) to known networks diff --git a/test/simulator/simulator.c b/test/simulator/simulator.c index 92edaee37..1e9da3286 100644 --- a/test/simulator/simulator.c +++ b/test/simulator/simulator.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -45,11 +46,6 @@ int commfd; static volatile sig_atomic_t sigint_called = false; static int sockfd; -static int get_usb_message_socket(uint8_t* input) -{ - return read(commfd, input, USB_HID_REPORT_OUT_SIZE); -} - static void send_usb_message_socket(void) { const uint8_t* data = queue_pull(queue_hww_queue()); @@ -63,14 +59,6 @@ static void send_usb_message_socket(void) } } -static void simulate_firmware_execution(const uint8_t* input) -{ - usb_packet_process((const USB_FRAME*)input); - rust_workflow_spin(); - rust_async_usb_spin(); - usb_processing_process(usb_processing_hww()); -} - static void _int_handler(int signum) { (void)signum; @@ -170,13 +158,37 @@ int main(int argc, char* argv[]) } printf("Socket connection setup success\n"); + // Set commfd to non-blocking + int flags = fcntl(commfd, F_GETFL, 0); + if (flags == -1 || fcntl(commfd, F_SETFL, flags | O_NONBLOCK) == -1) { + perror("fcntl"); + close(commfd); + continue; + } + // BitBox02 firmware loop uint8_t input[BUFFER_SIZE]; int temp_len; while (1) { // Simulator polls for USB messages from client and then processes them - if (!get_usb_message_socket(input)) break; - simulate_firmware_execution(input); + + int bytes_read = read(commfd, input, USB_HID_REPORT_OUT_SIZE); + if (bytes_read == 0) { + // client disconnected + break; + } + else if (bytes_read == -1) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + perror("read error"); + break; + } + } else { + usb_packet_process((const USB_FRAME*)input); + } + + usb_processing_process(usb_processing_hww()); + rust_workflow_spin(); + rust_async_usb_spin(); // If the USB message to be sent from firmware is bigger than one packet, // then the simulator sends the message in multiple packets. Packets use From 73c36040eac5f275e515c4c02a3350acdde4d68e Mon Sep 17 00:00:00 2001 From: Marko Bencun Date: Tue, 23 Sep 2025 13:46:45 +0200 Subject: [PATCH 2/2] [wip] with timeout Reduce CPU load --- test/simulator/simulator.c | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/simulator/simulator.c b/test/simulator/simulator.c index 1e9da3286..0c4dd607e 100644 --- a/test/simulator/simulator.c +++ b/test/simulator/simulator.c @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -172,20 +173,27 @@ int main(int argc, char* argv[]) while (1) { // Simulator polls for USB messages from client and then processes them - int bytes_read = read(commfd, input, USB_HID_REPORT_OUT_SIZE); - if (bytes_read == 0) { - // client disconnected + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(commfd, &readfds); + // 0.05ms timeout, so this loop does not lead to 100% CPU usage, but is also not + // unbearably slow. It is a trade-off as we don't have proper waking of our Rust tasks, + // just busy polling. + struct timeval timeout = {0, 500}; + + if (select(commfd + 1, &readfds, NULL, NULL, &timeout) < 0) { break; } - else if (bytes_read == -1) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - perror("read error"); - break; + + if (FD_ISSET(commfd, &readfds)) { + int bytes_read = read(commfd, input, USB_HID_REPORT_OUT_SIZE); + if (bytes_read == 0) break; + else if (bytes_read > 0) { + usb_packet_process((const USB_FRAME*)input); } - } else { - usb_packet_process((const USB_FRAME*)input); } + usb_processing_process(usb_processing_hww()); rust_workflow_spin(); rust_async_usb_spin();