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