feat(usb_cdc_console): moved usb-cdc ROM console to new component: esp_usb_cdc_rom_console

This commit is contained in:
Marius Vikhammer
2025-07-31 18:10:47 +08:00
parent 206be17ec9
commit 56e0c11bb6
34 changed files with 92 additions and 62 deletions

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -1,2 +0,0 @@
| Supported Targets | ESP32-S3 |
| ----------------- | -------- |

View File

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

View File

@@ -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();
}

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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();
}