mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-27 11:58:55 +00:00 
			
		
		
		
	 b1c4107275
			
		
	
	b1c4107275
	
	
	
		
			
			...otherwise some fields will contain garbage values. This wasn't noticed until HAVE_BLKSIZE got enabled in newlib builds.
		
			
				
	
	
		
			326 lines
		
	
	
		
			8.5 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			326 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);
 | |
|     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 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);
 | |
| }
 |