diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e8dc37..15ca6c5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,4 +9,6 @@ add_subdirectory(submodules/wch-ch56x-lib) add_subdirectory(hydradancer) - +if (DEFINED BUILD_LEGACY) +add_subdirectory(legacy) +endif() diff --git a/README.md b/README.md index d49146e..249bbe6 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,14 @@ As shown in the table below, Hydradancer currently supports 5 endpoints other th |:---:|:----:|:-:|:-:| Facedancer21/Raspdancer |USB2 Full-speed |EP1 OUT, EP2 IN, EP3 IN |yes| GreatFET One |USB2 Full-speed |3 IN / 3 OUT |yes| -**Hydradancer** |USB2 High-speed |5 IN / 5 OUT |no| +**Hydradancer** |USB2 High-speed |6 IN / 6 OUT |no| (Cynthion/LUNA)(coming early 2024) |(USB2 High-speed) |(15 IN / 15 OUT) |(yes)|

Facedancer backends functionalities

As Hydradancer tries to reach higher speeds than Facedancer21 and GreatFET, we benchmarked all the existing boards for comparison. All tests were done using the devices and scripts in the `tests` folder of our [Facedancer fork](https://github.com/HydraDancer/Facedancer). All tests were done using a single libusb transfer, except for GreatFET One which was unresponsive for packets of maximum size (64 bytes at full-speed). In this case, each packet was sent using one libusb transfer, with packets of size 63. +Previous results for Hydradancer used priming, which made it faster. The new versions use NAKs to detect IN transfer requests from the host and calls Facedancer's `handle_nak` method. However devices that support setting buffers in advance ("priming") can still do it by implementing the `handle_buffer_empty` callback, which is called once per "empty buffer" event. As `handle_buffer_empty` was not called by other backends at the moment of the test, only Hydradancer was tested for both methods (answering only after NAKs or priming whenever the buffer is empty). @@ -34,42 +35,62 @@ As Hydradancer tries to reach higher speeds than Facedancer21 and GreatFET, we b - + - - + + - + + + + + + + + + + + + + + + + + + + + + - + + + + - - - - + - - + + - - + + - + - - + + - - + + - + @@ -81,8 +102,8 @@ As Hydradancer tries to reach higher speeds than Facedancer21 and GreatFET, we b There are two configurations for Hydradancer: -* the dual-HydraUSB3 : you will need the firmware compiled from `hydradancer/firmware_control_board` and `hydradancer/firmware_emulation_board`. * the Hydradancer dongle : only the firmware from `hydradancer/firmware_hydradancer` is needed. +* (unmaintained) the dual-HydraUSB3 : you will need the firmware compiled from `hydradancer/firmware_control_board` and `hydradancer/firmware_emulation_board`. To build and flash the firmware, see [the build tutorial](BUILD.md). If you don't want to build the firmwares yourself, you can skip the building part by using the [latest release](https://github.com/HydraDancer/hydradancer_fw/releases/latest). @@ -90,6 +111,8 @@ To build and flash the firmware, see [the build tutorial](BUILD.md). If you don' First, clone Facedancer. While we hope to merge the Hydradancer backend for Facedancer into the [main repository](https://github.com/greatscottgadgets/Facedancer) along with some bug fixes we may have found, the Hydradancer backend is currently in our fork. +For the unmaintained dual-HydraUSB3 firmware, you will need v1.0.0 of our Facedancer fork. + ```shell git clone https://github.com/HydraDancer/Facedancer ``` diff --git a/hydradancer/CMakeLists.txt b/hydradancer/CMakeLists.txt index aa87e97..c4a95d8 100644 --- a/hydradancer/CMakeLists.txt +++ b/hydradancer/CMakeLists.txt @@ -1,6 +1,3 @@ - -add_subdirectory(firmware_control_board) -add_subdirectory(firmware_emulation_board) add_subdirectory(firmware_hydradancer) if (DEFINED BUILD_TESTS) diff --git a/hydradancer/firmware_hydradancer/CMakeLists.txt b/hydradancer/firmware_hydradancer/CMakeLists.txt index 8b98304..a166d86 100644 --- a/hydradancer/firmware_hydradancer/CMakeLists.txt +++ b/hydradancer/firmware_hydradancer/CMakeLists.txt @@ -16,7 +16,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/User #### wch-ch56x-lib options -target_compile_definitions(${PROJECT_NAME} PRIVATE POOL_BLOCK_SIZE=512 POOL_BLOCK_NUM=40 INTERRUPT_QUEUE_SIZE=20) +target_compile_definitions(${PROJECT_NAME} PRIVATE POOL_BLOCK_SIZE=512 POOL_BLOCK_NUM=100 INTERRUPT_QUEUE_SIZE=40) #### logging options diff --git a/hydradancer/firmware_hydradancer/User/definitions.c b/hydradancer/firmware_hydradancer/User/definitions.c index a323880..3e09684 100644 --- a/hydradancer/firmware_hydradancer/User/definitions.c +++ b/hydradancer/firmware_hydradancer/User/definitions.c @@ -1,19 +1,35 @@ #include "definitions.h" +#include "wch-ch56x-lib/USBDevice/usb20.h" uint8_t endpoint_mapping_reverse[MAX_ENDPOINTS_SUPPORTED]; //endpoint_mapping_reverse[PC_Endpoint] = Target_Endpoint uint8_t endpoint_mapping[MAX_ENDPOINTS_SUPPORTED]; //endpoint_mapping[Target_Endpoint] = PC_Endpoint __attribute__((aligned(16))) volatile hydradancer_status_t hydradancer_status __attribute__((section(".DMADATA"))) = { .ep_in_nak = 0, .ep_in_status = 0, .ep_out_status = 0, .other_events = 0 }; __attribute__((aligned(16))) volatile uint8_t boards_ready __attribute__((section(".DMADATA"))) = 0; +__attribute__((aligned(16))) volatile bool event_transfer_finished = true; +__attribute__((aligned(16))) volatile bool start_polling = false; +__attribute__((aligned(16))) volatile hydradancer_event_t _events_buffer[EVENT_QUEUE_SIZE] __attribute__((section(".DMADATA"))); +uint64_t MAX_BUSY_WAIT_CYCLES = 1234567899; //set to a lower value in main, after bsp_init -void send_hydradancer_status(void) +HYDRA_POOL_DEF(ep_queue, ep_queue_member_t, 100); + +void _ep_queue_cleanup(uint8_t* data) { - endp_tx_set_new_buffer(&usb_device_1, 1, (uint8_t*)&hydradancer_status, sizeof(hydradancer_status)); + hydra_pool_free(&ep_queue, data); } -HYDRA_POOL_DEF(ep0_queue, ep0_queue_member_t, 20); +HYDRA_FIFO_DEF(event_queue, hydradancer_event_t, EVENT_QUEUE_SIZE); -void _ep0_queue_cleanup(uint8_t* data) +void hydradancer_send_event(void) { - hydra_pool_free(&ep0_queue, data); + if (event_transfer_finished) + { + uint16_t events_count = fifo_count(&event_queue); + if (events_count > 0) + { + event_transfer_finished = false; + uint16_t count_read = fifo_read_n(&event_queue, (void*)_events_buffer, events_count); + endp_tx_set_new_buffer(&usb_device_1, 1, (uint8_t*)_events_buffer, count_read * sizeof(hydradancer_event_t)); + } + } } diff --git a/hydradancer/firmware_hydradancer/User/definitions.h b/hydradancer/firmware_hydradancer/User/definitions.h index e7492da..739d1b7 100644 --- a/hydradancer/firmware_hydradancer/User/definitions.h +++ b/hydradancer/firmware_hydradancer/User/definitions.h @@ -10,7 +10,9 @@ #define DEF_ENDP_IN_BURST_LEVEL (DEF_ENDP_OUT_BURST_LEVEL) #define DEF_ENDP_MAX_SIZE (DEF_ENDP1_OUT_BURST_LEVEL * ENDP_1_15_MAX_PACKET_SIZE) +#include "wch-ch56x-lib/memory/fifo.h" #include "wch-ch56x-lib/memory/pool.h" +#include "wch-ch56x-lib/memory/ramx_alloc.h" #include "wch-ch56x-lib/USBDevice/usb_device.h" #include "wch-ch56x-lib/USBDevice/usb_endpoints.h" @@ -26,15 +28,19 @@ // Standard control requests codes go from 0-27 and 48-49 #define ENABLE_USB_CONNECTION_REQUEST_CODE 50 #define SET_ADDRESS_REQUEST_CODE 51 -#define GET_EP_STATUS 52 +#define GET_EVENT 52 #define SET_ENDPOINT_MAPPING 53 #define DISABLE_USB 54 #define SET_SPEED 55 #define SET_EP_RESPONSE 56 #define CHECK_HYDRADANCER_READY 57 #define DO_BUS_RESET 58 +#define CONFIGURED 59 -#define HYDRADANCER_STATUS_BUS_RESET 0x1 +#define EVENT_BUS_RESET 0x0 +#define EVENT_IN_BUFFER_AVAILABLE 0x1 +#define EVENT_OUT_BUFFER_AVAILABLE 0x2 +#define EVENT_NAK 0x3 #undef FREQ_SYS /* System clock / MCU frequency in Hz (lowest possible speed 15MHz) */ @@ -45,6 +51,9 @@ #define BLINK_USB3 (250) // Blink LED each 500ms (250*2) #define BLINK_USB2 (1000) // Blink LED each 2000ms (1000*2) +#define MAX_BUSY_WAIT_CYCLES_MS 5 +extern uint64_t MAX_BUSY_WAIT_CYCLES; + typedef struct __attribute__((packed)) { uint8_t ep_in_status; @@ -53,22 +62,99 @@ typedef struct __attribute__((packed)) uint8_t other_events; } hydradancer_status_t; +typedef struct __attribute__((packed)) +{ + uint8_t type; + uint8_t value; +} hydradancer_event_t; + #define MAX_ENDPOINTS_SUPPORTED 8 //including EP0 extern uint8_t endpoint_mapping_reverse[MAX_ENDPOINTS_SUPPORTED]; //endpoint_mapping_reverse[PC_Endpoint] = Target_Endpoint extern uint8_t endpoint_mapping[MAX_ENDPOINTS_SUPPORTED]; //endpoint_mapping[Target_Endpoint] = PC_Endpoint extern __attribute__((aligned(16))) volatile hydradancer_status_t hydradancer_status __attribute__((section(".DMADATA"))); extern __attribute__((aligned(16))) volatile uint8_t boards_ready __attribute__((section(".DMADATA"))); +extern __attribute__((aligned(16))) volatile bool event_transfer_finished; +extern __attribute__((aligned(16))) volatile bool start_polling; + +#define EVENT_QUEUE_SIZE 100 // don't forget to update it in the Facedancer backend as well -void send_hydradancer_status(void); +extern __attribute__((aligned(16))) volatile hydradancer_event_t _events_buffer[EVENT_QUEUE_SIZE] __attribute__((section(".DMADATA"))); typedef struct __attribute__((packed)) { + uint8_t ep_num; uint8_t* ptr; uint16_t size; -} ep0_queue_member_t; +} ep_queue_member_t; + +HYDRA_POOL_DECLR(ep_queue); -HYDRA_POOL_DECLR(ep0_queue); +void _ep_queue_cleanup(uint8_t* data); -void _ep0_queue_cleanup(uint8_t* data); +HYDRA_FIFO_DECLR(event_queue, hydradancer_event_t, EVENT_QUEUE_SIZE); -#endif \ No newline at end of file +void hydradancer_send_event(void); + +__attribute__((always_inline)) inline static void write_event(uint8_t type, uint8_t value) +{ + bsp_disable_interrupt(); + hydradancer_event_t event = { + .type = type, + .value = value, + }; + fifo_write(&event_queue, &event, 1); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_set_out(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_out_status |= (1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_set_in(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_in_status |= (1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_set_nak(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_in_nak |= (1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_clear_out(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_out_status &= ~(1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_clear_in(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_in_status &= ~(1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_status_clear_nak(uint8_t endp_num) +{ + bsp_disable_interrupt(); + hydradancer_status.ep_in_nak &= ~(1 << endp_num); + bsp_enable_interrupt(); +} + +__attribute__((always_inline)) inline static void hydradancer_recover_out_interrupt(uint8_t endp_num) +{ + ramx_pool_free(usb_device_1.endpoints.tx[endpoint_mapping[endp_num]].buffer); + hydradancer_status_clear_out(endp_num); + bsp_disable_interrupt(); + endp_rx_set_state(&usb_device_0, endp_num, ENDP_STATE_ACK); + bsp_enable_interrupt(); +} + +#endif diff --git a/hydradancer/firmware_hydradancer/User/main.c b/hydradancer/firmware_hydradancer/User/main.c index 327df35..781ffdc 100644 --- a/hydradancer/firmware_hydradancer/User/main.c +++ b/hydradancer/firmware_hydradancer/User/main.c @@ -34,26 +34,27 @@ int blink_ms = BLINK_USB2; */ int main() { + // Does this change anything ? Setting the fast IRQ ptr to 0 makes the board crash + // I did not notice any huge difference from setting priorities, fast irqs or nested interrupts + // so maybe it is working, but it's not life-changing + PFIC_SetPriority(INT_ID_USBSS, 0xa0); + PFIC_SetPriority(INT_ID_LINK, 0x90); + PFIC_SetPriority(INT_ID_USBHS, 0xc0); + PFIC_HaltPushCfg(ENABLE); + PFIC_INTNestCfg(ENABLE); + PFIC_SetFastIRQ((uint32_t)LINK_IRQHandler, INT_ID_LINK, 0); + PFIC_SetFastIRQ((uint32_t)USBSS_IRQHandler, INT_ID_USBSS, 1); + PFIC_SetFastIRQ((uint32_t)USBHS_IRQHandler, INT_ID_USBHS, 2); + /* Configure GPIO In/Out default/safe state for the board */ bsp_gpio_init(); /* Init BSP (MCU Frequency & SysTick) */ bsp_init(FREQ_SYS); + MAX_BUSY_WAIT_CYCLES = bsp_ms_nbcycles * (uint64_t)MAX_BUSY_WAIT_CYCLES_MS; LOG_INIT(FREQ_SYS); hydra_interrupt_queue_init(); - // /* Need to check if setting priorities has any effects */ - // PFIC_SetPriority(INT_ID_WDOG, 0x90); - // // does not seem to make any difference - // PFIC_INTNestCfg(ENABLE); - // PFIC_SetPriority(INT_ID_SERDES, 0x20); - // PFIC_SetPriority(INT_ID_USBSS, 0x60); - // PFIC_SetPriority(INT_ID_LINK, 0x60); - // PFIC_SetPriority(INT_ID_USBHS, 0x60); - // PFIC_SetPriority(INT_ID_HSPI, 0x20); - // PFIC_SetPriority(INT_ID_UART1, 0x60); - // PFIC_SetPriority(INT_ID_GPIO, 0x60); - // Setup ramx pool and interrupt_queue ramx_pool_init(); @@ -110,6 +111,7 @@ int main() usb_device_0.endpoints.rx_callback[5] = usb_emulation_endp5_rx_callback; usb_device_0.endpoints.rx_callback[6] = usb_emulation_endp6_rx_callback; usb_device_0.endpoints.rx_callback[7] = usb_emulation_endp7_rx_callback; + usb_device_0.endpoints.nak_callback = usb_emulation_nak_callback; usb2_user_handled.usb2_device_handle_bus_reset = usb_emulation_usb2_device_handle_bus_reset; @@ -141,10 +143,15 @@ int main() WWDG_ITCfg(ENABLE); // Infinite loop USB2/USB3 managed with Interrupt + while (1) { WWDG_SetCounter(0); // rearm the watchdog hydra_interrupt_queue_run(); + + if (start_polling) + hydradancer_send_event(); + if (bsp_ubtn()) { LOG_DUMP(); diff --git a/hydradancer/firmware_hydradancer/User/usb_control_handlers.h b/hydradancer/firmware_hydradancer/User/usb_control_handlers.h index 444eba6..162f69f 100644 --- a/hydradancer/firmware_hydradancer/User/usb_control_handlers.h +++ b/hydradancer/firmware_hydradancer/User/usb_control_handlers.h @@ -26,17 +26,13 @@ void usb_control_endp1_tx_complete(TRANSACTION_STATUS status); void usb_control_endp1_tx_complete(TRANSACTION_STATUS status) { - if (hydradancer_status.other_events != 0) - { - hydradancer_status.other_events = 0; // only send events once - } + event_transfer_finished = true; } __attribute__((always_inline)) static inline void usb_control_endp_tx_complete(TRANSACTION_STATUS status, uint8_t endp_num) { - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "usb1 endp%d_tx_complete buffer \r\n", endp_num); ramx_pool_free(usb_device_1.endpoints.tx[endp_num].buffer); - hydradancer_status.ep_out_status &= ~(0x01 << endpoint_mapping_reverse[endp_num]); + hydradancer_status_clear_out(endpoint_mapping_reverse[endp_num]); endp_rx_set_state(&usb_device_0, endpoint_mapping_reverse[endp_num], ENDP_STATE_ACK); } @@ -78,32 +74,45 @@ void usb_control_endp7_tx_complete(TRANSACTION_STATUS status) bool _usb0_ep0_send(uint8_t* data); bool _usb0_ep0_send(uint8_t* data) { - uint8_t* ptr = ((ep0_queue_member_t*)data)->ptr; - uint16_t size = ((ep0_queue_member_t*)data)->size; + uint8_t* ptr = ((ep_queue_member_t*)data)->ptr; + uint16_t size = ((ep_queue_member_t*)data)->size; endp_tx_set_new_buffer(&usb_device_0, endpoint_mapping_reverse[0], ptr, size); return true; } +static bool _usb_control_endp_rx_callback(uint8_t* data); +static bool _usb_control_endp_rx_callback(uint8_t* data) +{ + ep_queue_member_t* ep_queue_member = (ep_queue_member_t*)data; + endp_tx_set_new_buffer(&usb_device_0, endpoint_mapping_reverse[ep_queue_member->ep_num], ep_queue_member->ptr, ep_queue_member->size); + return true; +} + __attribute__((always_inline)) static inline uint8_t usb_control_endp_rx_callback(uint8_t* const ptr, uint16_t size, uint8_t endp_num) { if (!(hydradancer_status.ep_in_status & (0x01 << endpoint_mapping_reverse[endp_num]))) { return ENDP_STATE_NAK; } - - if (endpoint_mapping_reverse[endp_num] != 0) + uint8_t* buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); + if (buffer == NULL) { - endp_tx_set_new_buffer(&usb_device_0, endpoint_mapping_reverse[endp_num], ptr, size); + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_control_endp_rx_callback could not allocate buffer\r\n"); + return ENDP_STATE_NAK; } - else + ep_queue_member_t* ep_queue_member = hydra_pool_get(&ep_queue); + if (ep_queue_member == NULL) { - ep0_queue_member_t* ep0_queue_member = hydra_pool_get(&ep0_queue); - ep0_queue_member->ptr = ptr; - ep0_queue_member->size = size; - hydra_interrupt_queue_set_next_task(_usb0_ep0_send, (uint8_t*)ep0_queue_member, _ep0_queue_cleanup, INTERRUPT_QUEUE_LOW_PRIO); + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_control_endp_rx_callback could not allocate pool member"); + ramx_pool_free(buffer); + return ENDP_STATE_NAK; } - usb_device_1.endpoints.rx[endp_num].buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); - hydradancer_status.ep_in_status &= ~(0x01 << endpoint_mapping_reverse[endp_num]); + ep_queue_member->ep_num = endp_num; + ep_queue_member->ptr = ptr; + ep_queue_member->size = size; + hydra_interrupt_queue_set_next_task(_usb_control_endp_rx_callback, (uint8_t*)ep_queue_member, _ep_queue_cleanup); + usb_device_1.endpoints.rx[ep_queue_member->ep_num].buffer = buffer; + hydradancer_status.ep_in_status &= ~(0x01 << endpoint_mapping_reverse[ep_queue_member->ep_num]); return ENDP_STATE_ACK; } @@ -143,26 +152,6 @@ uint8_t usb_control_endp7_rx_callback(uint8_t* const ptr, uint16_t size) return usb_control_endp_rx_callback(ptr, size, 7); } -bool _do_bus_reset(uint8_t* data); -bool _do_bus_reset(uint8_t* data) -{ - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "DO_BUS_RESET\r\n"); - bsp_disable_interrupt(); - hydradancer_status.ep_in_status = 0; - hydradancer_status.ep_out_status = 0; - hydradancer_status.ep_in_nak = 0; - hydradancer_status.other_events = 0; - // Setup ramx pool - hydra_interrupt_queue_free_all(); - hydra_interrupt_queue_init(); - hydra_pool_clean(&ep0_queue); - ramx_pool_init(); - bsp_enable_interrupt(); - usb_emulation_do_bus_reset(); - usb_control_reinit(); - return true; -} - bool _do_disable_usb(uint8_t* data); bool _do_disable_usb(uint8_t* data) { @@ -170,9 +159,11 @@ bool _do_disable_usb(uint8_t* data) usb2_device_deinit(); bsp_disable_interrupt(); boards_ready = 0; + event_transfer_finished = true; + start_polling = false; hydra_interrupt_queue_free_all(); hydra_interrupt_queue_init(); - hydra_pool_clean(&ep0_queue); + hydra_pool_clean(&ep_queue); ramx_pool_init(); bsp_enable_interrupt(); usb_emulation_reinit(); @@ -185,14 +176,16 @@ uint16_t usb_control_endp0_user_handled_control_request(USB_SETUP* request, uint { LOG_IF(LOG_LEVEL_TRACE, LOG_ID_USER, "endp0_user_handled_control_request bRequest %d \r\n", request->bRequest); - if (request->bRequest == GET_EP_STATUS) + if (request->bRequest == GET_EVENT) { - *buf = (uint8_t*)&hydradancer_status; - if (hydradancer_status.other_events != 0) + *buf = (uint8_t*)_events_buffer; + uint16_t events_count = fifo_count(&event_queue); + if (events_count > 0) { - hydradancer_status.other_events = 0; // only send events once + fifo_read_n(&event_queue, (void*)_events_buffer, events_count); + return events_count * sizeof(hydradancer_event_t); } - return sizeof(hydradancer_status); + return 0; } else if (request->bRequest == CHECK_HYDRADANCER_READY) { @@ -206,7 +199,12 @@ uint16_t usb_control_endp0_user_handled_control_request(USB_SETUP* request, uint endpoint_mapping_reverse[request->wValue.bw.bb0] = request->wValue.bw.bb1; hydradancer_status.ep_in_status |= (0x01 << request->wValue.bw.bb1); hydradancer_status.ep_out_status &= ~(0x01 << request->wValue.bw.bb1); - send_hydradancer_status(); + usb_device_0.endpoints.rx[request->wValue.bw.bb1].max_packet_size = usb_device_0.speed == USB2_HIGHSPEED ? 512 : 64; + hydradancer_event_t event = { + .type = EVENT_IN_BUFFER_AVAILABLE, + .value = request->wValue.bw.bb1, + }; + fifo_write(&event_queue, &event, 1); return 0; } else if (request->bRequest == SET_EP_RESPONSE) @@ -226,6 +224,7 @@ uint16_t usb_control_endp0_user_handled_control_request(USB_SETUP* request, uint { LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "ENABLE_USB_CONNECTION_REQUEST_CODE\r\n"); usb2_device_init(); + usb2_enable_nak(true); return 0; } else if (request->bRequest == SET_SPEED) @@ -248,14 +247,9 @@ uint16_t usb_control_endp0_user_handled_control_request(USB_SETUP* request, uint } return 0; } - else if (request->bRequest == DO_BUS_RESET) - { - hydra_interrupt_queue_set_next_task(_do_bus_reset, NULL, NULL, INTERRUPT_QUEUE_LOW_PRIO); - return 0; - } else if (request->bRequest == DISABLE_USB) { - hydra_interrupt_queue_set_next_task(_do_disable_usb, NULL, NULL, INTERRUPT_QUEUE_LOW_PRIO); + hydra_interrupt_queue_set_next_task(_do_disable_usb, NULL, NULL); return 0; } else if (request->bRequest == SET_ADDRESS_REQUEST_CODE) @@ -264,6 +258,12 @@ uint16_t usb_control_endp0_user_handled_control_request(USB_SETUP* request, uint usb2_set_device_address(request->wValue.bw.bb1); return 0; } + else if (request->bRequest == CONFIGURED) + { + LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "CONFIGURED\r\n"); + start_polling = true; + return 0; + } return 0xffff; } diff --git a/hydradancer/firmware_hydradancer/User/usb_emulation_handlers.h b/hydradancer/firmware_hydradancer/User/usb_emulation_handlers.h index feae206..a50b934 100644 --- a/hydradancer/firmware_hydradancer/User/usb_emulation_handlers.h +++ b/hydradancer/firmware_hydradancer/User/usb_emulation_handlers.h @@ -16,6 +16,7 @@ #include "usb_emulation_device.h" #include "wch-ch56x-lib/interrupt_queue/interrupt_queue.h" #include "wch-ch56x-lib/logging/logging.h" +#include "wch-ch56x-lib/memory/fifo.h" #include "wch-ch56x-lib/memory/pool.h" #include "wch-ch56x-lib/memory/ramx_alloc.h" #include "wch-ch56x-lib/USBDevice/usb20.h" @@ -26,10 +27,10 @@ __attribute__((always_inline)) static inline void usb_emulation_endp_tx_complete(TRANSACTION_STATUS status, uint8_t endp_num) { - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "endp%d_tx_complete\r\n", endp_num); ramx_pool_free(usb_device_0.endpoints.tx[endp_num].buffer); - hydradancer_status.ep_in_status |= (0x01 << endp_num); - send_hydradancer_status(); + hydradancer_status_clear_nak(endp_num); + hydradancer_status_set_in(endp_num); + write_event(EVENT_IN_BUFFER_AVAILABLE, endp_num); } void usb_emulation_endp0_tx_complete(TRANSACTION_STATUS status); @@ -83,31 +84,100 @@ void usb_emulation_endp7_tx_complete(TRANSACTION_STATUS status) bool _usb_emulation_endp0_passthrough_setup_callback(uint8_t* data); bool _usb_emulation_endp0_passthrough_setup_callback(uint8_t* data) { - // can't NAK a setup, can't STALL because it would mean the request is not supported - // so we just wait - while (hydradancer_status.ep_out_status & (0x01 << 0)) + uint8_t* ptr = ((ep_queue_member_t*)data)->ptr; + uint16_t size = ((ep_queue_member_t*)data)->size; + + // we need to wait here, because we can't NAK a packet with PID setup + // the only accepted answer is ACK, so we have to store it and wait for Facedancer to be ready + uint64_t start = bsp_get_SysTickCNT(); + uint64_t curr = 0; + uint64_t delta = 0; + + while (hydradancer_status.ep_out_status & (0x01 << 0) && delta < MAX_BUSY_WAIT_CYCLES) { + if (start_polling) + hydradancer_send_event(); WWDG_SetCounter(0); // rearm the watchdog - continue; + curr = bsp_get_SysTickCNT(); + delta = start - curr; // SysTickCNT is decremented so comparison is inverted } - uint8_t* ptr = ((ep0_queue_member_t*)data)->ptr; - uint16_t size = ((ep0_queue_member_t*)data)->size; - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "endp0_passthrough_setup_callback size %d data %d %d %d %d %d %d \r\n", size, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); - hydradancer_status.ep_out_status |= (0x01 << 0); + + hydradancer_status_set_out(0); endp_tx_set_new_buffer(&usb_device_1, endpoint_mapping[0], ptr, size); - send_hydradancer_status(); + write_event(EVENT_OUT_BUFFER_AVAILABLE, 0); + + // wait for the packet to be transmitted or count to reach MAX_COUNT + // we still miss some interrupts, count attemps to mitigate this issue by considering + // the packet has been sent after some time + start = bsp_get_SysTickCNT(); + curr = 0; + delta = 0; + while (hydradancer_status.ep_out_status & (0x01 << 0) && delta < MAX_BUSY_WAIT_CYCLES) + { + if (start_polling) + hydradancer_send_event(); + WWDG_SetCounter(0); // rearm the watchdog + curr = bsp_get_SysTickCNT(); + delta = start - curr; // SysTickCNT is decremented so comparison is inverted + } + + if (delta >= MAX_BUSY_WAIT_CYCLES) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "_usb_emulation_endp0_passthrough_setup_callback RECOVERY\r\n"); + hydradancer_recover_out_interrupt(0); + } return true; } uint8_t usb_emulation_endp0_passthrough_setup_callback(uint8_t* ptr, uint16_t size); uint8_t usb_emulation_endp0_passthrough_setup_callback(uint8_t* ptr, uint16_t size) { - usb_device_0.endpoints.rx[0].buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); - ep0_queue_member_t* ep0_queue_member = hydra_pool_get(&ep0_queue); - ep0_queue_member->ptr = ptr; - ep0_queue_member->size = size; - hydra_interrupt_queue_set_next_task(_usb_emulation_endp0_passthrough_setup_callback, (uint8_t*)ep0_queue_member, _ep0_queue_cleanup, INTERRUPT_QUEUE_LOW_PRIO); - return ENDP_STATE_ACK; + uint8_t* buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); + if (buffer == NULL) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp0_passthrough_setup_callback Could not allocate pool buf\r\n"); + return ENDP_STATE_NAK; + } + ep_queue_member_t* ep_queue_member = hydra_pool_get(&ep_queue); + if (ep_queue_member == NULL) + { + ramx_pool_free(buffer); + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp0_passthrough_setup_callback could not allocate ep_queue_member"); + return ENDP_STATE_NAK; + } + memcpy(buffer, ptr, size); + ep_queue_member->ptr = buffer; + ep_queue_member->size = size; + hydra_interrupt_queue_set_next_task(_usb_emulation_endp0_passthrough_setup_callback, (uint8_t*)ep_queue_member, _ep_queue_cleanup); + return ENDP_STATE_NAK; +} + +static bool _usb_emulation_endp_rx_callback(uint8_t* data) +{ + ep_queue_member_t* ep_queue_member = (ep_queue_member_t*)data; + hydradancer_status_set_out(ep_queue_member->ep_num); + endp_tx_set_new_buffer(&usb_device_1, endpoint_mapping[ep_queue_member->ep_num], ep_queue_member->ptr, ep_queue_member->size); + write_event(EVENT_OUT_BUFFER_AVAILABLE, ep_queue_member->ep_num); + + uint64_t start = bsp_get_SysTickCNT(); + uint64_t curr = 0; + uint64_t delta = 0; + + while (hydradancer_status.ep_out_status & (0x01 << ep_queue_member->ep_num) && delta < MAX_BUSY_WAIT_CYCLES) + { + if (start_polling) + hydradancer_send_event(); + WWDG_SetCounter(0); // rearm the watchdog + curr = bsp_get_SysTickCNT(); + delta = start - curr; // SysTickCNT is decremented so comparison is inverted + } + + if (delta >= MAX_BUSY_WAIT_CYCLES) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "_usb_emulation_endp_rx_callback RECOVERY\r\n"); + hydradancer_recover_out_interrupt(ep_queue_member->ep_num); + } + return true; } __attribute__((always_inline)) static inline uint8_t usb_emulation_endp_rx_callback(uint8_t* const ptr, uint16_t size, uint8_t endp_num) @@ -116,40 +186,87 @@ __attribute__((always_inline)) static inline uint8_t usb_emulation_endp_rx_callb { return ENDP_STATE_NAK; } - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "endp%d_rx_callback %d %d %d %d %d %d \r\n", endp_num, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); - hydradancer_status.ep_out_status |= (0x01 << endp_num); - endp_tx_set_new_buffer(&usb_device_1, endpoint_mapping[endp_num], ptr, size); - usb_device_0.endpoints.rx[endp_num].buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); - send_hydradancer_status(); - return ENDP_STATE_ACK; + + uint8_t* buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); + if (buffer == NULL) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp_rx_callback could not allocate buffer\r\n"); + return ENDP_STATE_NAK; + } + ep_queue_member_t* ep_queue_member = hydra_pool_get(&ep_queue); + if (ep_queue_member == NULL) + { + ramx_pool_free(buffer); + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp_rx_callback could not allocate ep queue member\r\n"); + return ENDP_STATE_NAK; + } + + ep_queue_member->ep_num = endp_num; + ep_queue_member->ptr = ptr; + ep_queue_member->size = size; + hydra_interrupt_queue_set_next_task(_usb_emulation_endp_rx_callback, (uint8_t*)ep_queue_member, _ep_queue_cleanup); + usb_device_0.endpoints.rx[endp_num].buffer = buffer; + return ENDP_STATE_NAK; } bool _usb_emulation_endp0_rx_callback(uint8_t* data); bool _usb_emulation_endp0_rx_callback(uint8_t* data) { - while (hydradancer_status.ep_out_status & (0x01 << 0)) + hydradancer_status_set_out(0); + + uint8_t* ptr = ((ep_queue_member_t*)data)->ptr; + uint16_t size = ((ep_queue_member_t*)data)->size; + + endp_tx_set_new_buffer(&usb_device_1, endpoint_mapping[0], ptr, size); + write_event(EVENT_OUT_BUFFER_AVAILABLE, 0); + + uint64_t start = bsp_get_SysTickCNT(); + uint64_t curr = 0; + uint64_t delta = 0; + + while (hydradancer_status.ep_out_status & (0x01 << 0) && delta < MAX_BUSY_WAIT_CYCLES) { + if (start_polling) + hydradancer_send_event(); WWDG_SetCounter(0); // rearm the watchdog - continue; + curr = bsp_get_SysTickCNT(); + delta = start - curr; // SysTickCNT is decremented so comparison is inverted + } + + if (delta >= MAX_BUSY_WAIT_CYCLES) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "_usb_emulation_endp0_rx_callback RECOVERY\r\n"); + hydradancer_recover_out_interrupt(0); } - uint8_t* ptr = ((ep0_queue_member_t*)data)->ptr; - uint16_t size = ((ep0_queue_member_t*)data)->size; - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "endp%d_rx_callback size %d data %d %d %d %d %d %d \r\n", 0, size, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5]); - hydradancer_status.ep_out_status |= (0x01 << 0); - endp_tx_set_new_buffer(&usb_device_1, endpoint_mapping[0], ptr, size); - send_hydradancer_status(); return true; } uint8_t usb_emulation_endp0_rx_callback(uint8_t* const ptr, uint16_t size); uint8_t usb_emulation_endp0_rx_callback(uint8_t* const ptr, uint16_t size) { - usb_device_0.endpoints.rx[0].buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); - ep0_queue_member_t* ep0_queue_member = hydra_pool_get(&ep0_queue); - ep0_queue_member->ptr = ptr; - ep0_queue_member->size = size; - hydra_interrupt_queue_set_next_task(_usb_emulation_endp0_rx_callback, (uint8_t*)ep0_queue_member, _ep0_queue_cleanup, INTERRUPT_QUEUE_LOW_PRIO); - return ENDP_STATE_ACK; + if (hydradancer_status.ep_out_status & (0x01 << 0)) + { + return ENDP_STATE_NAK; + } + + uint8_t* buffer = ramx_pool_alloc_bytes(ENDP_1_15_MAX_PACKET_SIZE); + if (buffer == NULL) + { + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp0_passthrough_setup_callback Could not allocate pool buf\r\n"); + return ENDP_STATE_NAK; + } + ep_queue_member_t* ep_queue_member = hydra_pool_get(&ep_queue); + if (ep_queue_member == NULL) + { + ramx_pool_free(buffer); + LOG_IF_LEVEL(LOG_LEVEL_CRITICAL, "usb_emulation_endp0_passthrough_setup_callback could not allocate ep_queue_member"); + return ENDP_STATE_NAK; + } + memcpy(buffer, ptr, size); + ep_queue_member->ptr = buffer; + ep_queue_member->size = size; + hydra_interrupt_queue_set_next_task(_usb_emulation_endp0_rx_callback, (uint8_t*)ep_queue_member, _ep_queue_cleanup); + return ENDP_STATE_NAK; } uint8_t usb_emulation_endp1_rx_callback(uint8_t* const ptr, uint16_t size); @@ -191,6 +308,16 @@ uint8_t usb_emulation_endp7_rx_callback(uint8_t* const ptr, uint16_t size) return usb_emulation_endp_rx_callback(ptr, size, 7); } +void usb_emulation_nak_callback(uint8_t endp_num); +void usb_emulation_nak_callback(uint8_t endp_num) +{ + if (!(hydradancer_status.ep_in_nak & (0x01 << endp_num))) + { + hydradancer_status_set_nak(endp_num); + write_event(EVENT_NAK, endp_num); + } +} + /* 1. Warn Facedancer about the bus reset via the control board 2. Facedancer should then trigger a usb vendor control reset to the control board @@ -200,9 +327,25 @@ uint8_t usb_emulation_endp7_rx_callback(uint8_t* const ptr, uint16_t size) void usb_emulation_usb2_device_handle_bus_reset(void); void usb_emulation_usb2_device_handle_bus_reset(void) { - LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "bus reset \r\n"); - hydradancer_status.other_events |= HYDRADANCER_STATUS_BUS_RESET; - send_hydradancer_status(); + bsp_disable_interrupt(); + hydradancer_status.ep_in_status = (0x1 << 0) & 0xff; // keep ep0 + hydradancer_status.ep_out_status = 0; + hydradancer_status.ep_in_nak = 0; + hydradancer_status.other_events = 0; + + // Set the USB device parameters + usb2_ep0_passthrough_enabled(true); + usb_device_set_endpoint_mask(&usb_device_0, ENDPOINT_1_RX | ENDPOINT_1_TX | ENDPOINT_2_RX | ENDPOINT_2_TX | ENDPOINT_3_RX | ENDPOINT_3_TX | ENDPOINT_4_RX | ENDPOINT_4_TX | ENDPOINT_5_RX | ENDPOINT_5_TX | ENDPOINT_6_RX | ENDPOINT_6_TX | ENDPOINT_7_RX | ENDPOINT_7_TX); + + for (int i = 0; i < MAX_ENDPOINTS_SUPPORTED; ++i) + { + endp_rx_set_state(&usb_device_0, i, ENDP_STATE_ACK); + } + + boards_ready = 1; + bsp_enable_interrupt(); + + write_event(EVENT_BUS_RESET, 0); } #endif diff --git a/hydradancer/tests/test_backend/firmware_emulation_board/CMakeLists.txt b/hydradancer/tests/test_backend/firmware_emulation_board/CMakeLists.txt index 2d5c322..07778ec 100644 --- a/hydradancer/tests/test_backend/firmware_emulation_board/CMakeLists.txt +++ b/hydradancer/tests/test_backend/firmware_emulation_board/CMakeLists.txt @@ -12,7 +12,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/User #### wch-ch56x-lib options -target_compile_definitions(wch-ch56x-lib-scheduled INTERFACE POOL_BLOCK_SIZE=512 POOL_BLOCK_NUM=40 INTERRUPT_QUEUE_SIZE=20) +target_compile_definitions(${PROJECT_NAME} PRIVATE POOL_BLOCK_SIZE=512 POOL_BLOCK_NUM=40 INTERRUPT_QUEUE_SIZE=20) #### logging options diff --git a/legacy/CMakeLists.txt b/legacy/CMakeLists.txt new file mode 100644 index 0000000..734bbf4 --- /dev/null +++ b/legacy/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(firmware_control_board) +add_subdirectory(firmware_emulation_board) diff --git a/hydradancer/firmware_control_board/.gitignore b/legacy/firmware_control_board/.gitignore similarity index 100% rename from hydradancer/firmware_control_board/.gitignore rename to legacy/firmware_control_board/.gitignore diff --git a/hydradancer/firmware_control_board/.ld b/legacy/firmware_control_board/.ld similarity index 100% rename from hydradancer/firmware_control_board/.ld rename to legacy/firmware_control_board/.ld diff --git a/hydradancer/firmware_control_board/CMakeLists.txt b/legacy/firmware_control_board/CMakeLists.txt similarity index 100% rename from hydradancer/firmware_control_board/CMakeLists.txt rename to legacy/firmware_control_board/CMakeLists.txt diff --git a/hydradancer/firmware_control_board/User/definitions.h b/legacy/firmware_control_board/User/definitions.h similarity index 100% rename from hydradancer/firmware_control_board/User/definitions.h rename to legacy/firmware_control_board/User/definitions.h diff --git a/hydradancer/firmware_control_board/User/main.c b/legacy/firmware_control_board/User/main.c similarity index 99% rename from hydradancer/firmware_control_board/User/main.c rename to legacy/firmware_control_board/User/main.c index 04bcc30..6eb5d70 100644 --- a/hydradancer/firmware_control_board/User/main.c +++ b/legacy/firmware_control_board/User/main.c @@ -104,7 +104,7 @@ void hspi_rx_callback(uint8_t* buffer, uint16_t size, uint16_t custom_register) if (request_code == HSPI_SERDES_EMULATION_BOARD_RESET) { LOG_IF(LOG_LEVEL_DEBUG, LOG_ID_USER, "HSPI_SERDES_EMULATION_BOARD_RESET\r\n"); - hydra_interrupt_queue_set_next_task(reinit_board, NULL, NULL, INTERRUPT_QUEUE_LOW_PRIO); + hydra_interrupt_queue_set_next_task(reinit_board, NULL, NULL); return; } else if (request_code == HSPI_SERDES_EMULATION_BOARD_BUS_RESET) diff --git a/hydradancer/firmware_control_board/User/usb2_device_descriptors.h b/legacy/firmware_control_board/User/usb2_device_descriptors.h similarity index 100% rename from hydradancer/firmware_control_board/User/usb2_device_descriptors.h rename to legacy/firmware_control_board/User/usb2_device_descriptors.h diff --git a/hydradancer/firmware_control_board/User/usb3_device_descriptors.h b/legacy/firmware_control_board/User/usb3_device_descriptors.h similarity index 100% rename from hydradancer/firmware_control_board/User/usb3_device_descriptors.h rename to legacy/firmware_control_board/User/usb3_device_descriptors.h diff --git a/hydradancer/firmware_control_board/User/usb_device.h b/legacy/firmware_control_board/User/usb_device.h similarity index 100% rename from hydradancer/firmware_control_board/User/usb_device.h rename to legacy/firmware_control_board/User/usb_device.h diff --git a/hydradancer/firmware_control_board/User/usb_handlers.h b/legacy/firmware_control_board/User/usb_handlers.h similarity index 100% rename from hydradancer/firmware_control_board/User/usb_handlers.h rename to legacy/firmware_control_board/User/usb_handlers.h diff --git a/hydradancer/firmware_control_board/format.sh b/legacy/firmware_control_board/format.sh similarity index 100% rename from hydradancer/firmware_control_board/format.sh rename to legacy/firmware_control_board/format.sh diff --git a/hydradancer/firmware_emulation_board/.gitignore b/legacy/firmware_emulation_board/.gitignore similarity index 100% rename from hydradancer/firmware_emulation_board/.gitignore rename to legacy/firmware_emulation_board/.gitignore diff --git a/hydradancer/firmware_emulation_board/.ld b/legacy/firmware_emulation_board/.ld similarity index 100% rename from hydradancer/firmware_emulation_board/.ld rename to legacy/firmware_emulation_board/.ld diff --git a/hydradancer/firmware_emulation_board/CMakeLists.txt b/legacy/firmware_emulation_board/CMakeLists.txt similarity index 100% rename from hydradancer/firmware_emulation_board/CMakeLists.txt rename to legacy/firmware_emulation_board/CMakeLists.txt diff --git a/hydradancer/firmware_emulation_board/User/definitions.h b/legacy/firmware_emulation_board/User/definitions.h similarity index 100% rename from hydradancer/firmware_emulation_board/User/definitions.h rename to legacy/firmware_emulation_board/User/definitions.h diff --git a/hydradancer/firmware_emulation_board/User/main.c b/legacy/firmware_emulation_board/User/main.c similarity index 100% rename from hydradancer/firmware_emulation_board/User/main.c rename to legacy/firmware_emulation_board/User/main.c diff --git a/hydradancer/firmware_emulation_board/User/usb_device.h b/legacy/firmware_emulation_board/User/usb_device.h similarity index 100% rename from hydradancer/firmware_emulation_board/User/usb_device.h rename to legacy/firmware_emulation_board/User/usb_device.h diff --git a/hydradancer/firmware_emulation_board/User/usb_handlers.h b/legacy/firmware_emulation_board/User/usb_handlers.h similarity index 100% rename from hydradancer/firmware_emulation_board/User/usb_handlers.h rename to legacy/firmware_emulation_board/User/usb_handlers.h diff --git a/hydradancer/firmware_emulation_board/format.sh b/legacy/firmware_emulation_board/format.sh similarity index 100% rename from hydradancer/firmware_emulation_board/format.sh rename to legacy/firmware_emulation_board/format.sh diff --git a/submodules/wch-ch56x-lib b/submodules/wch-ch56x-lib index 4299e42..797f465 160000 --- a/submodules/wch-ch56x-lib +++ b/submodules/wch-ch56x-lib @@ -1 +1 @@ -Subproject commit 4299e426b54bde1edc5934df93ac62305c81cc53 +Subproject commit 797f4653a5848907147507ff77d26dc76a9803c0
Hydradancer High-speed7996.352±314.348 KB/s3911±151 KB/s 4%499.712 KB4224.192±157.058 KB/s499.712KB2653±96 KB/s 4%499.712 KB499.712KB99.9%
Hydradancer High-speed (priming)3788±194 KB/s5%499.712KB2962±118 KB/s4%499.712KB99.9%
Hydradancer Full-speed (priming)369.80±2.46 KB/s1%49.984KB352.35±6.66 KB/s2%49.984KB 99.9%
Hydradancer Full-speed747.295±20.899 KB/s369.66±4.98 KB/s1%49.984KB266.64±7.32 KB/s 3%49.984 KB414.188±7.368 KB/s2%49.984 KB49.984KB 99.9%
GreatFET One Full-speed un par un32.422±0.844 KB/sGreatFET One Full-speed (one by one) (git-v2021.2.1-64-g2409575 firmware)32.42±0.85 KB/s 3%49.959 KB33.066±1.095 KB/s49.959KB33.07±1.10 KB/s 3%49.984 KB49.984KB 99.9%
Facedancer21 Full-speed0.697±0.0 KB/sFacedancer21 Full-speed (2014-07-05 firmware)0.697±0.000 KB/s 0%9.984 KB0.682±0.0 KB/s9.984KB0.682±0.000 KB/s 0%9.984 KB9.984KB 99.9%