2021-10-21 12:46:24 +08:00
|
|
|
/*
|
2024-10-31 14:13:04 +01:00
|
|
|
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
2021-10-21 12:46:24 +08:00
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
2020-04-30 16:36:20 +02:00
|
|
|
|
|
|
|
#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"
|
2024-05-28 08:56:36 +02:00
|
|
|
#include "esp_heap_caps.h"
|
2020-04-30 16:36:20 +02:00
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#include "esp_private/esp_vfs_cdcacm_select.h"
|
2020-04-30 16:36:20 +02:00
|
|
|
#include "esp_private/usb_console.h"
|
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#define USB_CDC_LOCAL_FD 0
|
|
|
|
|
2020-04-30 16:36:20 +02:00
|
|
|
// Newline conversion mode when transmitting
|
|
|
|
static esp_line_endings_t s_tx_mode =
|
2024-09-26 13:13:20 +07:00
|
|
|
#if CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF
|
2020-04-30 16:36:20 +02:00
|
|
|
ESP_LINE_ENDINGS_CRLF;
|
2024-09-26 13:13:20 +07:00
|
|
|
#elif CONFIG_LIBC_STDOUT_LINE_ENDING_CR
|
2020-04-30 16:36:20 +02:00
|
|
|
ESP_LINE_ENDINGS_CR;
|
|
|
|
#else
|
|
|
|
ESP_LINE_ENDINGS_LF;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Newline conversion mode when receiving
|
|
|
|
static esp_line_endings_t s_rx_mode =
|
2024-09-26 13:13:20 +07:00
|
|
|
#if CONFIG_LIBC_STDIN_LINE_ENDING_CRLF
|
2020-04-30 16:36:20 +02:00
|
|
|
ESP_LINE_ENDINGS_CRLF;
|
2024-09-26 13:13:20 +07:00
|
|
|
#elif CONFIG_LIBC_STDIN_LINE_ENDING_CR
|
2020-04-30 16:36:20 +02:00
|
|
|
ESP_LINE_ENDINGS_CR;
|
|
|
|
#else
|
|
|
|
ESP_LINE_ENDINGS_LF;
|
|
|
|
#endif
|
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#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
|
|
|
|
|
2020-04-30 16:36:20 +02:00
|
|
|
#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;
|
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#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
|
|
|
|
|
2020-04-30 16:36:20 +02:00
|
|
|
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)
|
|
|
|
{
|
2024-05-28 08:56:36 +02:00
|
|
|
return USB_CDC_LOCAL_FD; // fd 0
|
2020-04-30 16:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static int cdcacm_fstat(int fd, struct stat *st)
|
|
|
|
{
|
|
|
|
assert(fd == 0);
|
2020-11-05 17:38:22 +01:00
|
|
|
memset(st, 0, sizeof(*st));
|
2020-04-30 16:36:20 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-17 18:52:49 +02:00
|
|
|
static ssize_t cdcacm_data_length_in_buffer(void)
|
2020-04-30 16:36:20 +02:00
|
|
|
{
|
2022-05-17 18:52:49 +02:00
|
|
|
ssize_t len = esp_usb_console_available_for_read();
|
|
|
|
if (len < 0) {
|
|
|
|
len = 0;
|
2020-04-30 16:36:20 +02:00
|
|
|
}
|
2022-05-17 18:52:49 +02:00
|
|
|
if (s_peek_char != NONE) {
|
|
|
|
len += 1;
|
2020-04-30 16:36:20 +02:00
|
|
|
}
|
2022-05-17 18:52:49 +02:00
|
|
|
return len;
|
2020-04-30 16:36:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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 == 0);
|
|
|
|
char *data_c = (char *) data;
|
|
|
|
ssize_t received = 0;
|
|
|
|
_lock_acquire_recursive(&s_read_lock);
|
|
|
|
|
2022-05-17 18:52:49 +02:00
|
|
|
while (cdcacm_data_length_in_buffer() < size) {
|
2020-04-30 16:36:20 +02:00
|
|
|
if (!s_blocking) {
|
|
|
|
errno = EWOULDBLOCK;
|
|
|
|
_lock_release_recursive(&s_read_lock);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
xSemaphoreTake(s_rx_semaphore, portMAX_DELAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s_rx_mode == ESP_LINE_ENDINGS_CR || s_rx_mode == ESP_LINE_ENDINGS_LF) {
|
|
|
|
/* This is easy. Just receive, and if needed replace \r by \n. */
|
|
|
|
received = esp_usb_console_read_buf(data_c, size);
|
|
|
|
if (s_rx_mode == ESP_LINE_ENDINGS_CR) {
|
|
|
|
/* Change CRs to newlines */
|
|
|
|
for (ssize_t i = 0; i < received; i++) {
|
|
|
|
if (data_c[i] == '\r') {
|
|
|
|
data_c[i] = '\n';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
while (received < size) {
|
|
|
|
int c = cdcacm_read_char();
|
|
|
|
if (c == '\r') {
|
|
|
|
/* 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;
|
|
|
|
if (c == '\n') {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_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) {
|
2022-07-21 16:16:44 +08:00
|
|
|
result = O_RDWR;
|
2020-04-30 16:36:20 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#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];
|
|
|
|
s_registered_selects = heap_caps_realloc(s_registered_selects, new_size * sizeof(cdcacm_select_args_t *), CDCACM_VFS_MALLOC_FLAGS);
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2020-04-30 16:36:20 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-05-28 08:56:36 +02:00
|
|
|
#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
|
|
|
|
|
2024-10-31 14:13:04 +01:00
|
|
|
static const esp_vfs_fs_ops_t s_cdcacm_vfs = {
|
2021-10-21 12:46:24 +08:00
|
|
|
.write = &cdcacm_write,
|
|
|
|
.open = &cdcacm_open,
|
|
|
|
.fstat = &cdcacm_fstat,
|
|
|
|
.close = &cdcacm_close,
|
|
|
|
.read = &cdcacm_read,
|
|
|
|
.fcntl = &cdcacm_fcntl,
|
2024-05-28 08:56:36 +02:00
|
|
|
.fsync = &cdcacm_fsync,
|
|
|
|
#ifdef CONFIG_VFS_SUPPORT_SELECT
|
|
|
|
.select = &s_cdcacm_vfs_select,
|
|
|
|
#endif // CONFIG_VFS_SUPPORT_SELECT
|
2021-10-21 12:46:24 +08:00
|
|
|
};
|
|
|
|
|
2024-10-31 14:13:04 +01:00
|
|
|
const esp_vfs_fs_ops_t *esp_vfs_cdcacm_get_vfs(void)
|
2021-10-21 12:46:24 +08:00
|
|
|
{
|
2024-10-31 14:13:04 +01:00
|
|
|
return &s_cdcacm_vfs;
|
2021-10-21 12:46:24 +08:00
|
|
|
}
|
|
|
|
|
2020-04-30 16:36:20 +02:00
|
|
|
esp_err_t esp_vfs_dev_cdcacm_register(void)
|
|
|
|
{
|
2024-10-31 14:13:04 +01:00
|
|
|
return esp_vfs_register_fs("/dev/cdcacm", &s_cdcacm_vfs, ESP_VFS_FLAG_STATIC, NULL);
|
2020-04-30 16:36:20 +02:00
|
|
|
}
|