diff options
| author | sakumisu <[email protected]> | 2026-07-02 13:23:15 +0800 |
|---|---|---|
| committer | sakumisu <[email protected]> | 2026-07-02 13:23:15 +0800 |
| commit | 8f04252f0fd77926ad369346ce3e3442552d5f1f (patch) | |
| tree | a9af23f2a9bb7a7aba9ed171c7ea312d25ea81df | |
| parent | b3195b95e0289ca04d079ab2c686dc122d7690f7 (diff) | |
update(tools): update usb speed test
Signed-off-by: sakumisu <[email protected]>
| -rw-r--r-- | docs/zh/demo/usbd_cdc_acm.rst | 8 | ||||
| -rw-r--r-- | docs/zh/index.rst | 1 | ||||
| -rw-r--r-- | docs/zh/quick_start/usb_speed.rst | 27 | ||||
| -rw-r--r-- | tools/test_srcipts/libusb_winusb_device_for_speed.c | 238 | ||||
| -rw-r--r-- | tools/test_srcipts/python_cdc_acm_for_speed.c | 175 | ||||
| -rw-r--r-- | tools/test_srcipts/test_cdc_speed.c | 506 | ||||
| -rw-r--r-- | tools/test_srcipts/test_cdc_speed.exe | bin | 0 -> 107594 bytes |
7 files changed, 947 insertions, 8 deletions
diff --git a/docs/zh/demo/usbd_cdc_acm.rst b/docs/zh/demo/usbd_cdc_acm.rst index 5ae3568c..758c9e73 100644 --- a/docs/zh/demo/usbd_cdc_acm.rst +++ b/docs/zh/demo/usbd_cdc_acm.rst @@ -98,13 +98,5 @@ CDC ACM Device } - 上述我们需要注意,长度设置为 2048 是为了测试 ZLP 功能,通常实际使用时,接收长度应该使用 CDC_MAX_MPS 。具体原因参考 :ref:`usb_ext` -- 如果需要做性能测试,使用 tools/test_srcipts/test_cdc_speed.py 进行测试,并在测试之前删除 `usbd_cdc_acm_bulk_out` 和 `usbd_cdc_acm_bulk_in` 中的打印,否则会影响测试结果。 - -测试命令如下: - -.. code-block:: python - - python test_cdc_speed.py --port COM17 --mode tx --chunk-length 4M - python test_cdc_speed.py --port COM17 --mode rx --chunk-length 4M .. note:: 对于 CDC ACM 搭配 OS 的情况,通常我们 read 使用异步并将数据存储到 ringbuffer 中,write 使用同步搭配 sem 使用。
\ No newline at end of file diff --git a/docs/zh/index.rst b/docs/zh/index.rst index f524fe42..6e3b2f4a 100644 --- a/docs/zh/index.rst +++ b/docs/zh/index.rst @@ -59,6 +59,7 @@ CherryUSB 是一个小而美的、可移植性高的、用于嵌入式系统的 quick_start/q&a quick_start/migration quick_start/opensource + quick_start/usb_speed .. toctree:: :maxdepth: 1 diff --git a/docs/zh/quick_start/usb_speed.rst b/docs/zh/quick_start/usb_speed.rst new file mode 100644 index 00000000..24a43c3c --- /dev/null +++ b/docs/zh/quick_start/usb_speed.rst @@ -0,0 +1,27 @@ +USB 极限速度测试 +====================== + +在 tools 中我们提供了一个 USB 极限速度测试工具,帮助用户测试 USB 设备的传输速度。该工具可以测量 USB 设备在不同条件下的读写速度,并生成详细的测试报告。 + +- test_cdc_speed.c + libusb_winusb_device_for_speed.c + +该工具主要使用 libusb 进行测试,因此,需要保证本地文件需要有 libusb 库和头文件,device 使用 winusb,因为如果使用 cdc acm,则需要进行驱动转换。使用如下: + +.. code-block:: C + + #默认已经提供 test_cdc_speed.exe + + gcc -O2 -o test_cdc_speed.exe .\test_cdc_speed.c .\libusb-1.0.dll.a -I. -lm + .\test_cdc_speed.exe --vendor 0xFFFF --product 0xFFFF --interface 0 --mode tx --chunk-length 4M + .\test_cdc_speed.exe --vendor 0xFFFF --product 0xFFFF --interface 0 --mode rx --chunk-length 4M + +- test_cdc_speed.py + +该工具主要使用 pyserial 进行测试,因此,需要保证已经安装 pyserial 库。使用如下: + +.. code-block:: C + + python test_cdc_speed.py --port COM6 --mode tx --chunk-length 4M + python test_cdc_speed.py --port COM6 --mode rx --chunk-length 4M + +.. note:: python 测试 TX 和 libusb 基本相同,rx 会略低于 libusb,主要原因是因为 pyserial 读取后会放到缓存中,多一次拷贝导致
\ No newline at end of file diff --git a/tools/test_srcipts/libusb_winusb_device_for_speed.c b/tools/test_srcipts/libusb_winusb_device_for_speed.c new file mode 100644 index 00000000..bcd02a03 --- /dev/null +++ b/tools/test_srcipts/libusb_winusb_device_for_speed.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" + +#define WCID_VENDOR_CODE 0x17 + +#define USBD_VID 0xffff +#define USBD_PID 0xffff +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +#define WINUSB_IN_EP 0x81 +#define WINUSB_OUT_EP 0x02 + +#define USBD_PID_WINUSB10 USBD_PID +#define USB_CONFIG_SIZE (9 + 9 + 7 + 7) +#define INTF_NUM 1 + +static const uint8_t WCID_StringDescriptor_MSOS[18] = { + USB_MSOSV1_STRING_DESCRIPTOR_INIT(WCID_VENDOR_CODE) +}; + +static const uint8_t WINUSB_WCIDDescriptor[] = { + USB_MSOSV1_COMP_ID_HEADER_DESCRIPTOR_INIT(INTF_NUM), + USB_MSOSV1_COMP_ID_FUNCTION_WINUSB_DESCRIPTOR_INIT(0), +}; + +static const uint8_t WINUSB_IF0_WCIDProperties[142] = { + /* + * WCID property descriptor + */ + 0x8e, 0x00, 0x00, 0x00, /* dwLength */ + 0x00, 0x01, /* bcdVersion */ + 0x05, 0x00, /* wIndex */ + 0x01, 0x00, /* wCount */ + + /* + * registry propter descriptor + */ + 0x84, 0x00, 0x00, 0x00, /* dwSize */ + 0x01, 0x00, 0x00, 0x00, /* dwPropertyDataType */ + 0x28, 0x00, /* wPropertyNameLength */ + /* DeviceInterfaceGUID */ + 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, /* wcName_20 */ + 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, /* wcName_20 */ + 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, /* wcName_20 */ + 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, /* wcName_20 */ + 'U', 0x00, 'I', 0x00, 'D', 0x00, 0x00, 0x00, /* wcName_20 */ + 0x4e, 0x00, 0x00, 0x00, /* dwPropertyDataLength */ + /* {1D4B2365-4749-48EA-B38A-7C6FDDDD7E26} */ + '{', 0x00, '1', 0x00, 'D', 0x00, '4', 0x00, /* wcData_39 */ + 'B', 0x00, '2', 0x00, '3', 0x00, '6', 0x00, /* wcData_39 */ + '5', 0x00, '-', 0x00, '4', 0x00, '7', 0x00, /* wcData_39 */ + '4', 0x00, '9', 0x00, '-', 0x00, '4', 0x00, /* wcData_39 */ + '8', 0x00, 'E', 0x00, 'A', 0x00, '-', 0x00, /* wcData_39 */ + 'B', 0x00, '3', 0x00, '8', 0x00, 'A', 0x00, /* wcData_39 */ + '-', 0x00, '7', 0x00, 'C', 0x00, '6', 0x00, /* wcData_39 */ + 'F', 0x00, 'D', 0x00, 'D', 0x00, 'D', 0x00, /* wcData_39 */ + 'D', 0x00, '7', 0x00, 'E', 0x00, '2', 0x00, /* wcData_39 */ + '6', 0x00, '}', 0x00, 0x00, 0x00, /* wcData_39 */ +}; + +const uint8_t *WINUSB_IFx_WCIDProperties[] = { + WINUSB_IF0_WCIDProperties, +}; + +struct usb_msosv1_descriptor msosv1_desc = { + .string = WCID_StringDescriptor_MSOS, + .vendor_code = WCID_VENDOR_CODE, + .compat_id = WINUSB_WCIDDescriptor, + .comp_id_property = WINUSB_IFx_WCIDProperties, +}; + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, USBD_VID, USBD_PID_WINUSB10, 0x0001, 0x01), +}; + +static const uint8_t config_descriptor_hs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00), +}; + +static const uint8_t config_descriptor_fs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00), +}; + +static const uint8_t device_quality_descriptor[] = { + USB_DEVICE_QUALIFIER_DESCRIPTOR_INIT(USB_2_0, 0x00, 0x00, 0x00, 0x01), +}; + +static const uint8_t other_speed_config_descriptor_hs[] = { + USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_FS, 0x00), +}; + +static const uint8_t other_speed_config_descriptor_fs[] = { + USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, INTF_NUM, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + USB_INTERFACE_DESCRIPTOR_INIT(0x00, 0x00, 0x02, 0xff, 0xff, 0x00, 0x04), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_IN_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00), + USB_ENDPOINT_DESCRIPTOR_INIT(WINUSB_OUT_EP, USB_ENDPOINT_TYPE_BULK, USB_BULK_EP_MPS_HS, 0x00), +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB WinUSB Demo", /* Product */ + "2024051701", /* Serial Number */ + "CherryUSB WinUSB0", +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + (void)speed; + + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + if (speed == USB_SPEED_HIGH) { + return config_descriptor_hs; + } else if (speed == USB_SPEED_FULL) { + return config_descriptor_fs; + } else { + return NULL; + } +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + (void)speed; + + return device_quality_descriptor; +} + +static const uint8_t *other_speed_config_descriptor_callback(uint8_t speed) +{ + if (speed == USB_SPEED_HIGH) { + return other_speed_config_descriptor_hs; + } else if (speed == USB_SPEED_FULL) { + return other_speed_config_descriptor_fs; + } else { + return NULL; + } +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + (void)speed; + + if (index >= (sizeof(string_descriptors) / sizeof(char *))) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor winusb_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .other_speed_descriptor_callback = other_speed_config_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback, + .msosv1_descriptor = &msosv1_desc, +}; + +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[16384]; +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[16384]; + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + /* setup first out ep read transfer */ + usbd_ep_start_read(busid, WINUSB_OUT_EP, read_buffer, sizeof(read_buffer)); + usbd_ep_start_write(busid, WINUSB_IN_EP, write_buffer, sizeof(write_buffer)); + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +void usbd_winusb_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + usbd_ep_start_read(busid, WINUSB_OUT_EP, read_buffer, sizeof(read_buffer)); +} + +void usbd_winusb_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + usbd_ep_start_write(busid, WINUSB_IN_EP, write_buffer, sizeof(write_buffer)); +} + +struct usbd_endpoint winusb_out_ep1 = { + .ep_addr = WINUSB_OUT_EP, + .ep_cb = usbd_winusb_out +}; + +struct usbd_endpoint winusb_in_ep1 = { + .ep_addr = WINUSB_IN_EP, + .ep_cb = usbd_winusb_in +}; + +struct usbd_interface intf0; + +void winusb_init(uint8_t busid, uint32_t reg_base) +{ + usbd_desc_register(busid, &winusb_descriptor); + + usbd_add_interface(busid, &intf0); + usbd_add_endpoint(busid, &winusb_out_ep1); + usbd_add_endpoint(busid, &winusb_in_ep1); + + usbd_initialize(busid, reg_base, usbd_event_handler); +} diff --git a/tools/test_srcipts/python_cdc_acm_for_speed.c b/tools/test_srcipts/python_cdc_acm_for_speed.c new file mode 100644 index 00000000..e436bae4 --- /dev/null +++ b/tools/test_srcipts/python_cdc_acm_for_speed.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "usbd_core.h" +#include "usbd_cdc_acm.h" + +#define USBD_VID 0xffff +#define USBD_PID 0xffff +#define USBD_MAX_POWER 100 +#define USBD_LANGID_STRING 1033 + +/*!< endpoint address */ +#define CDC_IN_EP 0x81 +#define CDC_OUT_EP 0x02 +#define CDC_INT_EP 0x83 + +/*!< config descriptor size */ +#define USB_CONFIG_SIZE (9 + CDC_ACM_DESCRIPTOR_LEN) + +static const uint8_t device_descriptor[] = { + USB_DEVICE_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, USBD_VID, USBD_PID, 0x0100, 0x01) +}; + +static const uint8_t config_descriptor_hs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_HS, 0x02), +}; + +static const uint8_t config_descriptor_fs[] = { + USB_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_FS, 0x02), +}; + +static const uint8_t device_quality_descriptor[] = { + USB_DEVICE_QUALIFIER_DESCRIPTOR_INIT(USB_2_0, 0xEF, 0x02, 0x01, 0x01), +}; + +static const uint8_t other_speed_config_descriptor_hs[] = { + USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_FS, 0x02), +}; + +static const uint8_t other_speed_config_descriptor_fs[] = { + USB_OTHER_SPEED_CONFIG_DESCRIPTOR_INIT(USB_CONFIG_SIZE, 0x02, 0x01, USB_CONFIG_BUS_POWERED, USBD_MAX_POWER), + CDC_ACM_DESCRIPTOR_INIT(0x00, CDC_INT_EP, CDC_OUT_EP, CDC_IN_EP, USB_BULK_EP_MPS_HS, 0x02), +}; + +static const char *string_descriptors[] = { + (const char[]){ 0x09, 0x04 }, /* Langid */ + "CherryUSB", /* Manufacturer */ + "CherryUSB CDC DEMO", /* Product */ + "2024051702", /* Serial Number */ +}; + +static const uint8_t *device_descriptor_callback(uint8_t speed) +{ + (void)speed; + + return device_descriptor; +} + +static const uint8_t *config_descriptor_callback(uint8_t speed) +{ + if (speed == USB_SPEED_HIGH) { + return config_descriptor_hs; + } else if (speed == USB_SPEED_FULL) { + return config_descriptor_fs; + } else { + return NULL; + } +} + +static const uint8_t *device_quality_descriptor_callback(uint8_t speed) +{ + (void)speed; + + return device_quality_descriptor; +} + +static const uint8_t *other_speed_config_descriptor_callback(uint8_t speed) +{ + if (speed == USB_SPEED_HIGH) { + return other_speed_config_descriptor_hs; + } else if (speed == USB_SPEED_FULL) { + return other_speed_config_descriptor_fs; + } else { + return NULL; + } +} + +static const char *string_descriptor_callback(uint8_t speed, uint8_t index) +{ + (void)speed; + + if (index >= (sizeof(string_descriptors) / sizeof(char *))) { + return NULL; + } + return string_descriptors[index]; +} + +const struct usb_descriptor cdc_descriptor = { + .device_descriptor_callback = device_descriptor_callback, + .config_descriptor_callback = config_descriptor_callback, + .device_quality_descriptor_callback = device_quality_descriptor_callback, + .other_speed_descriptor_callback = other_speed_config_descriptor_callback, + .string_descriptor_callback = string_descriptor_callback, +}; + +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t read_buffer[16384]; +USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t write_buffer[16384]; + +static void usbd_event_handler(uint8_t busid, uint8_t event) +{ + switch (event) { + case USBD_EVENT_RESET: + break; + case USBD_EVENT_CONNECTED: + break; + case USBD_EVENT_DISCONNECTED: + break; + case USBD_EVENT_RESUME: + break; + case USBD_EVENT_SUSPEND: + break; + case USBD_EVENT_CONFIGURED: + usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, sizeof(read_buffer)); + usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, sizeof(write_buffer)); + break; + case USBD_EVENT_SET_REMOTE_WAKEUP: + break; + case USBD_EVENT_CLR_REMOTE_WAKEUP: + break; + + default: + break; + } +} + +void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, sizeof(read_buffer)); +} + +void usbd_cdc_acm_bulk_in(uint8_t busid, uint8_t ep, uint32_t nbytes) +{ + usbd_ep_start_write(busid, CDC_IN_EP, write_buffer, sizeof(write_buffer)); +} + +/*!< endpoint call back */ +struct usbd_endpoint cdc_out_ep = { + .ep_addr = CDC_OUT_EP, + .ep_cb = usbd_cdc_acm_bulk_out +}; + +struct usbd_endpoint cdc_in_ep = { + .ep_addr = CDC_IN_EP, + .ep_cb = usbd_cdc_acm_bulk_in +}; + +static struct usbd_interface intf0; +static struct usbd_interface intf1; + +/* function ------------------------------------------------------------------*/ + +void cdc_acm_init(uint8_t busid, uint32_t reg_base) +{ + usbd_desc_register(busid, &cdc_descriptor); + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf0)); + usbd_add_interface(busid, usbd_cdc_acm_init_intf(busid, &intf1)); + usbd_add_endpoint(busid, &cdc_out_ep); + usbd_add_endpoint(busid, &cdc_in_ep); + usbd_initialize(busid, reg_base, usbd_event_handler); +}
\ No newline at end of file diff --git a/tools/test_srcipts/test_cdc_speed.c b/tools/test_srcipts/test_cdc_speed.c new file mode 100644 index 00000000..28aa7ebd --- /dev/null +++ b/tools/test_srcipts/test_cdc_speed.c @@ -0,0 +1,506 @@ +/* + * Copyright (c) 2026, sakumisu + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include <time.h> +#include <signal.h> +#include <getopt.h> +#include "libusb.h" + +#ifdef _WIN32 +#include <windows.h> +#endif + +#define DEFAULT_VENDOR_ID 0xFFFF +#define DEFAULT_PRODUCT_ID 0xFFFF +#define DEFAULT_INTERFACE 0 +#define DEFAULT_TIMEOUT 1000 /* 1 second */ +#define MAX_PACKET_SIZE (4 * 1024 * 1024) +#define REPORT_INTERVAL 1.0 +#define TEST_PATTERN 0xAA + +typedef struct { + int vendor_id; + int product_id; + int interface_num; + int timeout; + char mode; /* 't' for tx, 'r' for rx */ + uint32_t chunk_length; + double report_interval; + unsigned char endpoint_out; + unsigned char endpoint_in; +} test_config_t; + +static volatile int should_exit = 0; + +void print_device_layout(libusb_device *device) +{ + struct libusb_config_descriptor *config_desc = NULL; + int r = libusb_get_config_descriptor(device, 0, &config_desc); + if (r < 0 || !config_desc) { + return; + } + + for (int if_num = 0; if_num < config_desc->bNumInterfaces; if_num++) { + const struct libusb_interface *interface = &config_desc->interface[if_num]; + for (int alt = 0; alt < interface->num_altsetting; alt++) { + const struct libusb_interface_descriptor *if_desc = &interface->altsetting[alt]; + printf(" Interface %d alt %d class 0x%02X endpoints %d\n", + if_desc->bInterfaceNumber, + if_desc->bAlternateSetting, + if_desc->bInterfaceClass, + if_desc->bNumEndpoints); + + for (int ep = 0; ep < if_desc->bNumEndpoints; ep++) { + const struct libusb_endpoint_descriptor *ep_desc = &if_desc->endpoint[ep]; + const char *dir = (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ? "IN" : "OUT"; + const char *type = "OTHER"; + int transfer_type = ep_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK; + if (transfer_type == LIBUSB_TRANSFER_TYPE_BULK) { + type = "BULK"; + } else if (transfer_type == LIBUSB_TRANSFER_TYPE_INTERRUPT) { + type = "INT"; + } else if (transfer_type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) { + type = "ISO"; + } else if (transfer_type == LIBUSB_TRANSFER_TYPE_CONTROL) { + type = "CTRL"; + } + + printf(" EP 0x%02X %s %s\n", ep_desc->bEndpointAddress, dir, type); + } + } + } + + libusb_free_config_descriptor(config_desc); +} + +libusb_device_handle* open_device_with_diagnostics(libusb_context *ctx, int vendor_id, int product_id) +{ + libusb_device **list = NULL; + ssize_t count = libusb_get_device_list(ctx, &list); + if (count < 0) { + fprintf(stderr, "Failed to enumerate USB devices: %s\n", libusb_error_name((int)count)); + return NULL; + } + + libusb_device_handle *handle = NULL; + int found_match = 0; + + for (ssize_t i = 0; i < count; i++) { + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(list[i], &desc); + if (r < 0) { + continue; + } + + if (desc.idVendor == vendor_id && desc.idProduct == product_id) { + found_match = 1; + + printf("Matched device VID:0x%04X PID:0x%04X bus %u addr %u\n", + vendor_id, + product_id, + libusb_get_bus_number(list[i]), + libusb_get_device_address(list[i])); + print_device_layout(list[i]); + + r = libusb_open(list[i], &handle); + if (r == 0 && handle) { + break; + } + + fprintf(stderr, + "Matched VID:0x%04X PID:0x%04X but open failed: %s\n", + vendor_id, product_id, libusb_error_name(r)); + +#ifdef _WIN32 + if (r == LIBUSB_ERROR_NOT_FOUND || r == LIBUSB_ERROR_ACCESS) { + fprintf(stderr, + "Hint: On Windows this usually means the target interface is not bound to WinUSB/libusbK.\n"); + fprintf(stderr, + " Rebind the target interface driver (not the whole composite device) and retry --interface N.\n"); + } +#endif + } + } + + if (!handle && !found_match) { + fprintf(stderr, "No USB device matched VID:0x%04X PID:0x%04X in enumeration\n", + vendor_id, product_id); + } + + libusb_free_device_list(list, 1); + return handle; +} + +void signal_handler(int signum) +{ + should_exit = 1; +} + +double get_time_ms(void) +{ +#ifdef _WIN32 + return (double)GetTickCount(); +#else + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return ts.tv_sec * 1000.0 + ts.tv_nsec / 1000000.0; +#endif +} + +const char* format_speed(uint64_t byte_count, double duration_sec) +{ + static char buffer[64]; + if (duration_sec <= 0) { + snprintf(buffer, sizeof(buffer), "0.00 MB/s (0.00 Mbps)"); + return buffer; + } + + double mb_per_sec = byte_count / duration_sec / 1024.0 / 1024.0; + double mbps = byte_count * 8.0 / duration_sec / 1000.0 / 1000.0; + snprintf(buffer, sizeof(buffer), "%.2f MB/s (%.2f Mbps)", mb_per_sec, mbps); + return buffer; +} + +void print_progress(const char *prefix, uint64_t done_bytes, uint64_t bytes_in_window, + double start_time, double window_start) +{ + double now = get_time_ms() / 1000.0; + double avg_speed_duration = (now - start_time); + double instant_speed_duration = (now - window_start); + + printf("%s: total %llu bytes, instant %s, average %s\n", + prefix, (unsigned long long)done_bytes, + format_speed(bytes_in_window, instant_speed_duration), + format_speed(done_bytes, avg_speed_duration)); +} + +uint64_t parse_size(const char *value) +{ + char *endptr; + double number = strtod(value, &endptr); + + if (endptr == value) { + return 0; + } + + while (*endptr && (*endptr == ' ' || *endptr == '\t')) { + endptr++; + } + + const char *unit = endptr; + uint64_t factor = 1; + + if (strcasecmp(unit, "b") == 0 || strcmp(unit, "") == 0) { + factor = 1; + } else if (strcasecmp(unit, "k") == 0 || strcasecmp(unit, "kb") == 0) { + factor = 1024; + } else if (strcasecmp(unit, "m") == 0 || strcasecmp(unit, "mb") == 0) { + factor = 1024 * 1024; + } else if (strcasecmp(unit, "g") == 0 || strcasecmp(unit, "gb") == 0) { + factor = 1024 * 1024 * 1024; + } + + return (uint64_t)(number * factor); +} + +int find_cdc_endpoints(libusb_device *device, test_config_t *config) +{ + struct libusb_device_descriptor desc; + struct libusb_config_descriptor *config_desc; + const struct libusb_interface *interface; + const struct libusb_interface_descriptor *if_desc; + const struct libusb_endpoint_descriptor *ep_desc; + int r; + + r = libusb_get_device_descriptor(device, &desc); + if (r < 0) { + fprintf(stderr, "Failed to get device descriptor\n"); + return r; + } + + r = libusb_get_config_descriptor(device, 0, &config_desc); + if (r < 0) { + fprintf(stderr, "Failed to get config descriptor\n"); + return r; + } + + interface = &config_desc->interface[config->interface_num]; + if_desc = &interface->altsetting[0]; + + for (int i = 0; i < if_desc->bNumEndpoints; i++) { + ep_desc = &if_desc->endpoint[i]; + + if ((ep_desc->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK) { + if (ep_desc->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) { + config->endpoint_in = ep_desc->bEndpointAddress; + printf("Found IN endpoint: 0x%02X\n", config->endpoint_in); + } else { + config->endpoint_out = ep_desc->bEndpointAddress; + printf("Found OUT endpoint: 0x%02X\n", config->endpoint_out); + } + } + } + + libusb_free_config_descriptor(config_desc); + + if (!config->endpoint_in || !config->endpoint_out) { + fprintf(stderr, "Could not find IN or OUT endpoint\n"); + return LIBUSB_ERROR_NOT_FOUND; + } + + return 0; +} + +int run_tx(libusb_device_handle *handle, test_config_t *config) +{ + uint8_t *buffer = malloc(config->chunk_length); + if (!buffer) { + fprintf(stderr, "Failed to allocate buffer\n"); + return LIBUSB_ERROR_NO_MEM; + } + + memset(buffer, TEST_PATTERN, config->chunk_length); + + uint64_t sent_bytes = 0; + double start_time = get_time_ms() / 1000.0; + double window_start = start_time; + uint64_t window_bytes = 0; + + printf("Starting TX test, packet size: %u bytes\n", config->chunk_length); + printf("Press Ctrl+C to stop\n"); + + while (!should_exit) { + int transferred = 0; + int r = libusb_bulk_transfer(handle, config->endpoint_out, buffer, + config->chunk_length, &transferred, config->timeout); + + if (r < 0) { + fprintf(stderr, "Transfer error: %s\n", libusb_error_name(r)); + continue; + } + + sent_bytes += transferred; + window_bytes += transferred; + + double now = get_time_ms() / 1000.0; + if (now - window_start >= config->report_interval) { + print_progress("TX", sent_bytes, window_bytes, start_time, window_start); + window_start = now; + window_bytes = 0; + } + } + + if (window_bytes > 0) { + print_progress("TX", sent_bytes, window_bytes, start_time, window_start); + } + + double total_time = get_time_ms() / 1000.0 - start_time; + printf("TX stopped: %s\n", format_speed(sent_bytes, total_time)); + + free(buffer); + return 0; +} + +int run_rx(libusb_device_handle *handle, test_config_t *config) +{ + uint8_t *buffer = malloc(config->chunk_length); + if (!buffer) { + fprintf(stderr, "Failed to allocate buffer\n"); + return LIBUSB_ERROR_NO_MEM; + } + + uint64_t recv_bytes = 0; + double start_time = get_time_ms() / 1000.0; + double window_start = start_time; + uint64_t window_bytes = 0; + + printf("Starting RX test, packet size: %u bytes\n", config->chunk_length); + printf("Press Ctrl+C to stop\n"); + + while (!should_exit) { + int transferred = 0; + int r = libusb_bulk_transfer(handle, config->endpoint_in, buffer, + config->chunk_length, &transferred, config->timeout); + + if (r < 0) { + fprintf(stderr, "Transfer error: %s\n", libusb_error_name(r)); + continue; + } + + if (transferred > 0) { + recv_bytes += transferred; + window_bytes += transferred; + + double now = get_time_ms() / 1000.0; + if (now - window_start >= config->report_interval) { + print_progress("RX", recv_bytes, window_bytes, start_time, window_start); + window_start = now; + window_bytes = 0; + } + } + } + + if (window_bytes > 0) { + print_progress("RX", recv_bytes, window_bytes, start_time, window_start); + } + + double total_time = get_time_ms() / 1000.0 - start_time; + printf("RX stopped: %s\n", format_speed(recv_bytes, total_time)); + + free(buffer); + return 0; +} + +void print_usage(const char *prog_name) +{ + printf("Usage: %s [options]\n", prog_name); + printf("Options:\n"); + printf(" -m, --mode <tx|rx> Mode: tx (send) or rx (receive) [required]\n"); + printf(" -v, --vendor <id> Vendor ID in hex (default: 0x%04X)\n", DEFAULT_VENDOR_ID); + printf(" -p, --product <id> Product ID in hex (default: 0x%04X)\n", DEFAULT_PRODUCT_ID); + printf(" -i, --interface <num> Interface number (default: %d)\n", DEFAULT_INTERFACE); + printf(" -c, --chunk-length <size> Packet size (default: 4096, supports K/M/G suffixes)\n"); + printf(" -r, --report-interval <sec> Report interval in seconds (default: %.1f)\n", REPORT_INTERVAL); + printf(" -t, --timeout <ms> USB timeout in milliseconds (default: %d)\n", DEFAULT_TIMEOUT); + printf(" -h, --help Show this help message\n"); +} + +int main(int argc, char *argv[]) +{ + test_config_t config = { + .vendor_id = DEFAULT_VENDOR_ID, + .product_id = DEFAULT_PRODUCT_ID, + .interface_num = DEFAULT_INTERFACE, + .timeout = DEFAULT_TIMEOUT, + .mode = 0, + .chunk_length = 4096, + .report_interval = REPORT_INTERVAL, + .endpoint_in = 0, + .endpoint_out = 0, + }; + + static struct option long_options[] = { + {"mode", required_argument, 0, 'm'}, + {"vendor", required_argument, 0, 'v'}, + {"product", required_argument, 0, 'p'}, + {"interface", required_argument, 0, 'i'}, + {"chunk-length", required_argument, 0, 'c'}, + {"report-interval", required_argument, 0, 'r'}, + {"timeout", required_argument, 0, 't'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt; + while ((opt = getopt_long(argc, argv, "m:v:p:i:c:r:t:h", long_options, NULL)) != -1) { + switch (opt) { + case 'm': + if (strcmp(optarg, "tx") == 0) { + config.mode = 't'; + } else if (strcmp(optarg, "rx") == 0) { + config.mode = 'r'; + } else { + fprintf(stderr, "Invalid mode: %s\n", optarg); + print_usage(argv[0]); + return 1; + } + break; + case 'v': + config.vendor_id = strtol(optarg, NULL, 16); + break; + case 'p': + config.product_id = strtol(optarg, NULL, 16); + break; + case 'i': + config.interface_num = atoi(optarg); + break; + case 'c': + config.chunk_length = parse_size(optarg); + if (config.chunk_length > MAX_PACKET_SIZE) { + fprintf(stderr, "Chunk length cannot exceed %u bytes\n", MAX_PACKET_SIZE); + return 1; + } + break; + case 'r': + config.report_interval = atof(optarg); + break; + case 't': + config.timeout = atoi(optarg); + break; + case 'h': + print_usage(argv[0]); + return 0; + default: + print_usage(argv[0]); + return 1; + } + } + + if (!config.mode) { + fprintf(stderr, "Error: mode (-m) is required\n"); + print_usage(argv[0]); + return 1; + } + + signal(SIGINT, signal_handler); + + libusb_context *ctx = NULL; + int r = libusb_init(&ctx); + if (r < 0) { + fprintf(stderr, "Failed to initialize libusb: %s\n", libusb_error_name(r)); + return 1; + } + + libusb_device_handle *handle = open_device_with_diagnostics(ctx, + config.vendor_id, + config.product_id); + if (!handle) { + fprintf(stderr, "Device not found (VID:0x%04X, PID:0x%04X), please use winusb device\n", + config.vendor_id, config.product_id); + libusb_exit(ctx); + return 1; + } + + printf("Device found: VID=0x%04X, PID=0x%04X\n", config.vendor_id, config.product_id); + + /* Find endpoints */ + libusb_device *device = libusb_get_device(handle); + r = find_cdc_endpoints(device, &config); + if (r < 0) { + fprintf(stderr, "Failed to find CDC endpoints\n"); + libusb_close(handle); + libusb_exit(ctx); + return 1; + } + + /* Claim interface */ + r = libusb_claim_interface(handle, config.interface_num); + if (r < 0) { + fprintf(stderr, "Failed to claim interface: %s\n", libusb_error_name(r)); + libusb_close(handle); + libusb_exit(ctx); + return 1; + } + + printf("Interface claimed successfully\n"); + printf("Chunk length: %u bytes\n", config.chunk_length); + + int result = 0; + if (config.mode == 't') { + result = run_tx(handle, &config); + } else { + result = run_rx(handle, &config); + } + + libusb_release_interface(handle, config.interface_num); + libusb_close(handle); + libusb_exit(ctx); + + return result; +} diff --git a/tools/test_srcipts/test_cdc_speed.exe b/tools/test_srcipts/test_cdc_speed.exe Binary files differnew file mode 100644 index 00000000..deb6f44d --- /dev/null +++ b/tools/test_srcipts/test_cdc_speed.exe |
