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 
16 #include <stdio.h>
17 #include <stdio_ext.h>
18 #include "esp_log.h"
19 #include "cdc.h"
20 #include "tusb_console.h"
21 #include "tinyusb.h"
22 #include "vfs_tinyusb.h"
23 
24 #define STRINGIFY(s) STRINGIFY2(s)
25 #define STRINGIFY2(s) #s
26 
27 static const char *TAG = "tusb_console";
28 
29 typedef struct {
30     FILE *in;
31     FILE *out;
32     FILE *err;
33 } console_handle_t;
34 
35 static console_handle_t con;
36 
37 
38 /**
39  * @brief Reopen standard streams using a new path
40  *
41  * @param f_in - pointer to a pointer holding a file for in or NULL to don't change stdin
42  * @param f_out - pointer to a pointer holding a file for out or NULL to don't change stdout
43  * @param f_err - pointer to a pointer holding a file for err or NULL to don't change stderr
44  * @param path - mount point
45  * @return esp_err_t ESP_FAIL or ESP_OK
46  */
redirect_std_streams_to(FILE ** f_in,FILE ** f_out,FILE ** f_err,const char * path)47 static esp_err_t redirect_std_streams_to(FILE **f_in, FILE **f_out, FILE **f_err, const char *path)
48 {
49     if (f_in) {
50         *f_in = freopen(path, "r", stdin);
51         if (*f_in == NULL) {
52             ESP_LOGE(TAG, "Failed to reopen in!");
53             return ESP_FAIL;
54         }
55     }
56     if (f_out) {
57         *f_out = freopen(path, "w", stdout);
58         if (*f_out == NULL) {
59             ESP_LOGE(TAG, "Failed to reopen out!");
60             return ESP_FAIL;
61         }
62     }
63     if (f_err) {
64         *f_err = freopen(path, "w", stderr);
65         if (*f_err == NULL) {
66             ESP_LOGE(TAG, "Failed to reopen err!");
67             return ESP_FAIL;
68         }
69     }
70 
71     return ESP_OK;
72 }
73 
74 /**
75  * @brief Restore output to default
76  *
77  * @param f_in - pointer to a pointer of an in file updated with `redirect_std_streams_to` or NULL to don't change stdin
78  * @param f_out - pointer to a pointer of an out file updated with `redirect_std_streams_to` or NULL to don't change stdout
79  * @param f_err - pointer to a pointer of an err file updated with `redirect_std_streams_to` or NULL to don't change stderr
80  * @return esp_err_t ESP_FAIL or ESP_OK
81  */
restore_std_streams(FILE ** f_in,FILE ** f_out,FILE ** f_err)82 static esp_err_t restore_std_streams(FILE **f_in, FILE **f_out, FILE **f_err)
83 {
84     const char *default_uart_dev = "/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM);
85     if (f_in) {
86         stdin = freopen(default_uart_dev, "r", *f_in);
87         if (stdin == NULL) {
88             ESP_LOGE(TAG, "Failed to reopen stdin!");
89             return ESP_FAIL;
90         }
91     }
92     if (f_out) {
93         stdout = freopen(default_uart_dev, "w", *f_out);
94         if (stdout == NULL) {
95             ESP_LOGE(TAG, "Failed to reopen stdout!");
96             return ESP_FAIL;
97         }
98     }
99     if (f_err) {
100         stderr = freopen(default_uart_dev, "w", *f_err);
101         if (stderr == NULL) {
102             ESP_LOGE(TAG, "Failed to reopen stderr!");
103             return ESP_FAIL;
104         }
105     }
106     return ESP_OK;
107 }
108 
esp_tusb_init_console(int cdc_intf)109 esp_err_t esp_tusb_init_console(int cdc_intf)
110 {
111     if (!tinyusb_cdc_initialized(cdc_intf)) {
112         ESP_LOGE(TAG, "Can't init the console because TinyUSB's CDC is not initialized!");
113         return ESP_ERR_INVALID_STATE;
114     }
115     /* Registering TUSB at VFS */
116     int res = esp_vfs_tusb_cdc_register(cdc_intf, NULL);
117     if (res != ESP_OK) {
118         return res;
119     }
120 
121     res = redirect_std_streams_to(&con.in, &con.out, &con.err, "/dev/tusb_cdc");
122     if (res != ESP_OK) {
123         return res;
124     }
125 
126     return ESP_OK;
127 }
128 
esp_tusb_deinit_console(int cdc_intf)129 esp_err_t esp_tusb_deinit_console(int cdc_intf)
130 {
131     if (!tinyusb_cdc_initialized(cdc_intf)) {
132         ESP_LOGE(TAG, "Can't deinit the console because TinyUSB's CDC is not initialized!");
133         return ESP_ERR_INVALID_STATE;
134     }
135 
136     int res = restore_std_streams(&con.in, &con.out, &con.err);
137     if (res != ESP_OK) {
138         return res;
139     }
140     esp_vfs_tusb_cdc_unregister(NULL);
141     return ESP_OK;
142 }
143