1 // Copyright 2020 Espressif Systems (Shanghai) Co. Ltd.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <stdint.h>
16 #include "esp_check.h"
17 #include "esp_err.h"
18 #include "esp_log.h"
19 #include "freertos/FreeRTOS.h"
20 #include "freertos/task.h"
21 #include "tusb.h"
22 #include "tusb_cdc_acm.h"
23 #include "cdc.h"
24 #include "sdkconfig.h"
25 
26 #define RX_UNREADBUF_SZ_DEFAULT 64 // buffer storing all unread RX data
27 
28 
29 typedef struct {
30     bool initialized;
31     size_t rx_unread_buf_sz;
32     RingbufHandle_t rx_unread_buf;
33     xSemaphoreHandle ringbuf_read_mux;
34     uint8_t *rx_tfbuf;
35     tusb_cdcacm_callback_t callback_rx;
36     tusb_cdcacm_callback_t callback_rx_wanted_char;
37     tusb_cdcacm_callback_t callback_line_state_changed;
38     tusb_cdcacm_callback_t callback_line_coding_changed;
39 } esp_tusb_cdcacm_t; /*!< CDC_AMC object */
40 
41 static const char *TAG = "tusb_cdc_acm";
42 
get_acm(tinyusb_cdcacm_itf_t itf)43 static inline esp_tusb_cdcacm_t *get_acm(tinyusb_cdcacm_itf_t itf)
44 {
45     esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
46     if (cdc_inst == NULL) {
47         return (esp_tusb_cdcacm_t *)NULL;
48     }
49     return (esp_tusb_cdcacm_t *)(cdc_inst->subclass_obj);
50 }
51 
52 
53 /* TinyUSB callbacks
54    ********************************************************************* */
55 
56 /* Invoked by cdc interface when line state changed e.g connected/disconnected */
tud_cdc_line_state_cb(uint8_t itf,bool dtr,bool rts)57 void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
58 {
59     esp_tusb_cdcacm_t *acm = get_acm(itf);
60     if (dtr && rts) { // connected
61         if (acm != NULL) {
62             ESP_LOGV(TAG, "Host connected to CDC no.%d.", itf);
63         } else {
64             ESP_LOGW(TAG, "Host is connected to CDC no.%d, but it is not initialized. Initialize it using `tinyusb_cdc_init`.", itf);
65             return;
66         }
67     } else { // disconnected
68         if (acm != NULL) {
69             ESP_LOGV(TAG, "Serial device is ready to connect to CDC no.%d", itf);
70         } else {
71             return;
72         }
73     }
74     if (acm) {
75         tusb_cdcacm_callback_t cb = acm->callback_line_state_changed;
76         if (cb) {
77             cdcacm_event_t event = {
78                 .type = CDC_EVENT_LINE_STATE_CHANGED,
79                 .line_state_changed_data = {
80                     .dtr = dtr,
81                     .rts = rts
82                 }
83             };
84             cb(itf, &event);
85         }
86     }
87 }
88 
89 
90 /* Invoked when CDC interface received data from host */
tud_cdc_rx_cb(uint8_t itf)91 void tud_cdc_rx_cb(uint8_t itf)
92 {
93     esp_tusb_cdcacm_t *acm = get_acm(itf);
94     if (acm) {
95         if (!acm->rx_unread_buf) {
96             ESP_LOGE(TAG, "There is no RX buffer created");
97             abort();
98         }
99     } else {
100         tud_cdc_n_read_flush(itf); // we have no place to store data, so just drop it
101         return;
102     }
103     while (tud_cdc_n_available(itf)) {
104         int read_res = tud_cdc_n_read(  itf,
105                                         acm->rx_tfbuf,
106                                         CONFIG_TINYUSB_CDC_RX_BUFSIZE );
107         int res = xRingbufferSend(acm->rx_unread_buf,
108                                   acm->rx_tfbuf,
109                                   read_res, 0);
110         if (res != pdTRUE) {
111             ESP_LOGW(TAG, "The unread buffer is too small, the data has been lost");
112         } else {
113             ESP_LOGV(TAG, "Sent %d bytes to the buffer", read_res);
114         }
115     }
116     if (acm) {
117         tusb_cdcacm_callback_t cb = acm->callback_rx;
118         if (cb) {
119             cdcacm_event_t event = {
120                 .type = CDC_EVENT_RX
121             };
122             cb(itf, &event);
123         }
124     }
125 }
126 
127 // Invoked when line coding is change via SET_LINE_CODING
tud_cdc_line_coding_cb(uint8_t itf,cdc_line_coding_t const * p_line_coding)128 void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding)
129 {
130     esp_tusb_cdcacm_t *acm = get_acm(itf);
131     if (acm) {
132         tusb_cdcacm_callback_t cb = acm->callback_line_coding_changed;
133         if (cb) {
134             cdcacm_event_t event = {
135                 .type = CDC_EVENT_LINE_CODING_CHANGED,
136                 .line_coding_changed_data = {
137                     .p_line_coding = p_line_coding,
138                 }
139             };
140             cb(itf, &event);
141         }
142     } else {
143         return;
144     }
145 }
146 
147 // Invoked when received `wanted_char`
tud_cdc_rx_wanted_cb(uint8_t itf,char wanted_char)148 void tud_cdc_rx_wanted_cb(uint8_t itf, char wanted_char)
149 {
150     esp_tusb_cdcacm_t *acm = get_acm(itf);
151     if (acm) {
152         tusb_cdcacm_callback_t cb = acm->callback_rx_wanted_char;
153         if (cb) {
154             cdcacm_event_t event = {
155                 .type = CDC_EVENT_RX_WANTED_CHAR,
156                 .rx_wanted_char_data = {
157                     .wanted_char = wanted_char,
158                 }
159             };
160             cb(itf, &event);
161         }
162     } else {
163         return;
164     }
165 }
166 
167 
168 
tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,cdcacm_event_type_t event_type,tusb_cdcacm_callback_t callback)169 esp_err_t tinyusb_cdcacm_register_callback(tinyusb_cdcacm_itf_t itf,
170         cdcacm_event_type_t event_type,
171         tusb_cdcacm_callback_t callback)
172 {
173     esp_tusb_cdcacm_t *acm = get_acm(itf);
174     if (acm) {
175         switch (event_type) {
176         case CDC_EVENT_RX:
177             acm->callback_rx = callback;
178             return ESP_OK;
179         case CDC_EVENT_RX_WANTED_CHAR:
180             acm->callback_rx_wanted_char = callback;
181             return ESP_OK;
182         case CDC_EVENT_LINE_STATE_CHANGED:
183             acm->callback_line_state_changed = callback;
184             return ESP_OK;
185         case CDC_EVENT_LINE_CODING_CHANGED:
186             acm->callback_line_coding_changed = callback;
187             return ESP_OK;
188         default:
189             ESP_LOGE(TAG, "Wrong event type");
190             return ESP_ERR_INVALID_ARG;
191         }
192     } else {
193         ESP_LOGE(TAG, "CDC-ACM is not initialized");
194         return ESP_ERR_INVALID_STATE;
195     }
196 }
197 
198 
tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf,cdcacm_event_type_t event_type)199 esp_err_t tinyusb_cdcacm_unregister_callback(tinyusb_cdcacm_itf_t itf,
200         cdcacm_event_type_t event_type)
201 {
202     esp_tusb_cdcacm_t *acm = get_acm(itf);
203     if (!acm) {
204         ESP_LOGE(TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
205         return ESP_ERR_INVALID_STATE;
206     }
207     switch (event_type) {
208     case CDC_EVENT_RX:
209         acm->callback_rx = NULL;
210         return ESP_OK;
211     case CDC_EVENT_RX_WANTED_CHAR:
212         acm->callback_rx_wanted_char = NULL;
213         return ESP_OK;
214     case CDC_EVENT_LINE_STATE_CHANGED:
215         acm->callback_line_state_changed = NULL;
216         return ESP_OK;
217     case CDC_EVENT_LINE_CODING_CHANGED:
218         acm->callback_line_coding_changed = NULL;
219         return ESP_OK;
220     default:
221         ESP_LOGE(TAG, "Wrong event type");
222         return ESP_ERR_INVALID_ARG;
223     }
224 }
225 
226 /*********************************************************************** TinyUSB callbacks*/
227 /* CDC-ACM
228    ********************************************************************* */
229 
read_from_rx_unread_to_buffer(esp_tusb_cdcacm_t * acm,uint8_t * out_buf,size_t req_bytes,size_t * read_bytes)230 static esp_err_t read_from_rx_unread_to_buffer(esp_tusb_cdcacm_t *acm, uint8_t *out_buf, size_t req_bytes, size_t *read_bytes)
231 {
232     uint8_t *buf = xRingbufferReceiveUpTo(acm->rx_unread_buf, read_bytes, 0, req_bytes);
233     if (buf) {
234         memcpy(out_buf, buf, *read_bytes);
235         vRingbufferReturnItem(acm->rx_unread_buf, (void *)(buf));
236         return ESP_OK;
237     } else {
238         return ESP_ERR_NO_MEM;
239     }
240 }
241 
ringbuf_mux_take(esp_tusb_cdcacm_t * acm)242 static esp_err_t ringbuf_mux_take(esp_tusb_cdcacm_t *acm)
243 {
244     if (xSemaphoreTake(acm->ringbuf_read_mux, 0) != pdTRUE) {
245         ESP_LOGW(TAG, "Read error: ACM is busy");
246         return ESP_ERR_INVALID_STATE;
247     }
248     return ESP_OK;
249 }
250 
ringbuf_mux_give(esp_tusb_cdcacm_t * acm)251 static esp_err_t ringbuf_mux_give(esp_tusb_cdcacm_t *acm)
252 {
253     BaseType_t ret = xSemaphoreGive(acm->ringbuf_read_mux);
254     assert(ret == pdTRUE);
255     return ESP_OK;
256 }
257 
tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf,uint8_t * out_buf,size_t out_buf_sz,size_t * rx_data_size)258 esp_err_t tinyusb_cdcacm_read(tinyusb_cdcacm_itf_t itf, uint8_t *out_buf, size_t out_buf_sz, size_t *rx_data_size)
259 {
260     esp_tusb_cdcacm_t *acm = get_acm(itf);
261     ESP_RETURN_ON_FALSE(acm, ESP_ERR_INVALID_STATE, TAG, "Interface is not initialized. Use `tinyusb_cdc_init` for initialization");
262     size_t read_sz;
263 
264     /* Take a mutex to proceed two uninterrupted read operations */
265     ESP_RETURN_ON_ERROR(ringbuf_mux_take(acm), TAG, "ringbuf_mux_take failed");
266 
267     esp_err_t res = read_from_rx_unread_to_buffer(acm, out_buf, out_buf_sz, &read_sz);
268     if (res != ESP_OK) {
269         ESP_RETURN_ON_ERROR(ringbuf_mux_give(acm), TAG, "ringbuf_mux_give failed");
270         return res;
271     }
272 
273     *rx_data_size = read_sz;
274     /* Buffer's data can be wrapped, at that situations we should make another retrievement */
275     if (read_from_rx_unread_to_buffer(acm, out_buf + read_sz, out_buf_sz - read_sz, &read_sz) == ESP_OK) {
276         *rx_data_size += read_sz;
277     }
278 
279     ESP_RETURN_ON_ERROR(ringbuf_mux_give(acm), TAG, "ringbuf_mux_give failed");
280     return ESP_OK;
281 }
282 
283 
tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf,char ch)284 size_t tinyusb_cdcacm_write_queue_char(tinyusb_cdcacm_itf_t itf, char ch)
285 {
286     if (!get_acm(itf)) { // non-initialized
287         return 0;
288     }
289     return tud_cdc_n_write_char(itf, ch);
290 }
291 
292 
tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf,const uint8_t * in_buf,size_t in_size)293 size_t tinyusb_cdcacm_write_queue(tinyusb_cdcacm_itf_t itf, const uint8_t *in_buf, size_t in_size)
294 {
295     if (!get_acm(itf)) { // non-initialized
296         return 0;
297     }
298     return tud_cdc_n_write(itf, in_buf, in_size);
299 }
300 
tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf)301 static uint32_t tud_cdc_n_write_occupied(tinyusb_cdcacm_itf_t itf)
302 {
303     return CFG_TUD_CDC_TX_BUFSIZE - tud_cdc_n_write_available(itf);
304 }
305 
tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf,uint32_t timeout_ticks)306 esp_err_t tinyusb_cdcacm_write_flush(tinyusb_cdcacm_itf_t itf, uint32_t timeout_ticks)
307 {
308     if (!get_acm(itf)) { // non-initialized
309         return ESP_FAIL;
310     }
311 
312     if (!timeout_ticks) { // if no timeout - nonblocking mode
313         int res = tud_cdc_n_write_flush(itf);
314         if (!res) {
315             ESP_LOGW(TAG, "flush failed (res: %d)", res);
316             return ESP_FAIL;
317         } else {
318             if (tud_cdc_n_write_occupied(itf)) {
319                 ESP_LOGW(TAG, "remained data to flush!");
320                 return ESP_FAIL;
321             }
322         }
323         return ESP_ERR_TIMEOUT;
324     } else { // trying during the timeout
325         uint32_t ticks_start = xTaskGetTickCount();
326         uint32_t ticks_now = ticks_start;
327         while (1) { // loop until success or until the time runs out
328             ticks_now = xTaskGetTickCount();
329             if (!tud_cdc_n_write_occupied(itf)) { // if nothing to write - nothing to flush
330                 break;
331             }
332             if (tud_cdc_n_write_flush(itf)) { // Success
333                 break;
334             }
335             if ( (ticks_now - ticks_start) > timeout_ticks ) { // Time is up
336                 ESP_LOGW(TAG, "Flush failed");
337                 return ESP_ERR_TIMEOUT;
338             }
339             vTaskDelay(1);
340         }
341         return ESP_OK;
342     }
343 }
344 
alloc_obj(tinyusb_cdcacm_itf_t itf)345 static esp_err_t alloc_obj(tinyusb_cdcacm_itf_t itf)
346 {
347     esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
348     cdc_inst->subclass_obj = calloc(1, sizeof(esp_tusb_cdcacm_t));
349     if (!cdc_inst->subclass_obj) {
350         return ESP_FAIL;
351     } else {
352         return ESP_OK;
353     }
354 }
355 
free_obj(tinyusb_cdcacm_itf_t itf)356 static void free_obj(tinyusb_cdcacm_itf_t itf)
357 {
358     esp_tusb_cdc_t *cdc_inst = tinyusb_cdc_get_intf(itf);
359     free(cdc_inst->subclass_obj);
360     cdc_inst->subclass_obj = NULL;
361 }
362 
tusb_cdc_acm_init(const tinyusb_config_cdcacm_t * cfg)363 esp_err_t tusb_cdc_acm_init(const tinyusb_config_cdcacm_t *cfg)
364 {
365     int itf = (int)cfg->cdc_port;
366     /* Creating a CDC object */
367     const tinyusb_config_cdc_t cdc_cfg = {
368         .usb_dev = cfg->usb_dev,
369         .cdc_class = TUSB_CLASS_CDC,
370         .cdc_subclass.comm_subclass = CDC_COMM_SUBCLASS_ABSTRACT_CONTROL_MODEL
371     };
372     ESP_RETURN_ON_ERROR(tinyusb_cdc_init(itf, &cdc_cfg), TAG, "tinyusb_cdc_init failed");
373     ESP_RETURN_ON_ERROR(alloc_obj(itf), TAG, "alloc_obj failed");
374 
375     esp_tusb_cdcacm_t *acm = get_acm(itf);
376     /* Callbacks setting up*/
377     if (cfg->callback_rx) {
378         tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX, cfg->callback_rx);
379     }
380     if (cfg->callback_rx_wanted_char) {
381         tinyusb_cdcacm_register_callback(itf, CDC_EVENT_RX_WANTED_CHAR, cfg->callback_rx_wanted_char);
382     }
383     if (cfg->callback_line_state_changed) {
384         tinyusb_cdcacm_register_callback(itf, CDC_EVENT_LINE_STATE_CHANGED, cfg->callback_line_state_changed);
385     }
386     if (cfg->callback_line_coding_changed) {
387         tinyusb_cdcacm_register_callback( itf, CDC_EVENT_LINE_CODING_CHANGED, cfg->callback_line_coding_changed);
388     }
389 
390     /* Buffers */
391 
392     acm->ringbuf_read_mux = xSemaphoreCreateMutex();
393     if (acm->ringbuf_read_mux == NULL) {
394         ESP_LOGE(TAG, "Creation of a ringbuf mutex failed");
395         free_obj(itf);
396         return ESP_ERR_NO_MEM;
397     }
398 
399     acm->rx_tfbuf = malloc(CONFIG_TINYUSB_CDC_RX_BUFSIZE);
400     if (!acm->rx_tfbuf) {
401         ESP_LOGE(TAG, "Creation buffer error");
402         free_obj(itf);
403         return ESP_ERR_NO_MEM;
404     }
405     acm->rx_unread_buf_sz = cfg->rx_unread_buf_sz == 0 ? RX_UNREADBUF_SZ_DEFAULT : cfg->rx_unread_buf_sz;
406     acm->rx_unread_buf = xRingbufferCreate(acm->rx_unread_buf_sz, RINGBUF_TYPE_BYTEBUF);
407     if (acm->rx_unread_buf == NULL) {
408         ESP_LOGE(TAG, "Creation buffer error");
409         free_obj(itf);
410         return ESP_ERR_NO_MEM;
411     } else {
412         ESP_LOGD(TAG, "Comm Initialized buff:%d bytes", cfg->rx_unread_buf_sz);
413         return ESP_OK;
414     }
415 }
416 
tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)417 bool tusb_cdc_acm_initialized(tinyusb_cdcacm_itf_t itf)
418 {
419     esp_tusb_cdcacm_t *acm = get_acm(itf);
420     if (acm) {
421         return true;
422     } else {
423         return false;
424     }
425 }
426 /*********************************************************************** CDC-ACM*/
427