Skip to content

Commit 36403b5

Browse files
Merge pull request #401 from espressif/feat/msc_host_add_linux_build
feat(msc_host): Add linux target build
2 parents 7583b53 + aacaa8b commit 36403b5

File tree

12 files changed

+290
-3
lines changed

12 files changed

+290
-3
lines changed

.build-test-rules.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ host/class/cdc/usb_host_cdc_acm/host_test:
7171
host/class/hid/usb_host_hid/host_test:
7272
<<: *host_test_enable_rules
7373

74+
host/class/msc/usb_host_msc/host_test:
75+
<<: *host_test_enable_rules
76+
7477
host/class/uvc/usb_host_uvc/host_test:
7578
<<: *host_test_enable_rules
7679

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
3+
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
4+
set(COMPONENTS main)
5+
6+
# Register usb component, must be registered before registering mock
7+
list(APPEND EXTRA_COMPONENT_DIRS "../../../../usb")
8+
9+
list(APPEND EXTRA_COMPONENT_DIRS
10+
"../../../../usb/test/mocks/usb_host_full_mock/usb" # Full USB Host stack mock (all the layers are mocked)
11+
12+
# The following line would be needed to include the freertos mock component if this test used mocked FreeRTOS.
13+
#"$ENV{IDF_PATH}/tools/mocks/freertos/"
14+
)
15+
16+
add_definitions("-DCMOCK_MEM_DYNAMIC")
17+
project(host_test_usb_msc)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
| Supported Targets | Linux |
2+
| ----------------- | ----- |
3+
4+
# Description
5+
6+
This directory contains test code for `USB Host MSC` driver. Namely:
7+
8+
- Simple public API call with mocked USB component to test Linux build and Cmock run for this class driver
9+
10+
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.
11+
12+
# Build
13+
14+
Tests build regularly like an idf project. Currently only working on Linux machines.
15+
16+
```
17+
idf.py --preview set-target linux
18+
idf.py build
19+
```
20+
21+
# Run
22+
23+
The build produces an executable in the build folder.
24+
25+
Just run:
26+
27+
```
28+
idf.py monitor
29+
```
30+
31+
or run the executable directly:
32+
33+
```
34+
./build/host_test_usb_msc.elf
35+
```
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
idf_component_register(SRC_DIRS .
2+
REQUIRES cmock
3+
INCLUDE_DIRS .
4+
WHOLE_ARCHIVE)
5+
6+
# We do not use main() from esp-idf as it discards argc and argv arguments
7+
# We wrap main so argc and argv are correctly passed to Catch2
8+
target_link_libraries(${COMPONENT_LIB} INTERFACE "-Wl,--wrap=main")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dependencies:
2+
espressif/catch2: "^3.4.0"
3+
usb_host_msc:
4+
version: "*"
5+
override_path: "../../"
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <catch2/catch_session.hpp>
9+
#include "freertos/FreeRTOS.h"
10+
#include "freertos/task.h"
11+
12+
struct MainTaskArgs {
13+
int argc;
14+
const char **argv;
15+
};
16+
17+
static void main_task(void *args)
18+
{
19+
MainTaskArgs *task_args = (MainTaskArgs *)args;
20+
auto result = Catch::Session().run(task_args->argc, task_args->argv);
21+
22+
fflush(stdout);
23+
delete task_args;
24+
exit(result);
25+
vTaskDelete(NULL);
26+
}
27+
28+
extern "C" int __wrap_main(int argc, const char **argv)
29+
{
30+
// Following section is copied from components\freertos\FreeRTOS-Kernel\portable\linux\port_idf.c
31+
// It starts the FreeRTOS scheduler and creates the main task to run Catch2 tests.
32+
// Only difference from esp-idf implementation is passing of argc and argv to the main task.
33+
34+
// This makes sure that stdio is always synchronized so that idf.py monitor
35+
// and other tools read text output on time.
36+
setvbuf(stdout, NULL, _IONBF, 0);
37+
38+
usleep(1000);
39+
MainTaskArgs *task_args = new MainTaskArgs{argc, argv};
40+
BaseType_t res = xTaskCreatePinnedToCore(&main_task, "main",
41+
ESP_TASK_MAIN_STACK, task_args,
42+
ESP_TASK_MAIN_PRIO, NULL, ESP_TASK_MAIN_CORE);
43+
assert(res == pdTRUE);
44+
(void)res;
45+
46+
vTaskStartScheduler();
47+
48+
// This line should never be reached
49+
assert(false);
50+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <stdio.h>
8+
#include <catch2/catch_test_macros.hpp>
9+
10+
#include "usb/msc_host_vfs.h"
11+
#include "mock_add_usb_device.h"
12+
13+
extern "C" {
14+
#include "Mockusb_host.h"
15+
}
16+
17+
SCENARIO("MSC Host pre-uninstall")
18+
{
19+
// No MSC Driver installed
20+
GIVEN("NO MSC Host previously installed") {
21+
22+
// Uninstall not-installed MAC Host driver
23+
SECTION("Uninstalling not installed MSC Host returns error") {
24+
// Call the DUT function, expect ESP_ERR_INVALID_STATE
25+
REQUIRE(ESP_ERR_INVALID_STATE == msc_host_uninstall());
26+
}
27+
}
28+
}
29+
30+
SCENARIO("MSC Host install")
31+
{
32+
msc_host_driver_config_t msc_host_driver_config = {
33+
.create_backround_task = true,
34+
.task_priority = 5,
35+
.stack_size = 4096,
36+
.core_id = 0,
37+
.callback = (reinterpret_cast<msc_host_event_cb_t>(0xdeadbeef)),
38+
.callback_arg = nullptr,
39+
};
40+
41+
// MSC Host driver config set to nullptr
42+
GIVEN("NO MSC Host driver config, driver not already installed") {
43+
44+
SECTION("Config is nullptr") {
45+
// Call the DUT function msc_host_install with msc_host_driver_config set to nullptr
46+
REQUIRE(ESP_ERR_INVALID_ARG == msc_host_install(nullptr));
47+
}
48+
}
49+
50+
// MSC Host driver config set to config from MSC Host example,
51+
// but with various various changes causing MSC Host driver installation to fail
52+
GIVEN("Minimal MSC Host driver config, driver not already installed") {
53+
54+
SECTION("Config error: no callback") {
55+
msc_host_driver_config.callback = nullptr;
56+
// Call the DUT function, expect ESP_ERR_INVALID_ARG
57+
REQUIRE(ESP_ERR_INVALID_ARG == msc_host_install(&msc_host_driver_config));
58+
}
59+
60+
SECTION("Config error: stack size is 0") {
61+
msc_host_driver_config.stack_size = 0;
62+
// Call the DUT function, expect ESP_ERR_INVALID_ARG
63+
REQUIRE(ESP_ERR_INVALID_ARG == msc_host_install(&msc_host_driver_config));
64+
}
65+
66+
SECTION("Config error: task priority is 0") {
67+
msc_host_driver_config.task_priority = 0;
68+
// Call the DUT function, expect ESP_ERR_INVALID_ARG
69+
REQUIRE(ESP_ERR_INVALID_ARG == msc_host_install(&msc_host_driver_config));
70+
}
71+
}
72+
73+
// MSC Host driver config set to config from MSC Host example
74+
GIVEN("Full MSC Host config, driver not already installed") {
75+
76+
// Unable to register client: Invalid state, goto fail
77+
SECTION("Client register not successful: goto fail") {
78+
// Register a client, return ESP_ERR_INVALID_STATE, so the client is not registered successfully
79+
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE);
80+
81+
// goto fail: delete the semaphore and deregister client
82+
usb_host_client_deregister_ExpectAnyArgsAndReturn(ESP_OK);
83+
84+
// Call the DUT function, expect ESP_ERR_INVALID_STATE
85+
REQUIRE(ESP_ERR_INVALID_STATE == msc_host_install(&msc_host_driver_config));
86+
}
87+
88+
// Call msc_host_install and expect successful installation
89+
SECTION("Client register successful: msc_host_install successful") {
90+
91+
// Register a client, return ESP_OK, so the client is registered successfully
92+
usb_host_client_register_ExpectAnyArgsAndReturn(ESP_OK);
93+
usb_host_client_register_AddCallback(usb_host_client_register_mock_callback);
94+
95+
// create USB Host Library client processing function
96+
usb_host_client_handle_events_ExpectAnyArgsAndReturn(ESP_OK);
97+
usb_host_client_handle_events_AddCallback(usb_host_client_handle_events_mock_callback);
98+
99+
// Call the DUT function, expect ESP_OK
100+
REQUIRE(ESP_OK == msc_host_install(&msc_host_driver_config));
101+
}
102+
}
103+
104+
// MSC Host driver config set to config from MSC Host example
105+
GIVEN("Full MSC Host config, driver already installed") {
106+
107+
// Driver is already installed
108+
SECTION("Install error: driver already installed") {
109+
// Call the DUT function, expect ESP_ERR_INVALID_STATE
110+
REQUIRE(ESP_ERR_INVALID_STATE == msc_host_install(&msc_host_driver_config));
111+
}
112+
}
113+
}
114+
115+
SCENARIO("MSC Host post-uninstall")
116+
{
117+
// MSC Host driver successfully installed
118+
GIVEN("MSC Host previously installed") {
119+
120+
// MSC Driver is installed, uninstall the driver
121+
SECTION("MSC Host driver uninstall successful") {
122+
123+
// Unblock client, return ESP_OK, register callback for unblocking the USB Host Library client processing function
124+
usb_host_client_unblock_ExpectAnyArgsAndReturn(ESP_OK);
125+
usb_host_client_unblock_AddCallback(usb_host_client_unblock_mock_callback);
126+
127+
// Deregister the client
128+
usb_host_client_deregister_ExpectAnyArgsAndReturn(ESP_OK);
129+
usb_host_client_deregister_AddCallback(usb_host_client_deregister_mock_callback);
130+
131+
// Call the DUT function, expect ESP_OK
132+
REQUIRE(ESP_OK == msc_host_uninstall());
133+
}
134+
}
135+
136+
// MSC Host driver successfully uninstalled
137+
GIVEN("MSC Host successfully uninstalled") {
138+
139+
// MSC Driver already successfully uninstalled, uninstall it again
140+
SECTION("Uninstall already uninstalled MSC Host driver") {
141+
// Call the DUT function, expect error
142+
REQUIRE(ESP_ERR_INVALID_STATE == msc_host_uninstall());
143+
}
144+
}
145+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
2+
# SPDX-License-Identifier: Unlicense OR CC0-1.0
3+
import pytest
4+
from pytest_embedded import Dut
5+
from pytest_embedded_idf.utils import idf_parametrize
6+
7+
8+
@pytest.mark.host_test
9+
@idf_parametrize('target', ['linux'], indirect=['target'])
10+
def test_msc_host_linux(dut: Dut) -> None:
11+
dut.expect_exact('All tests passed', timeout=5)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This file was generated using idf.py save-defconfig. It can be edited manually.
2+
# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Minimal Configuration
3+
#
4+
CONFIG_IDF_TARGET="linux"
5+
CONFIG_COMPILER_CXX_EXCEPTIONS=y
6+
CONFIG_ESP_MAIN_TASK_STACK_SIZE=12000
7+
CONFIG_FREERTOS_HZ=1000
8+
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n

host/class/msc/usb_host_msc/idf_component.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ targets:
2020
- esp32s3
2121
- esp32p4
2222
- esp32h4
23+
- linux
2324
files:
2425
exclude:
2526
- "test_app"
27+
- "host_test"

0 commit comments

Comments
 (0)