-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cdc_acm): Mocked device open/close
- Opening and closing mocked USB devices - Interaction with mocked USB devices
- Loading branch information
1 parent
555db55
commit 16912b6
Showing
27 changed files
with
986 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 12 additions & 0 deletions
12
host/class/cdc/usb_host_cdc_acm/host_test/device_interaction/CMakeLists.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
cmake_minimum_required(VERSION 3.16) | ||
|
||
include($ENV{IDF_PATH}/tools/cmake/project.cmake) | ||
set(COMPONENTS main) | ||
|
||
list(APPEND EXTRA_COMPONENT_DIRS | ||
"$ENV{IDF_PATH}/tools/mocks/usb/" | ||
#"$ENV{IDF_PATH}/tools/mocks/freertos/" We are using freertos as real component | ||
) | ||
|
||
add_definitions("-DCMOCK_MEM_DYNAMIC") | ||
project(host_test_usb_cdc) |
31 changes: 31 additions & 0 deletions
31
host/class/cdc/usb_host_cdc_acm/host_test/device_interaction/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
| Supported Targets | Linux | | ||
| ----------------- | ----- | | ||
|
||
# Description | ||
|
||
This directory contains test code for `USB Host CDC-ACM` driver. Namely: | ||
* Interactions with Mocked device added to the CDC-ACM driver (Device open, send mocked transfers, device close) | ||
|
||
Tests are written using [Catch2](https://github.com/catchorg/Catch2) test framework, use CMock, so you must install Ruby on your machine to run them. | ||
|
||
This test directory uses freertos as real component | ||
# Build | ||
|
||
Tests build regularly like an idf project. Currently only working on Linux machines. | ||
|
||
``` | ||
idf.py --preview set-target linux | ||
idf.py build | ||
``` | ||
|
||
# Run | ||
|
||
The build produces an executable in the build folder. | ||
|
||
Just run: | ||
|
||
``` | ||
./build/host_test_usb_cdc.elf | ||
``` | ||
|
||
The test executable have some options provided by the test framework. |
9 changes: 9 additions & 0 deletions
9
host/class/cdc/usb_host_cdc_acm/host_test/device_interaction/main/CMakeLists.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
idf_component_register(SRC_DIRS . | ||
REQUIRES cmock usb | ||
INCLUDE_DIRS "../../" . | ||
PRIV_INCLUDE_DIRS "../../../private_include" | ||
WHOLE_ARCHIVE) | ||
|
||
# Currently 'main' for IDF_TARGET=linux is defined in freertos component. | ||
# Since we are using a freertos mock here, need to let Catch2 provide 'main'. | ||
#target_link_libraries(${COMPONENT_LIB} PRIVATE Catch2WithMain) # We don't mock FreeRTOS for this test |
311 changes: 311 additions & 0 deletions
311
host/class/cdc/usb_host_cdc_acm/host_test/device_interaction/main/common_test_fixtures.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,311 @@ | ||
/* | ||
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include <stdio.h> | ||
#include <string.h> | ||
#include <catch2/catch_test_macros.hpp> | ||
#include "usb/cdc_acm_host.h" | ||
#include "mock_add_usb_device.h" | ||
#include "common_test_fixtures.hpp" | ||
#include "cdc_host_descriptor_parsing.h" | ||
|
||
extern "C" { | ||
#include "Mockusb_host.h" | ||
} | ||
|
||
/** | ||
* @brief CMock expectations for current CDC device | ||
* | ||
* Only used for mocked usb host expectations | ||
* | ||
* For example, based on the descriptors, we record that a certain device has CTRL EP, IN EP, OUT EP and NO notif EP | ||
* Then, during the device opening we expect usb_host_transfer_alloc() exactly 3 times (CTRL, IN, OUT) | ||
* Also, during the device closing we expect usb_host_transfer_free() exactly 3 times (CTRL, IN, OUT) | ||
* Different device could have CTRL, IN, OUT and NOTIF, thus we must expect those mocked functions exactly 4 times | ||
*/ | ||
typedef struct { | ||
struct { | ||
usb_transfer_t *out_xfer; | ||
usb_transfer_t *in_xfer; | ||
uint8_t in_bEndpointAddress; | ||
uint8_t out_bEndpointAddress; | ||
} data; | ||
struct { | ||
usb_transfer_t *xfer; | ||
uint8_t bEndpointAddress; | ||
} notif; | ||
} cdc_dev_expects_t; | ||
|
||
static cdc_dev_expects_t *p_cdc_dev_expects = nullptr; | ||
|
||
/** | ||
* @brief Create CMock expectations for current device | ||
* | ||
* This function creates CMock expectations for a USB device, | ||
* | ||
* @param[in] dev_address Device address | ||
* @param[in] interface_index Interface index to be used | ||
* @param[in] dev_config CDC-ACM Host device config struct | ||
* | ||
* @return | ||
* - ESP_OK: Mock expectations created successfully | ||
* - ESP_ERR_NO_MEM: Not enough memory | ||
*/ | ||
static esp_err_t _test_create_cmock_expectations(uint8_t dev_address, uint8_t interface_index, const cdc_acm_host_device_config_t *dev_config) | ||
{ | ||
cdc_dev_expects_t *cdc_dev_expects = (cdc_dev_expects_t *)calloc(1, sizeof(cdc_dev_expects_t)); | ||
if (cdc_dev_expects == nullptr) { | ||
return ESP_ERR_NO_MEM; | ||
} | ||
|
||
int notif_xfer, data_in_xfer, data_out_xfer; | ||
const usb_config_desc_t *config_desc; | ||
const usb_device_desc_t *device_desc; | ||
cdc_parsed_info_t cdc_info; | ||
usb_host_mock_get_config_descriptor_by_address(dev_address, &config_desc); | ||
usb_host_mock_get_device_descriptor_by_address(dev_address, &device_desc); | ||
cdc_parse_interface_descriptor(device_desc, config_desc, interface_index, &cdc_info); | ||
|
||
// Get IN and OUT endpoints addresses | ||
cdc_dev_expects->data.in_bEndpointAddress = cdc_info.in_ep->bEndpointAddress; | ||
cdc_dev_expects->data.out_bEndpointAddress = cdc_info.out_ep->bEndpointAddress; | ||
|
||
// Get notification endpoint address and check if notification transfer is allocated (if notif. EP exists) | ||
if (cdc_info.notif_ep) { | ||
cdc_dev_expects->notif.bEndpointAddress = cdc_info.notif_ep->bEndpointAddress; | ||
cdc_dev_expects->notif.xfer = reinterpret_cast<usb_transfer_t *>(¬if_xfer); | ||
} else { | ||
cdc_dev_expects->notif.bEndpointAddress = 0; | ||
cdc_dev_expects->notif.xfer = nullptr; | ||
} | ||
|
||
// Check, if IN data transfer is allocated | ||
if (dev_config->in_buffer_size) { | ||
cdc_dev_expects->data.in_xfer = reinterpret_cast<usb_transfer_t *>(&data_in_xfer); | ||
} else { | ||
cdc_dev_expects->data.in_xfer = nullptr; | ||
} | ||
|
||
// Check if OUT data transfer is allocated | ||
if (dev_config->out_buffer_size) { | ||
cdc_dev_expects->data.out_xfer = reinterpret_cast<usb_transfer_t *>(&data_out_xfer); | ||
} else { | ||
cdc_dev_expects->data.out_xfer = nullptr; | ||
} | ||
|
||
p_cdc_dev_expects = cdc_dev_expects; | ||
return ESP_OK; | ||
} | ||
|
||
/** | ||
* @brief free space allocated for p_cdc_dev_expects | ||
*/ | ||
static void _test_delete_cmock_expectations(void) | ||
{ | ||
free(p_cdc_dev_expects); | ||
p_cdc_dev_expects = nullptr; | ||
} | ||
|
||
esp_err_t test_cdc_acm_host_install(const cdc_acm_host_driver_config_t *driver_config) | ||
{ | ||
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_client_register_AddCallback(usb_host_client_register_mock_callback); | ||
|
||
usb_host_client_handle_events_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_client_handle_events_AddCallback(usb_host_client_handle_events_mock_callback); | ||
|
||
// Call the real function cdc_acm_host_install() | ||
return cdc_acm_host_install(driver_config); | ||
} | ||
|
||
esp_err_t test_cdc_acm_host_uninstall(void) | ||
{ | ||
usb_host_client_unblock_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_client_unblock_AddCallback(usb_host_client_unblock_mock_callback); | ||
|
||
usb_host_client_deregister_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_client_deregister_AddCallback(usb_host_client_deregister_mock_callback); | ||
|
||
_test_delete_cmock_expectations(); | ||
|
||
// Call the real function, cdc_acm_host_uninstall() | ||
return cdc_acm_host_uninstall(); | ||
} | ||
|
||
esp_err_t test_cdc_acm_host_open(uint8_t dev_address, uint16_t vid, uint16_t pid, uint8_t interface_index, const cdc_acm_host_device_config_t *dev_config, cdc_acm_dev_hdl_t *cdc_hdl_ret) | ||
{ | ||
esp_err_t ret = _test_create_cmock_expectations(dev_address, interface_index, dev_config); | ||
if (ret != ESP_OK) { | ||
return ret; // ESP_ERR_NO_MEM | ||
} | ||
|
||
// Device opening | ||
usb_host_device_addr_list_fill_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_device_addr_list_fill_AddCallback(usb_host_device_addr_list_fill_mock_callback); | ||
|
||
// We are expecting usb_host_device_open, usb_host_get_device_descriptor usb_host_device_close | ||
// to be called at least mocked_devs_count times | ||
const int mocked_devs_count = usb_host_mock_get_devs_count(); // Get number of mocked USB devices in mocked device list | ||
for (int i = 0; i < mocked_devs_count; i++) { | ||
usb_host_device_open_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_get_device_descriptor_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_device_close_ExpectAnyArgsAndReturn(ESP_OK); | ||
} | ||
|
||
usb_host_device_open_AddCallback(usb_host_device_open_mock_callback); | ||
usb_host_get_device_descriptor_AddCallback(usb_host_get_device_descriptor_mock_callback); | ||
usb_host_device_close_AddCallback(usb_host_device_close_mock_callback); | ||
|
||
// We have found the device by specified PID VID | ||
|
||
// Get Device and Configuration descriptors of the correct device | ||
usb_host_get_device_descriptor_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_get_active_config_descriptor_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_get_active_config_descriptor_AddCallback(usb_host_get_active_config_descriptor_mock_callback); | ||
|
||
// Setup control transfer | ||
usb_host_transfer_alloc_ExpectAnyArgsAndReturn(ESP_OK); | ||
|
||
// Setup Notif transfer | ||
if (p_cdc_dev_expects->notif.xfer) { | ||
usb_host_transfer_alloc_ExpectAnyArgsAndReturn(ESP_OK); | ||
} | ||
|
||
// Setup IN data transfer | ||
if (dev_config->in_buffer_size) { | ||
usb_host_transfer_alloc_ExpectAnyArgsAndReturn(ESP_OK); | ||
} | ||
|
||
// Setup OUT bulk transfer | ||
if (dev_config->out_buffer_size) { | ||
usb_host_transfer_alloc_ExpectAnyArgsAndReturn(ESP_OK); | ||
} | ||
|
||
// Register callback | ||
usb_host_transfer_alloc_AddCallback(usb_host_transfer_alloc_mock_callback); | ||
|
||
// Call cdc_acm_start | ||
|
||
// Claim data interface | ||
// Make sure that the interface_index has been claimed | ||
test_usb_host_interface_claim(interface_index); | ||
|
||
// Claim notification interface (if supported) | ||
if (p_cdc_dev_expects->notif.xfer) { | ||
test_usb_host_interface_claim(interface_index); | ||
} | ||
|
||
// Call the real function cdc_acm_host_open | ||
// Expect ESP_OK and dev_hdl non nullptr | ||
ret = cdc_acm_host_open(vid, pid, interface_index, dev_config, cdc_hdl_ret); | ||
|
||
// If the cdc_acm_host_open() fails, delete the created cdc_device | ||
if (ret != ESP_OK) { | ||
_test_delete_cmock_expectations(); | ||
} | ||
return ret; | ||
} | ||
|
||
esp_err_t test_cdc_acm_host_close(cdc_acm_dev_hdl_t *cdc_hdl, uint8_t interface_index) | ||
{ | ||
// Cancel pooling of IN endpoint -> halt, flush, clear | ||
test_cdc_acm_reset_transfer_endpoint(p_cdc_dev_expects->data.in_bEndpointAddress); | ||
|
||
// Cancel pooling of Notification endpoint -> halt, flush, clear | ||
if (p_cdc_dev_expects->notif.xfer) { | ||
test_cdc_acm_reset_transfer_endpoint(p_cdc_dev_expects->notif.bEndpointAddress); | ||
} | ||
|
||
// Release data interface | ||
usb_host_interface_release_ExpectAndReturn(nullptr, nullptr, interface_index, ESP_OK); | ||
usb_host_interface_release_IgnoreArg_client_hdl(); // Ignore all function parameters, except interface_index | ||
usb_host_interface_release_IgnoreArg_dev_hdl(); | ||
|
||
|
||
// Free notif transfer | ||
if (p_cdc_dev_expects->notif.xfer) { | ||
usb_host_transfer_free_ExpectAnyArgsAndReturn(ESP_OK); | ||
p_cdc_dev_expects->notif.xfer = nullptr; | ||
} | ||
|
||
// Free in transfer | ||
if (p_cdc_dev_expects->data.in_xfer) { | ||
usb_host_transfer_free_ExpectAnyArgsAndReturn(ESP_OK); | ||
p_cdc_dev_expects->data.in_xfer = nullptr; | ||
} | ||
|
||
// Free out transfer | ||
if (p_cdc_dev_expects->data.out_xfer) { | ||
usb_host_transfer_free_ExpectAnyArgsAndReturn(ESP_OK); | ||
p_cdc_dev_expects->data.out_xfer = nullptr; | ||
} | ||
|
||
// Call cdc_acm_device_remove | ||
// Free ctrl transfer | ||
usb_host_transfer_free_ExpectAnyArgsAndReturn(ESP_OK); | ||
usb_host_transfer_free_AddCallback(usb_host_transfer_free_mock_callback); | ||
|
||
usb_host_device_close_ExpectAnyArgsAndReturn(ESP_OK); | ||
|
||
// Call the real function cdc_acm_host_close | ||
return cdc_acm_host_close(*cdc_hdl); | ||
} | ||
|
||
esp_err_t test_cdc_acm_host_data_tx_blocking(cdc_acm_dev_hdl_t cdc_hdl, const uint8_t *data, size_t data_len, uint32_t timeout_ms, mock_usb_transfer_response_t transfer_response) | ||
{ | ||
usb_host_transfer_submit_ExpectAnyArgsAndReturn(ESP_OK); | ||
|
||
switch (transfer_response) { | ||
case MOCK_USB_TRANSFER_SUCCESS: { | ||
// Make the submitted transfer to pass | ||
usb_host_transfer_submit_AddCallback(usb_host_transfer_submit_success_mock_callback); | ||
break; | ||
} | ||
case MOCK_USB_TRANSFER_ERROR: { | ||
// Make the submitted transfer to fail | ||
usb_host_transfer_submit_AddCallback(usb_host_transfer_submit_invalid_response_mock_callback); | ||
break; | ||
} | ||
case MOCK_USB_TRANSFER_TIMEOUT: { | ||
// Make the submitted transfer to be timed out | ||
usb_host_transfer_submit_AddCallback(usb_host_transfer_submit_timeout_mock_callback); | ||
// Reset out endpoint | ||
test_cdc_acm_reset_transfer_endpoint(p_cdc_dev_expects->data.out_bEndpointAddress); | ||
break; | ||
} | ||
default: | ||
break; | ||
} | ||
|
||
// Call the real function cdc_acm_host_data_tx_blocking() | ||
return cdc_acm_host_data_tx_blocking(cdc_hdl, data, data_len, timeout_ms); | ||
} | ||
|
||
esp_err_t test_cdc_acm_reset_transfer_endpoint(uint8_t ep_address) | ||
{ | ||
// Expect correct ep_address for all (halt, flush, clear) | ||
usb_host_endpoint_halt_ExpectAndReturn(nullptr, ep_address, ESP_OK); | ||
usb_host_endpoint_halt_IgnoreArg_dev_hdl(); | ||
|
||
usb_host_endpoint_flush_ExpectAndReturn(nullptr, ep_address, ESP_OK); | ||
usb_host_endpoint_flush_IgnoreArg_dev_hdl(); | ||
|
||
usb_host_endpoint_clear_ExpectAndReturn(nullptr, ep_address, ESP_OK); | ||
usb_host_endpoint_clear_IgnoreArg_dev_hdl(); | ||
|
||
return ESP_OK; | ||
} | ||
|
||
esp_err_t test_usb_host_interface_claim(uint8_t interface_index) | ||
{ | ||
usb_host_interface_claim_ExpectAndReturn(nullptr, nullptr, interface_index, 0, ESP_OK); | ||
usb_host_interface_claim_IgnoreArg_client_hdl(); // Ignore all function parameters, except interface_index | ||
usb_host_interface_claim_IgnoreArg_dev_hdl(); | ||
usb_host_interface_claim_IgnoreArg_bAlternateSetting(); | ||
usb_host_transfer_submit_ExpectAnyArgsAndReturn(ESP_OK); | ||
|
||
return ESP_OK; | ||
} |
Oops, something went wrong.