vfs: add vfs_cdcacm driver (aka USB CDC)
This commit is contained in:
parent
34f441249b
commit
fc8cd4048d
4 changed files with 397 additions and 5 deletions
|
@ -3,6 +3,10 @@ idf_component_register(SRCS "vfs.c"
|
|||
"vfs_semihost.c"
|
||||
INCLUDE_DIRS include)
|
||||
|
||||
if(CONFIG_ESP_CONSOLE_USB_CDC)
|
||||
target_sources(${COMPONENT_LIB} PRIVATE "vfs_cdcacm.c")
|
||||
endif()
|
||||
|
||||
# Some newlib syscalls are implemented in vfs.c, make sure these are always
|
||||
# seen by the linker
|
||||
target_link_libraries(${COMPONENT_LIB} INTERFACE "-u vfs_include_syscalls_impl")
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
ifndef CONFIG_ESP_CONSOLE_USB_CDC
|
||||
COMPONENT_OBJEXCLUDE := vfs_cdcacm.o
|
||||
endif
|
||||
|
|
66
components/vfs/include/esp_vfs_cdcacm.h
Normal file
66
components/vfs/include/esp_vfs_cdcacm.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2015-2017 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#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
|
324
components/vfs/vfs_cdcacm.c
Normal file
324
components/vfs/vfs_cdcacm.c
Normal file
|
@ -0,0 +1,324 @@
|
|||
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#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_private/usb_console.h"
|
||||
|
||||
// Newline conversion mode when transmitting
|
||||
static esp_line_endings_t s_tx_mode =
|
||||
#if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
|
||||
ESP_LINE_ENDINGS_CRLF;
|
||||
#elif CONFIG_NEWLIB_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_NEWLIB_STDIN_LINE_ENDING_CRLF
|
||||
ESP_LINE_ENDINGS_CRLF;
|
||||
#elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
|
||||
ESP_LINE_ENDINGS_CR;
|
||||
#else
|
||||
ESP_LINE_ENDINGS_LF;
|
||||
#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;
|
||||
|
||||
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 0; // fd 0
|
||||
}
|
||||
|
||||
static int cdcacm_fstat(int fd, struct stat *st)
|
||||
{
|
||||
assert(fd == 0);
|
||||
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 bool cdcacm_data_in_buffer(void)
|
||||
{
|
||||
if (s_peek_char != NONE) {
|
||||
return true;
|
||||
}
|
||||
if (esp_usb_console_read_available()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* 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);
|
||||
|
||||
while (!cdcacm_data_in_buffer()) {
|
||||
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) {
|
||||
result = 0;
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
esp_err_t esp_vfs_dev_cdcacm_register(void)
|
||||
{
|
||||
const esp_vfs_t vfs = {
|
||||
.flags = ESP_VFS_FLAG_DEFAULT,
|
||||
.write = &cdcacm_write,
|
||||
.open = &cdcacm_open,
|
||||
.fstat = &cdcacm_fstat,
|
||||
.close = &cdcacm_close,
|
||||
.read = &cdcacm_read,
|
||||
.fcntl = &cdcacm_fcntl,
|
||||
.fsync = &cdcacm_fsync
|
||||
};
|
||||
return esp_vfs_register("/dev/cdcacm", &vfs, NULL);
|
||||
}
|
Loading…
Reference in a new issue