Răsfoiți Sursa

vfs: add vfs_cdcacm driver (aka USB CDC)

Ivan Grokhotkov 5 ani în urmă
părinte
comite
fc8cd4048d

+ 4 - 0
components/vfs/CMakeLists.txt

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

+ 3 - 5
components/vfs/component.mk

@@ -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 - 0
components/vfs/include/esp_vfs_cdcacm.h

@@ -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 - 0
components/vfs/vfs_cdcacm.c

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