1 /*
2  * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <string.h>
8 #include <stdbool.h>
9 #include <stdarg.h>
10 #include <sys/errno.h>
11 #include <sys/lock.h>
12 #include <sys/fcntl.h>
13 #include <sys/param.h>
14 #include "esp_vfs.h"
15 #include "esp_vfs_cdcacm.h"
16 #include "esp_attr.h"
17 #include "sdkconfig.h"
18 
19 #include "esp_private/usb_console.h"
20 
21 // Newline conversion mode when transmitting
22 static esp_line_endings_t s_tx_mode =
23 #if CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
24     ESP_LINE_ENDINGS_CRLF;
25 #elif CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR
26     ESP_LINE_ENDINGS_CR;
27 #else
28     ESP_LINE_ENDINGS_LF;
29 #endif
30 
31 // Newline conversion mode when receiving
32 static esp_line_endings_t s_rx_mode =
33 #if CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF
34     ESP_LINE_ENDINGS_CRLF;
35 #elif CONFIG_NEWLIB_STDIN_LINE_ENDING_CR
36     ESP_LINE_ENDINGS_CR;
37 #else
38     ESP_LINE_ENDINGS_LF;
39 #endif
40 
41 #define NONE -1
42 
43 //Read and write lock, lazily initialized
44 static _lock_t s_write_lock;
45 static _lock_t s_read_lock;
46 
47 static bool s_blocking;
48 static SemaphoreHandle_t s_rx_semaphore;
49 static SemaphoreHandle_t s_tx_semaphore;
50 
cdcacm_write(int fd,const void * data,size_t size)51 static ssize_t cdcacm_write(int fd, const void *data, size_t size)
52 {
53     assert(fd == 0);
54     const char *cdata = (const char *)data;
55     _lock_acquire_recursive(&s_write_lock);
56     for (size_t i = 0; i < size; i++) {
57         if (cdata[i] != '\n') {
58             esp_usb_console_write_buf(&cdata[i], 1);
59         } else {
60             if (s_tx_mode == ESP_LINE_ENDINGS_CRLF || s_tx_mode == ESP_LINE_ENDINGS_CR) {
61                 char cr = '\r';
62                 esp_usb_console_write_buf(&cr, 1);
63             }
64             if (s_tx_mode == ESP_LINE_ENDINGS_CRLF || s_tx_mode == ESP_LINE_ENDINGS_LF) {
65                 char lf = '\n';
66                 esp_usb_console_write_buf(&lf, 1);
67             }
68         }
69     }
70     _lock_release_recursive(&s_write_lock);
71     return size;
72 }
73 
cdcacm_fsync(int fd)74 static int cdcacm_fsync(int fd)
75 {
76     assert(fd == 0);
77     _lock_acquire_recursive(&s_write_lock);
78     ssize_t written = esp_usb_console_flush();
79     _lock_release_recursive(&s_write_lock);
80     return (written < 0) ? -1 : 0;
81 }
82 
cdcacm_open(const char * path,int flags,int mode)83 static int cdcacm_open(const char *path, int flags, int mode)
84 {
85     return 0; // fd 0
86 }
87 
cdcacm_fstat(int fd,struct stat * st)88 static int cdcacm_fstat(int fd, struct stat *st)
89 {
90     assert(fd == 0);
91     memset(st, 0, sizeof(*st));
92     st->st_mode = S_IFCHR;
93     return 0;
94 }
95 
cdcacm_close(int fd)96 static int cdcacm_close(int fd)
97 {
98     assert(fd == 0);
99     return 0;
100 }
101 
102 static int s_peek_char = NONE;
103 
104 /* Helper function which returns a previous character or reads a new one from
105  * CDC-ACM driver. Previous character can be returned ("pushed back") using
106  * cdcacm_return_char function. Returns NONE if no character is available. Note
107  * the cdcacm driver maintains its own RX buffer and a receive call does not
108  * invoke an USB operation, so there's no penalty to reading data char-by-char.
109  */
cdcacm_read_char(void)110 static int cdcacm_read_char(void)
111 {
112     /* return character from peek buffer, if it is there */
113     if (s_peek_char != NONE) {
114         int c = s_peek_char;
115         s_peek_char = NONE;
116         return c;
117     }
118     /* Peek buffer is empty; try to read from cdcacm driver. */
119     uint8_t c;
120     ssize_t read = esp_usb_console_read_buf((char *) &c, 1);
121     if (read <= 0) {
122         return NONE;
123     } else {
124         return c;
125     }
126 }
127 
cdcacm_data_in_buffer(void)128 static bool cdcacm_data_in_buffer(void)
129 {
130     if (s_peek_char != NONE) {
131         return true;
132     }
133     if (esp_usb_console_read_available()) {
134         return true;
135     }
136     return false;
137 }
138 
139 /* Push back a character; it will be returned by next call to cdcacm_read_char */
cdcacm_return_char(int c)140 static void cdcacm_return_char(int c)
141 {
142     assert(s_peek_char == NONE);
143     s_peek_char = c;
144 }
145 
cdcacm_read(int fd,void * data,size_t size)146 static ssize_t cdcacm_read(int fd, void *data, size_t size)
147 {
148     assert(fd == 0);
149     char *data_c = (char *) data;
150     ssize_t received = 0;
151     _lock_acquire_recursive(&s_read_lock);
152 
153     while (!cdcacm_data_in_buffer()) {
154         if (!s_blocking) {
155             errno = EWOULDBLOCK;
156             _lock_release_recursive(&s_read_lock);
157             return -1;
158         }
159         xSemaphoreTake(s_rx_semaphore, portMAX_DELAY);
160     }
161 
162 
163     if (s_rx_mode == ESP_LINE_ENDINGS_CR || s_rx_mode == ESP_LINE_ENDINGS_LF) {
164         /* This is easy. Just receive, and if needed replace \r by \n. */
165         received = esp_usb_console_read_buf(data_c, size);
166         if (s_rx_mode == ESP_LINE_ENDINGS_CR) {
167             /* Change CRs to newlines */
168             for (ssize_t i = 0; i < received; i++) {
169                 if (data_c[i] == '\r') {
170                     data_c[i] = '\n';
171                 }
172             }
173         }
174     } else {
175         while (received < size) {
176             int c = cdcacm_read_char();
177             if (c == '\r') {
178                 /* look ahead */
179                 int c2 = cdcacm_read_char();
180                 if (c2 == NONE) {
181                     /* could not look ahead, put the current character back */
182                     cdcacm_return_char(c);
183                     break;
184                 }
185                 if (c2 == '\n') {
186                     /* this was \r\n sequence. discard \r, return \n */
187                     c = '\n';
188                 } else {
189                     /* \r followed by something else. put the second char back,
190                      * it will be processed on next iteration. return \r now.
191                      */
192                     cdcacm_return_char(c2);
193                 }
194             } else if (c == NONE) {
195                 break;
196             }
197             data_c[received++] = (char) c;
198             if (c == '\n') {
199                 break;
200             }
201         }
202     }
203     _lock_release_recursive(&s_read_lock);
204     if (received > 0) {
205         return received;
206     }
207     errno = EWOULDBLOCK;
208     return -1;
209 }
210 
211 /* Non-static, to be able to place into IRAM by ldgen */
cdcacm_rx_cb(void * arg)212 void cdcacm_rx_cb(void* arg)
213 {
214     assert(s_blocking);
215     xSemaphoreGive(s_rx_semaphore);
216 }
217 
218 /* Non-static, to be able to place into IRAM by ldgen */
cdcacm_tx_cb(void * arg)219 void cdcacm_tx_cb(void* arg)
220 {
221     assert(s_blocking);
222     xSemaphoreGive(s_tx_semaphore);
223 }
224 
cdcacm_enable_blocking(void)225 static int cdcacm_enable_blocking(void)
226 {
227     s_rx_semaphore = xSemaphoreCreateBinary();
228     if (!s_rx_semaphore) {
229         errno = ENOMEM;
230         goto fail;
231     }
232     s_tx_semaphore = xSemaphoreCreateBinary();
233     if (!s_tx_semaphore) {
234         errno = ENOMEM;
235         goto fail;
236     }
237     esp_err_t err = esp_usb_console_set_cb(&cdcacm_rx_cb, &cdcacm_tx_cb, NULL);
238     if (err != ESP_OK) {
239         errno = ENODEV;
240         goto fail;
241     }
242     s_blocking = true;
243     return 0;
244 
245 fail:
246     if (s_rx_semaphore) {
247         vSemaphoreDelete(s_rx_semaphore);
248         s_rx_semaphore = NULL;
249     }
250     if (s_tx_semaphore) {
251         vSemaphoreDelete(s_tx_semaphore);
252         s_tx_semaphore = NULL;
253     }
254     return -1;
255 }
256 
cdcacm_disable_blocking(void)257 static int cdcacm_disable_blocking(void)
258 {
259     esp_usb_console_set_cb(NULL, NULL, NULL); /* ignore any errors */
260     vSemaphoreDelete(s_rx_semaphore);
261     s_rx_semaphore = NULL;
262     vSemaphoreDelete(s_tx_semaphore);
263     s_tx_semaphore = NULL;
264     s_blocking = false;
265     return 0;
266 }
267 
268 
cdcacm_fcntl(int fd,int cmd,int arg)269 static int cdcacm_fcntl(int fd, int cmd, int arg)
270 {
271     assert(fd == 0);
272     int result;
273     if (cmd == F_GETFL) {
274         result = 0;
275         if (!s_blocking) {
276             result |= O_NONBLOCK;
277         }
278     } else if (cmd == F_SETFL) {
279         bool blocking = (arg & O_NONBLOCK) == 0;
280         result = 0;
281         if (blocking && !s_blocking) {
282             result = cdcacm_enable_blocking();
283         } else if (!blocking && s_blocking) {
284             result = cdcacm_disable_blocking();
285         }
286     } else {
287         /* unsupported operation */
288         result = -1;
289         errno = ENOSYS;
290     }
291     return result;
292 }
293 
esp_vfs_dev_cdcacm_set_tx_line_endings(esp_line_endings_t mode)294 void esp_vfs_dev_cdcacm_set_tx_line_endings(esp_line_endings_t mode)
295 {
296     s_tx_mode = mode;
297 }
298 
esp_vfs_dev_cdcacm_set_rx_line_endings(esp_line_endings_t mode)299 void esp_vfs_dev_cdcacm_set_rx_line_endings(esp_line_endings_t mode)
300 {
301     s_rx_mode = mode;
302 }
303 
304 static const esp_vfs_t vfs = {
305     .flags = ESP_VFS_FLAG_DEFAULT,
306     .write = &cdcacm_write,
307     .open = &cdcacm_open,
308     .fstat = &cdcacm_fstat,
309     .close = &cdcacm_close,
310     .read = &cdcacm_read,
311     .fcntl = &cdcacm_fcntl,
312     .fsync = &cdcacm_fsync
313 };
314 
esp_vfs_cdcacm_get_vfs(void)315 const esp_vfs_t *esp_vfs_cdcacm_get_vfs(void)
316 {
317     return &vfs;
318 }
319 
esp_vfs_dev_cdcacm_register(void)320 esp_err_t esp_vfs_dev_cdcacm_register(void)
321 {
322     return esp_vfs_register("/dev/cdcacm", &vfs, NULL);
323 }
324