mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-11-04 00:51:42 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			325 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// 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);
 | 
						|
}
 |