mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-12 15:42:52 +00:00
feat(usb_cdc_console): moved usb-cdc ROM console to new component: esp_usb_cdc_rom_console
This commit is contained in:
@@ -8,12 +8,8 @@ set(srcs "vfs_console.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS include
|
||||
PRIV_REQUIRES vfs esp_driver_uart esp_driver_usb_serial_jtag
|
||||
LDFRAGMENTS linker.lf)
|
||||
|
||||
if(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "vfs_cdcacm.c")
|
||||
endif()
|
||||
PRIV_REQUIRES vfs esp_driver_uart esp_driver_usb_serial_jtag esp_usb_cdc_rom_console
|
||||
)
|
||||
|
||||
if(CONFIG_VFS_SUPPORT_IO)
|
||||
target_link_libraries(${COMPONENT_LIB} PUBLIC idf::vfs)
|
||||
|
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_vfs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief get pointer of cdcacm vfs.
|
||||
*
|
||||
* This function is called in vfs_console in order to get the vfs implementation
|
||||
* of cdcacm.
|
||||
*
|
||||
* @return pointer to structure esp_vfs_t
|
||||
*/
|
||||
const esp_vfs_fs_ops_t *esp_vfs_cdcacm_get_vfs(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_common.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief add /dev/cdcacm virtual filesystem driver
|
||||
*
|
||||
* This function is called from startup code to enable console output
|
||||
*/
|
||||
esp_err_t esp_vfs_dev_cdcacm_register(void);
|
||||
|
||||
/**
|
||||
* @brief Set the line endings expected to be received
|
||||
*
|
||||
* This specifies the conversion between line endings received and
|
||||
* newlines ('\n', LF) passed into stdin:
|
||||
*
|
||||
* - ESP_LINE_ENDINGS_CRLF: convert CRLF to LF
|
||||
* - ESP_LINE_ENDINGS_CR: convert CR to LF
|
||||
* - ESP_LINE_ENDINGS_LF: no modification
|
||||
*
|
||||
* @note this function is not thread safe w.r.t. reading
|
||||
*
|
||||
* @param mode line endings expected
|
||||
*/
|
||||
void esp_vfs_dev_cdcacm_set_rx_line_endings(esp_line_endings_t mode);
|
||||
|
||||
/**
|
||||
* @brief Set the line endings to sent
|
||||
*
|
||||
* This specifies the conversion between newlines ('\n', LF) on stdout and line
|
||||
* endings sent:
|
||||
*
|
||||
* - ESP_LINE_ENDINGS_CRLF: convert LF to CRLF
|
||||
* - ESP_LINE_ENDINGS_CR: convert LF to CR
|
||||
* - ESP_LINE_ENDINGS_LF: no modification
|
||||
*
|
||||
* @note this function is not thread safe w.r.t. writing
|
||||
*
|
||||
* @param mode line endings to send
|
||||
*/
|
||||
void esp_vfs_dev_cdcacm_set_tx_line_endings(esp_line_endings_t mode);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@@ -1,4 +0,0 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
components/esp_vfs_console/test_apps/usb_cdc_vfs:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32s3"] # reason: console components is only implemented on these targets. TODO P4: IDF-9120
|
@@ -1,23 +0,0 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults")
|
||||
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(usb_cdc_vfs_test)
|
||||
|
||||
idf_build_get_property(elf EXECUTABLE)
|
||||
if(CONFIG_COMPILER_DUMP_RTL_FILES)
|
||||
add_custom_target(check_test_app_sections ALL
|
||||
COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py
|
||||
--rtl-dirs ${CMAKE_BINARY_DIR}/esp-idf/driver/,${CMAKE_BINARY_DIR}/esp-idf/hal/
|
||||
--elf-file ${CMAKE_BINARY_DIR}/*.elf
|
||||
find-refs
|
||||
--from-sections=.iram0.text
|
||||
--to-sections=.flash.text,.flash.rodata
|
||||
--exit-code
|
||||
DEPENDS ${elf}
|
||||
)
|
||||
endif()
|
@@ -1,2 +0,0 @@
|
||||
| Supported Targets | ESP32-S3 |
|
||||
| ----------------- | -------- |
|
@@ -1,7 +0,0 @@
|
||||
set(src "test_app_main.c")
|
||||
|
||||
idf_component_register(SRCS ${src}
|
||||
PRIV_INCLUDE_DIRS .
|
||||
PRIV_REQUIRES esp_system esp_vfs_console unity
|
||||
WHOLE_ARCHIVE
|
||||
)
|
@@ -1,311 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/errno.h>
|
||||
#include "unity.h"
|
||||
#include "unity_test_utils_cache.h"
|
||||
#include "esp_private/usb_console.h"
|
||||
#include "esp_vfs_cdcacm.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
static void flush_write(void)
|
||||
{
|
||||
fflush(stdout);
|
||||
fsync(fileno(stdout));
|
||||
}
|
||||
|
||||
static void test_setup(const char* func_name, const size_t func_name_size)
|
||||
{
|
||||
/* write the name of the test back to the pytest environment to start the test */
|
||||
write(fileno(stdout), func_name, func_name_size);
|
||||
write(fileno(stdout), "\n", 1);
|
||||
flush_write();
|
||||
}
|
||||
|
||||
static int read_bytes_with_select(FILE *stream, void *buf, size_t buf_len, struct timeval* tv)
|
||||
{
|
||||
int fd = fileno(stream);
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
/* call select to wait for either a read ready or an except to happen */
|
||||
int nread = select(fd + 1, &read_fds, NULL, NULL, tv);
|
||||
if (nread < 0) {
|
||||
return -1;
|
||||
} else if (FD_ISSET(fd, &read_fds)) {
|
||||
int read_count = 0;
|
||||
int total_read = 0;
|
||||
|
||||
do {
|
||||
read_count = read(fd, buf + total_read, buf_len - total_read);
|
||||
if (read_count < 0 && errno != EWOULDBLOCK) {
|
||||
return -1;
|
||||
} else if (read_count > 0) {
|
||||
total_read += read_count;
|
||||
if (total_read > buf_len) {
|
||||
fflush(stream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (read_count > 0);
|
||||
|
||||
return total_read;
|
||||
} else {
|
||||
/* select timed out */
|
||||
return -2;
|
||||
}
|
||||
return nread;
|
||||
}
|
||||
|
||||
static bool wait_for_read_ready(FILE *stream)
|
||||
{
|
||||
int fd = fileno(stream);
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(fd, &read_fds);
|
||||
/* call select to wait for either a read ready or an except to happen */
|
||||
int nread = select(fd + 1, &read_fds, NULL, NULL, NULL);
|
||||
if ((nread >= 0) && (FD_ISSET(fd, &read_fds))) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void test_usb_cdc_select(void)
|
||||
{
|
||||
test_setup(__func__, sizeof(__func__));
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
char out_buffer[32];
|
||||
memset(out_buffer, 0, sizeof(out_buffer));
|
||||
size_t out_buffer_len = sizeof(out_buffer);
|
||||
|
||||
// stdin needs to be non blocking to properly call read after select returns
|
||||
// with read ready on stdin.
|
||||
int fd = fileno(stdin);
|
||||
int flags = fcntl(fd, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
int res = fcntl(fd, F_SETFL, flags);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
// init driver
|
||||
// ESP_ERROR_CHECK(esp_usb_console_init());
|
||||
// esp_vfs_dev_cdcacm_register();
|
||||
|
||||
// send the message from pytest environment and make sure it can be read
|
||||
bool message_received = false;
|
||||
size_t char_read = 0;
|
||||
while (!message_received && out_buffer_len > char_read) {
|
||||
int nread = read_bytes_with_select(stdin, out_buffer + char_read, out_buffer_len - char_read, &tv);
|
||||
if (nread > 0) {
|
||||
char_read += nread;
|
||||
if (out_buffer[char_read - 1] == '\n') {
|
||||
message_received = true;
|
||||
}
|
||||
} else if (nread == -2) {
|
||||
// time out occurred, send the expected message back to the testing
|
||||
// environment to trigger the testing environment into sending the
|
||||
// test message. don't update this message without updating the pytest
|
||||
// function since the string is expected as is by the test environment
|
||||
char timeout_msg[] = "select timed out\n";
|
||||
write(fileno(stdout), timeout_msg, sizeof(timeout_msg));
|
||||
flush_write();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// write the received message back to the test environment. The test
|
||||
// environment will check that the message received matches the one sent
|
||||
write(fileno(stdout), out_buffer, char_read);
|
||||
flush_write();
|
||||
|
||||
vTaskDelay(10); // wait for the string to send
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that the non blocking read is compliant with POSIX standards
|
||||
*/
|
||||
static void test_usb_cdc_read_non_blocking(void)
|
||||
{
|
||||
test_setup(__func__, sizeof(__func__));
|
||||
|
||||
const size_t max_read_bytes = 16;
|
||||
char read_data_buffer[max_read_bytes];
|
||||
|
||||
memset(read_data_buffer, 0x00, max_read_bytes);
|
||||
|
||||
/* reset errno to 0 for the purpose of the test to make sure it is no
|
||||
* longer set to EWOULDBLOCK later in the test. */
|
||||
errno = 0;
|
||||
|
||||
/* make sure to read in non blocking mode */
|
||||
int stdin_fd = fileno(stdin);
|
||||
int flags = fcntl(stdin_fd, F_GETFL);
|
||||
flags |= O_NONBLOCK;
|
||||
int res = fcntl(stdin_fd, F_SETFL, flags);
|
||||
TEST_ASSERT(res == 0);
|
||||
|
||||
/* call read when no data is available for reading.
|
||||
* expected result:
|
||||
* - read returns -1 and errno has been set to EWOULDBLOCK */
|
||||
int nread = read(stdin_fd, read_data_buffer, max_read_bytes);
|
||||
TEST_ASSERT(nread == -1);
|
||||
TEST_ASSERT(errno == EWOULDBLOCK);
|
||||
|
||||
/* reset errno to 0 for the purpose of the test to make sure it is no
|
||||
* longer set to EWOULDBLOCK later in the test. */
|
||||
errno = 0;
|
||||
|
||||
/* call read X number of bytes when less than X number of
|
||||
* bytes are available for reading (Y bytes).
|
||||
* expected result:
|
||||
* - read returns at least 1 bytes errno is not set to EWOULDBLOCK */
|
||||
char message_8[] = "send_bytes\n";
|
||||
write(fileno(stdout), message_8, sizeof(message_8));
|
||||
flush_write();
|
||||
|
||||
const bool is_ready = wait_for_read_ready(stdin);
|
||||
TEST_ASSERT(is_ready);
|
||||
nread = read(stdin_fd, read_data_buffer, max_read_bytes);
|
||||
TEST_ASSERT(nread >= 1);
|
||||
TEST_ASSERT(errno != EWOULDBLOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that the blocking read will return with the available
|
||||
* data even if the size is less than the requested size.
|
||||
*/
|
||||
static void test_usb_cdc_read_blocking(void)
|
||||
{
|
||||
test_setup(__func__, sizeof(__func__));
|
||||
|
||||
const size_t out_buffer_len = 32;
|
||||
char out_buffer[out_buffer_len] = {};
|
||||
|
||||
ESP_ERROR_CHECK(esp_vfs_dev_cdcacm_register());
|
||||
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_LF);
|
||||
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
|
||||
|
||||
/* make sure blocking mode is enabled */
|
||||
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags & (~O_NONBLOCK));
|
||||
|
||||
// trigger the test environment to send the test message
|
||||
char ready_msg[] = "ready to receive\n";
|
||||
write(fileno(stdout), ready_msg, sizeof(ready_msg));
|
||||
|
||||
const int nread = read(STDIN_FILENO, out_buffer, out_buffer_len);
|
||||
TEST_ASSERT(nread > 0);
|
||||
TEST_ASSERT(nread < out_buffer_len);
|
||||
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags);
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
vTaskDelay(2); // wait for tasks to exit
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that the read function does not prematurely return
|
||||
* on reception of new line character.
|
||||
*/
|
||||
static void test_usb_cdc_read_no_exit_on_newline_reception(void)
|
||||
{
|
||||
test_setup(__func__, sizeof(__func__));
|
||||
|
||||
// Send a string with length less than the read requested length
|
||||
const char in_buffer[] = "!(@*#&(!*@&#((SDasdkjhad\nce"; // read should not early return on \n
|
||||
const size_t in_buffer_len = sizeof(in_buffer);
|
||||
|
||||
const size_t out_buffer_len = in_buffer_len - 1; // don't compare the null character at the end of in_buffer string
|
||||
char out_buffer[out_buffer_len] = {};
|
||||
|
||||
ESP_ERROR_CHECK(esp_vfs_dev_cdcacm_register());
|
||||
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_LF);
|
||||
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_LF);
|
||||
|
||||
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
// trigger the test environment to send the test message
|
||||
char ready_msg[] = "ready to receive\n";
|
||||
write(fileno(stdout), ready_msg, sizeof(ready_msg));
|
||||
|
||||
// wait for the string to be sent and buffered
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
char *out_buffer_ptr = out_buffer;
|
||||
size_t bytes_read = 0;
|
||||
do {
|
||||
int nread = read(STDIN_FILENO, out_buffer_ptr, out_buffer_len);
|
||||
if (nread > 0) {
|
||||
out_buffer_ptr += nread;
|
||||
bytes_read += nread;
|
||||
}
|
||||
} while (bytes_read < in_buffer_len);
|
||||
|
||||
// string compare
|
||||
for (size_t i = 0; i < out_buffer_len; i++) {
|
||||
TEST_ASSERT_EQUAL(in_buffer[i], out_buffer[i]);
|
||||
}
|
||||
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags);
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
vTaskDelay(2); // wait for tasks to exit
|
||||
}
|
||||
|
||||
static void IRAM_ATTR test_input_output_post_cache_disable(void *args)
|
||||
{
|
||||
static DRAM_ATTR const char test_msg[] = "test_message\n";
|
||||
esp_rom_printf(test_msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Test that the tx and rx cb are placed in IRAM correctly
|
||||
*/
|
||||
static void test_usb_cdc_ets_printf_cache_disabled(void)
|
||||
{
|
||||
test_setup(__func__, sizeof(__func__));
|
||||
|
||||
/* make sure blocking mode is enabled */
|
||||
int flags = fcntl(STDIN_FILENO, F_GETFL, 0);
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags & (~O_NONBLOCK));
|
||||
|
||||
#if CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF
|
||||
unity_utils_run_cache_disable_stub(test_input_output_post_cache_disable, NULL);
|
||||
#else
|
||||
#error This test must run with CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF enabled
|
||||
#endif
|
||||
|
||||
// send message to the test environment to report successful test
|
||||
char ready_msg[] = "successful test\n";
|
||||
write(fileno(stdout), ready_msg, sizeof(ready_msg));
|
||||
|
||||
fcntl(STDIN_FILENO, F_SETFL, flags);
|
||||
esp_vfs_dev_cdcacm_set_rx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
esp_vfs_dev_cdcacm_set_tx_line_endings(ESP_LINE_ENDINGS_CRLF);
|
||||
vTaskDelay(2); // wait for tasks to exit
|
||||
}
|
||||
|
||||
/* Always make sure that the function calling sequence in the main
|
||||
* function matches the expected order in the pytest function.
|
||||
*/
|
||||
void app_main(void)
|
||||
{
|
||||
test_usb_cdc_select();
|
||||
test_usb_cdc_read_non_blocking();
|
||||
test_usb_cdc_read_blocking();
|
||||
test_usb_cdc_read_no_exit_on_newline_reception();
|
||||
test_usb_cdc_ets_printf_cache_disabled();
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.usb_device
|
||||
@pytest.mark.parametrize(
|
||||
'port, flash_port, config',
|
||||
[
|
||||
pytest.param('/dev/serial_ports/ttyACM-esp32', '/dev/serial_ports/ttyUSB-esp32', 'release'),
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
@idf_parametrize('target', ['esp32s3'], indirect=['target'])
|
||||
def test_usb_cdc_vfs_default(dut: Dut) -> None:
|
||||
test_message = 'test123456789!@#%^&*'
|
||||
|
||||
# test run: test_usb_cdc_select
|
||||
dut.expect_exact('test_usb_cdc_select', timeout=2)
|
||||
dut.expect_exact('select timed out', timeout=2)
|
||||
dut.write(test_message)
|
||||
dut.expect_exact(test_message, timeout=2)
|
||||
|
||||
# test run: test_usb_cdc_read_non_blocking
|
||||
dut.expect_exact('test_usb_cdc_read_non_blocking', timeout=2)
|
||||
dut.expect_exact('send_bytes', timeout=2)
|
||||
dut.write('abcdefgh')
|
||||
|
||||
# test run: test_usb_cdc_read_blocking
|
||||
dut.expect_exact('test_usb_cdc_read_blocking', timeout=2)
|
||||
dut.expect_exact('ready to receive', timeout=2)
|
||||
dut.write('testdata')
|
||||
|
||||
# test run: test_usb_cdc_read_no_exit_on_newline_reception
|
||||
dut.expect_exact('test_usb_cdc_read_no_exit_on_newline_reception', timeout=2)
|
||||
dut.expect_exact('ready to receive', timeout=2)
|
||||
dut.write('!(@*#&(!*@&#((SDasdkjhad\nce')
|
||||
|
||||
# test run: test_usb_cdc_ets_printf_cache_disabled
|
||||
dut.expect_exact('test_usb_cdc_ets_printf_cache_disabled', timeout=2)
|
||||
dut.expect_exact('test_message', timeout=2)
|
||||
dut.expect_exact('successful test', timeout=2)
|
@@ -1,6 +0,0 @@
|
||||
CONFIG_PM_ENABLE=y
|
||||
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_NONE=y
|
@@ -1,12 +0,0 @@
|
||||
# Enable Unity fixture support
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=n
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
|
||||
# Custom partition table for this test app
|
||||
CONFIG_ESP_TASK_WDT_INIT=n
|
||||
|
||||
CONFIG_ESP_CONSOLE_USB_CDC=y
|
||||
|
||||
# one test requires this option to be enabled
|
||||
CONFIG_ESP_CONSOLE_USB_CDC_SUPPORT_ETS_PRINTF=y
|
@@ -1,524 +0,0 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdarg.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/lock.h>
|
||||
#include <sys/fcntl.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_vfs_cdcacm.h"
|
||||
#include "esp_attr.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_heap_caps.h"
|
||||
|
||||
#include "esp_private/esp_vfs_cdcacm_select.h"
|
||||
#include "esp_private/usb_console.h"
|
||||
|
||||
#define USB_CDC_LOCAL_FD 0
|
||||
|
||||
// Newline conversion mode when transmitting
|
||||
static esp_line_endings_t s_tx_mode =
|
||||
#if CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF
|
||||
ESP_LINE_ENDINGS_CRLF;
|
||||
#elif CONFIG_LIBC_STDOUT_LINE_ENDING_CR
|
||||
ESP_LINE_ENDINGS_CR;
|
||||
#else
|
||||
ESP_LINE_ENDINGS_LF;
|
||||
#endif
|
||||
|
||||
// Newline conversion mode when receiving
|
||||
static esp_line_endings_t s_rx_mode =
|
||||
#if CONFIG_LIBC_STDIN_LINE_ENDING_CRLF
|
||||
ESP_LINE_ENDINGS_CRLF;
|
||||
#elif CONFIG_LIBC_STDIN_LINE_ENDING_CR
|
||||
ESP_LINE_ENDINGS_CR;
|
||||
#else
|
||||
ESP_LINE_ENDINGS_LF;
|
||||
#endif
|
||||
|
||||
#if CONFIG_VFS_SELECT_IN_RAM
|
||||
#define CDCACM_VFS_MALLOC_FLAGS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
|
||||
#else
|
||||
#define CDCACM_VFS_MALLOC_FLAGS MALLOC_CAP_DEFAULT
|
||||
#endif
|
||||
|
||||
#define NONE -1
|
||||
|
||||
//Read and write lock, lazily initialized
|
||||
static _lock_t s_write_lock;
|
||||
static _lock_t s_read_lock;
|
||||
|
||||
static bool s_blocking;
|
||||
static SemaphoreHandle_t s_rx_semaphore;
|
||||
static SemaphoreHandle_t s_tx_semaphore;
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
typedef struct {
|
||||
esp_vfs_select_sem_t select_sem;
|
||||
fd_set *readfds;
|
||||
fd_set *writefds;
|
||||
fd_set *errorfds;
|
||||
fd_set readfds_orig;
|
||||
fd_set writefds_orig;
|
||||
fd_set errorfds_orig;
|
||||
} cdcacm_select_args_t;
|
||||
|
||||
static cdcacm_select_args_t **s_registered_selects = NULL;
|
||||
static int s_registered_select_num = 0;
|
||||
static portMUX_TYPE s_registered_select_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
|
||||
static esp_err_t cdcacm_end_select(void *end_select_args);
|
||||
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
static ssize_t cdcacm_write(int fd, const void *data, size_t size)
|
||||
{
|
||||
assert(fd == 0);
|
||||
const char *cdata = (const char *)data;
|
||||
_lock_acquire_recursive(&s_write_lock);
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
if (cdata[i] != '\n') {
|
||||
esp_usb_console_write_buf(&cdata[i], 1);
|
||||
} else {
|
||||
if (s_tx_mode == ESP_LINE_ENDINGS_CRLF || s_tx_mode == ESP_LINE_ENDINGS_CR) {
|
||||
char cr = '\r';
|
||||
esp_usb_console_write_buf(&cr, 1);
|
||||
}
|
||||
if (s_tx_mode == ESP_LINE_ENDINGS_CRLF || s_tx_mode == ESP_LINE_ENDINGS_LF) {
|
||||
char lf = '\n';
|
||||
esp_usb_console_write_buf(&lf, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_lock_release_recursive(&s_write_lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
static int cdcacm_fsync(int fd)
|
||||
{
|
||||
assert(fd == 0);
|
||||
_lock_acquire_recursive(&s_write_lock);
|
||||
ssize_t written = esp_usb_console_flush();
|
||||
_lock_release_recursive(&s_write_lock);
|
||||
return (written < 0) ? -1 : 0;
|
||||
}
|
||||
|
||||
static int cdcacm_open(const char *path, int flags, int mode)
|
||||
{
|
||||
return USB_CDC_LOCAL_FD; // fd 0
|
||||
}
|
||||
|
||||
static int cdcacm_fstat(int fd, struct stat *st)
|
||||
{
|
||||
assert(fd == 0);
|
||||
memset(st, 0, sizeof(*st));
|
||||
st->st_mode = S_IFCHR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdcacm_close(int fd)
|
||||
{
|
||||
assert(fd == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s_peek_char = NONE;
|
||||
|
||||
/* Helper function which returns a previous character or reads a new one from
|
||||
* CDC-ACM driver. Previous character can be returned ("pushed back") using
|
||||
* cdcacm_return_char function. Returns NONE if no character is available. Note
|
||||
* the cdcacm driver maintains its own RX buffer and a receive call does not
|
||||
* invoke an USB operation, so there's no penalty to reading data char-by-char.
|
||||
*/
|
||||
static int cdcacm_read_char(void)
|
||||
{
|
||||
/* return character from peek buffer, if it is there */
|
||||
if (s_peek_char != NONE) {
|
||||
int c = s_peek_char;
|
||||
s_peek_char = NONE;
|
||||
return c;
|
||||
}
|
||||
/* Peek buffer is empty; try to read from cdcacm driver. */
|
||||
uint8_t c;
|
||||
ssize_t read = esp_usb_console_read_buf((char *) &c, 1);
|
||||
if (read <= 0) {
|
||||
return NONE;
|
||||
} else {
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t cdcacm_data_length_in_buffer(void)
|
||||
{
|
||||
ssize_t len = esp_usb_console_available_for_read();
|
||||
if (len < 0) {
|
||||
len = 0;
|
||||
}
|
||||
if (s_peek_char != NONE) {
|
||||
len += 1;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Push back a character; it will be returned by next call to cdcacm_read_char */
|
||||
static void cdcacm_return_char(int c)
|
||||
{
|
||||
assert(s_peek_char == NONE);
|
||||
s_peek_char = c;
|
||||
}
|
||||
|
||||
static ssize_t cdcacm_read(int fd, void *data, size_t size)
|
||||
{
|
||||
assert(fd == USB_CDC_LOCAL_FD);
|
||||
char *data_c = (char *) data;
|
||||
ssize_t received = 0;
|
||||
_lock_acquire_recursive(&s_read_lock);
|
||||
|
||||
if (s_blocking) {
|
||||
/* in blocking mode, wait for data. by the POSIX standards,
|
||||
* the amount of available bytes does not need to match the
|
||||
* requested size to return */
|
||||
while (cdcacm_data_length_in_buffer() <= 0) {
|
||||
xSemaphoreTake(s_rx_semaphore, portMAX_DELAY);
|
||||
}
|
||||
} else {
|
||||
/* process pending interrupts before requesting available data */
|
||||
esp_usb_console_poll_interrupts();
|
||||
size = MIN(size, cdcacm_data_length_in_buffer());
|
||||
}
|
||||
|
||||
while (received < size) {
|
||||
int c = cdcacm_read_char();
|
||||
if (c == '\r') {
|
||||
if (s_rx_mode == ESP_LINE_ENDINGS_CR) {
|
||||
c = '\n';
|
||||
} else if (s_rx_mode == ESP_LINE_ENDINGS_CRLF) {
|
||||
/* look ahead */
|
||||
int c2 = cdcacm_read_char();
|
||||
if (c2 == NONE) {
|
||||
/* could not look ahead, put the current character back */
|
||||
cdcacm_return_char(c);
|
||||
break;
|
||||
}
|
||||
if (c2 == '\n') {
|
||||
/* this was \r\n sequence. discard \r, return \n */
|
||||
c = '\n';
|
||||
} else {
|
||||
/* \r followed by something else. put the second char back,
|
||||
* it will be processed on next iteration. return \r now.
|
||||
*/
|
||||
cdcacm_return_char(c2);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (c == NONE) {
|
||||
break;
|
||||
}
|
||||
data_c[received++] = (char) c;
|
||||
}
|
||||
_lock_release_recursive(&s_read_lock);
|
||||
if (received > 0) {
|
||||
return received;
|
||||
}
|
||||
errno = EWOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Non-static, to be able to place into IRAM by ldgen */
|
||||
void cdcacm_rx_cb(void* arg)
|
||||
{
|
||||
assert(s_blocking);
|
||||
xSemaphoreGive(s_rx_semaphore);
|
||||
}
|
||||
|
||||
/* Non-static, to be able to place into IRAM by ldgen */
|
||||
void cdcacm_tx_cb(void* arg)
|
||||
{
|
||||
assert(s_blocking);
|
||||
xSemaphoreGive(s_tx_semaphore);
|
||||
}
|
||||
|
||||
static int cdcacm_enable_blocking(void)
|
||||
{
|
||||
s_rx_semaphore = xSemaphoreCreateBinary();
|
||||
if (!s_rx_semaphore) {
|
||||
errno = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
s_tx_semaphore = xSemaphoreCreateBinary();
|
||||
if (!s_tx_semaphore) {
|
||||
errno = ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
esp_err_t err = esp_usb_console_set_cb(&cdcacm_rx_cb, &cdcacm_tx_cb, NULL);
|
||||
if (err != ESP_OK) {
|
||||
errno = ENODEV;
|
||||
goto fail;
|
||||
}
|
||||
s_blocking = true;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (s_rx_semaphore) {
|
||||
vSemaphoreDelete(s_rx_semaphore);
|
||||
s_rx_semaphore = NULL;
|
||||
}
|
||||
if (s_tx_semaphore) {
|
||||
vSemaphoreDelete(s_tx_semaphore);
|
||||
s_tx_semaphore = NULL;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int cdcacm_disable_blocking(void)
|
||||
{
|
||||
esp_usb_console_set_cb(NULL, NULL, NULL); /* ignore any errors */
|
||||
vSemaphoreDelete(s_rx_semaphore);
|
||||
s_rx_semaphore = NULL;
|
||||
vSemaphoreDelete(s_tx_semaphore);
|
||||
s_tx_semaphore = NULL;
|
||||
s_blocking = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cdcacm_fcntl(int fd, int cmd, int arg)
|
||||
{
|
||||
assert(fd == 0);
|
||||
int result;
|
||||
if (cmd == F_GETFL) {
|
||||
result = O_RDWR;
|
||||
if (!s_blocking) {
|
||||
result |= O_NONBLOCK;
|
||||
}
|
||||
} else if (cmd == F_SETFL) {
|
||||
bool blocking = (arg & O_NONBLOCK) == 0;
|
||||
result = 0;
|
||||
if (blocking && !s_blocking) {
|
||||
result = cdcacm_enable_blocking();
|
||||
} else if (!blocking && s_blocking) {
|
||||
result = cdcacm_disable_blocking();
|
||||
}
|
||||
} else {
|
||||
/* unsupported operation */
|
||||
result = -1;
|
||||
errno = ENOSYS;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
static void select_notif_callback_isr(cdcacm_select_notif_t cdcacm_select_notif, BaseType_t *task_woken)
|
||||
{
|
||||
portENTER_CRITICAL_ISR(&s_registered_select_lock);
|
||||
for (int i = 0; i < s_registered_select_num; ++i) {
|
||||
cdcacm_select_args_t *args = s_registered_selects[i];
|
||||
if (args) {
|
||||
switch (cdcacm_select_notif) {
|
||||
case CDCACM_SELECT_READ_NOTIF:
|
||||
if (FD_ISSET(USB_CDC_LOCAL_FD, &args->readfds_orig)) {
|
||||
FD_SET(USB_CDC_LOCAL_FD, args->readfds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
case CDCACM_SELECT_WRITE_NOTIF:
|
||||
if (FD_ISSET(USB_CDC_LOCAL_FD, &args->writefds_orig)) {
|
||||
FD_SET(USB_CDC_LOCAL_FD, args->writefds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
case CDCACM_SELECT_ERROR_NOTIF:
|
||||
if (FD_ISSET(USB_CDC_LOCAL_FD, &args->errorfds_orig)) {
|
||||
FD_SET(USB_CDC_LOCAL_FD, args->errorfds);
|
||||
esp_vfs_select_triggered_isr(args->select_sem, task_woken);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL_ISR(&s_registered_select_lock);
|
||||
}
|
||||
|
||||
static esp_err_t register_select(cdcacm_select_args_t *args)
|
||||
{
|
||||
esp_err_t ret = ESP_ERR_INVALID_ARG;
|
||||
|
||||
if (args) {
|
||||
portENTER_CRITICAL(&s_registered_select_lock);
|
||||
const int new_size = s_registered_select_num + 1;
|
||||
cdcacm_select_args_t **new_selects;
|
||||
if ((new_selects = heap_caps_realloc(s_registered_selects, new_size * sizeof(cdcacm_select_args_t *), CDCACM_VFS_MALLOC_FLAGS)) == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
} else {
|
||||
/* on first select registration register the callback */
|
||||
if (s_registered_select_num == 0) {
|
||||
cdcacm_set_select_notif_callback(select_notif_callback_isr);
|
||||
}
|
||||
|
||||
s_registered_selects = new_selects;
|
||||
s_registered_selects[s_registered_select_num] = args;
|
||||
s_registered_select_num = new_size;
|
||||
ret = ESP_OK;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_registered_select_lock);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t unregister_select(cdcacm_select_args_t *args)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
if (args) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
portENTER_CRITICAL(&s_registered_select_lock);
|
||||
for (int i = 0; i < s_registered_select_num; ++i) {
|
||||
if (s_registered_selects[i] == args) {
|
||||
const int new_size = s_registered_select_num - 1;
|
||||
// The item is removed by overwriting it with the last item. The subsequent rellocation will drop the
|
||||
// last item.
|
||||
s_registered_selects[i] = s_registered_selects[new_size];
|
||||
cdcacm_select_args_t **new_selects = heap_caps_realloc(s_registered_selects, new_size * sizeof(cdcacm_select_args_t *), CDCACM_VFS_MALLOC_FLAGS);
|
||||
if (new_selects == NULL && new_size > 0) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
break;
|
||||
} else {
|
||||
s_registered_selects = new_selects;
|
||||
}
|
||||
// Shrinking a buffer with realloc is guaranteed to succeed.
|
||||
s_registered_select_num = new_size;
|
||||
|
||||
/* when the last select is unregistered, also unregister the callback */
|
||||
if (s_registered_select_num == 0) {
|
||||
cdcacm_set_select_notif_callback(NULL);
|
||||
}
|
||||
|
||||
ret = ESP_OK;
|
||||
break;
|
||||
}
|
||||
}
|
||||
portEXIT_CRITICAL(&s_registered_select_lock);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t cdcacm_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
|
||||
esp_vfs_select_sem_t select_sem, void **end_select_args)
|
||||
{
|
||||
(void)nfds; /* Since there is only 1 USB OTG, this parameter is useless */
|
||||
*end_select_args = NULL;
|
||||
|
||||
if (!esp_usb_console_is_installed()) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
cdcacm_select_args_t *args = heap_caps_malloc(sizeof(cdcacm_select_args_t), CDCACM_VFS_MALLOC_FLAGS);
|
||||
|
||||
if (args == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
args->select_sem = select_sem;
|
||||
args->readfds = readfds;
|
||||
args->writefds = writefds;
|
||||
args->errorfds = exceptfds;
|
||||
args->readfds_orig = *readfds; // store the original values because they will be set to zero
|
||||
args->writefds_orig = *writefds;
|
||||
args->errorfds_orig = *exceptfds;
|
||||
FD_ZERO(readfds);
|
||||
FD_ZERO(writefds);
|
||||
FD_ZERO(exceptfds);
|
||||
|
||||
esp_err_t ret = register_select(args);
|
||||
if (ret != ESP_OK) {
|
||||
free(args);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* make sure the driver processes any pending interrupts before checking read or write
|
||||
* readiness */
|
||||
esp_usb_console_poll_interrupts();
|
||||
bool trigger_select = false;
|
||||
if (FD_ISSET(USB_CDC_LOCAL_FD, &args->readfds_orig) &&
|
||||
esp_usb_console_available_for_read() > 0) {
|
||||
|
||||
// signalize immediately when read is ready
|
||||
FD_SET(USB_CDC_LOCAL_FD, readfds);
|
||||
trigger_select = true;
|
||||
}
|
||||
|
||||
if (FD_ISSET(USB_CDC_LOCAL_FD, &args->writefds_orig) &&
|
||||
esp_usb_console_write_available()) {
|
||||
|
||||
// signalize immediately when write is ready
|
||||
FD_SET(USB_CDC_LOCAL_FD, writefds);
|
||||
trigger_select = true;
|
||||
}
|
||||
|
||||
if (trigger_select) {
|
||||
esp_vfs_select_triggered(args->select_sem);
|
||||
}
|
||||
|
||||
*end_select_args = args;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t cdcacm_end_select(void *end_select_args)
|
||||
{
|
||||
cdcacm_select_args_t *args = end_select_args;
|
||||
esp_err_t ret = unregister_select(args);
|
||||
if (args) {
|
||||
free(args);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
void esp_vfs_dev_cdcacm_set_tx_line_endings(esp_line_endings_t mode)
|
||||
{
|
||||
s_tx_mode = mode;
|
||||
}
|
||||
|
||||
void esp_vfs_dev_cdcacm_set_rx_line_endings(esp_line_endings_t mode)
|
||||
{
|
||||
s_rx_mode = mode;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
static const esp_vfs_select_ops_t s_cdcacm_vfs_select = {
|
||||
.start_select = &cdcacm_start_select,
|
||||
.end_select = &cdcacm_end_select,
|
||||
};
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
|
||||
static const esp_vfs_fs_ops_t s_cdcacm_vfs = {
|
||||
.write = &cdcacm_write,
|
||||
.open = &cdcacm_open,
|
||||
.fstat = &cdcacm_fstat,
|
||||
.close = &cdcacm_close,
|
||||
.read = &cdcacm_read,
|
||||
.fcntl = &cdcacm_fcntl,
|
||||
.fsync = &cdcacm_fsync,
|
||||
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
||||
.select = &s_cdcacm_vfs_select,
|
||||
#endif // CONFIG_VFS_SUPPORT_SELECT
|
||||
};
|
||||
|
||||
const esp_vfs_fs_ops_t *esp_vfs_cdcacm_get_vfs(void)
|
||||
{
|
||||
return &s_cdcacm_vfs;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_dev_cdcacm_register(void)
|
||||
{
|
||||
return esp_vfs_register_fs("/dev/cdcacm", &s_cdcacm_vfs, ESP_VFS_FLAG_STATIC, NULL);
|
||||
}
|
@@ -247,14 +247,6 @@ esp_err_t esp_vfs_console_register(void)
|
||||
primary_vfs = esp_vfs_usb_serial_jtag_get_vfs();
|
||||
#elif CONFIG_ESP_CONSOLE_USB_CDC
|
||||
primary_vfs = esp_vfs_cdcacm_get_vfs();
|
||||
err = esp_usb_console_init();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
err = esp_vfs_dev_cdcacm_register();
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
#else
|
||||
primary_vfs = esp_vfs_null_get_vfs();
|
||||
#endif
|
||||
@@ -267,7 +259,7 @@ esp_err_t esp_vfs_console_register(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
ESP_SYSTEM_INIT_FN(init_vfs_console, CORE, BIT(0), 114)
|
||||
ESP_SYSTEM_INIT_FN(init_vfs_console, CORE, BIT(0), 119)
|
||||
{
|
||||
return esp_vfs_console_register();
|
||||
}
|
||||
|
Reference in New Issue
Block a user