Skip to content

Commit

Permalink
Merge pull request #1 from HydraDancer/add_naks_rethink_events
Browse files Browse the repository at this point in the history
Pass Facedancer stress test, adding NAK support
  • Loading branch information
kauwua authored Jun 6, 2024
2 parents 410fd69 + 160c228 commit 68526f4
Show file tree
Hide file tree
Showing 30 changed files with 421 additions and 145 deletions.
4 changes: 3 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ add_subdirectory(submodules/wch-ch56x-lib)

add_subdirectory(hydradancer)


if (DEFINED BUILD_LEGACY)
add_subdirectory(legacy)
endif()
65 changes: 44 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)|

<p style="text-align: center "><em>Facedancer backends functionalities</em></p>

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).

<table class="table table-striped">
<thead>
Expand All @@ -34,42 +35,62 @@ As Hydradancer tries to reach higher speeds than Facedancer21 and GreatFET, we b
<tbody>
<tr>
<th>Hydradancer High-speed</th>
<td>7996.352±314.348 KB/s</td>
<td>3911±151 KB/s</td>
<td>4%</td>
<td>499.712 KB</td>
<td>4224.192±157.058 KB/s</td>
<td>499.712KB</td>
<td>2653±96 KB/s</td>
<td>4%</td>
<td>499.712 KB</td>
<td>499.712KB</td>
<td>99.9%</td>
</tr>
<tr>
<th>Hydradancer High-speed (priming)</th>
<td>3788±194 KB/s</td>
<td>5%</td>
<td>499.712KB</td>
<td>2962±118 KB/s</td>
<td>4%</td>
<td>499.712KB</td>
<td>99.9%</td>
</tr>
<tr>
<th>Hydradancer Full-speed (priming)</th>
<td>369.80±2.46 KB/s</td>
<td>1%</td>
<td>49.984KB</td>
<td>352.35±6.66 KB/s</td>
<td>2%</td>
<td>49.984KB</td>
<td>99.9%</td>
</tr>
<tr>
<th>Hydradancer Full-speed</th>
<td>747.295±20.899 KB/s</td>
<td>369.66±4.98 KB/s</td>
<td>1%</td>
<td>49.984KB</td>
<td>266.64±7.32 KB/s</td>
<td>3%</td>
<td>49.984 KB</td>
<td>414.188±7.368 KB/s</td>
<td>2%</td>
<td>49.984 KB</td>
<td>49.984KB</td>
<td>99.9%</td>
</tr>
<tr>
<th>GreatFET One Full-speed un par un</th>
<td>32.422±0.844 KB/s</td>
<th>GreatFET One Full-speed (one by one) (git-v2021.2.1-64-g2409575 firmware)</th>
<td>32.42±0.85 KB/s</td>
<td>3%</td>
<td>49.959 KB</td>
<td>33.066±1.095 KB/s</td>
<td>49.959KB</td>
<td>33.07±1.10 KB/s</td>
<td>3%</td>
<td>49.984 KB</td>
<td>49.984KB</td>
<td>99.9%</td>
</tr>
<tr>
<th>Facedancer21 Full-speed</th>
<td>0.697±0.0 KB/s</td>
<th>Facedancer21 Full-speed (2014-07-05 firmware)</th>
<td>0.697±0.000 KB/s</td>
<td>0%</td>
<td>9.984 KB</td>
<td>0.682±0.0 KB/s</td>
<td>9.984KB</td>
<td>0.682±0.000 KB/s</td>
<td>0%</td>
<td>9.984 KB</td>
<td>9.984KB</td>
<td>99.9%</td>
</tr>
</tbody>
Expand All @@ -81,15 +102,17 @@ 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).

# ... and finally, using Facedancer with Hydradancer !

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
```
Expand Down
3 changes: 0 additions & 3 deletions hydradancer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@

add_subdirectory(firmware_control_board)
add_subdirectory(firmware_emulation_board)
add_subdirectory(firmware_hydradancer)

if (DEFINED BUILD_TESTS)
Expand Down
2 changes: 1 addition & 1 deletion hydradancer/firmware_hydradancer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
26 changes: 21 additions & 5 deletions hydradancer/firmware_hydradancer/User/definitions.c
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
100 changes: 93 additions & 7 deletions hydradancer/firmware_hydradancer/User/definitions.h
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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) */
Expand All @@ -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;
Expand All @@ -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
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
31 changes: 19 additions & 12 deletions hydradancer/firmware_hydradancer/User/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 68526f4

Please sign in to comment.