1 /*
2  * Copyright (c) 2018 The Fuchsia Authors
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 
9 #include <lib/cdcconsole/cdcconsole.h>
10 
11 #include <lk/compiler.h>
12 #include <lk/err.h>
13 #include <string.h>
14 
15 #if !CONSOLE_HAS_INPUT_BUFFER
16 #error "CONSOLE_HAS_INPUT_BUFFER needs to be configured for cdcconsole."
17 #endif
18 
19 #ifndef CDCCONSOLE_TX_BUFFER_SIZE
20 #define CDCCONSOLE_TX_BUFFER_SIZE 64
21 #endif // CDCCONSOLE_TX_BUFFER_SIZE
22 
23 /*
24  * This implementation is pretty simple right now.  Possible future work:
25  *  * Make cdcconsole_print() asynchronous.
26  *  * Buffer output when offline so that you can see boot messages.
27  *  * Buffer writes with a timeout allowing the driver to coalesce them into
28  *    less packets.
29  */
30 
31 static status_t cdcconsole_rx_cb(ep_t endpoint, usbc_transfer_t *t);
32 static status_t cdcconsole_tx_cb(ep_t endpoint, usbc_transfer_t *t);
33 
34 // Call with con->tx_lock
cdcconsole_handle_tx(cdcconsole_t * con)35 static void cdcconsole_handle_tx(cdcconsole_t *con) {
36     iovec_t regions[2];
37     cbuf_peek(&con->tx_buf, regions);
38     if (regions[0].iov_len > 0) {
39         cdcserial_write_async(&con->cdc_chan, &con->tx_transfer, cdcconsole_tx_cb,
40                               regions[0].iov_len, regions[0].iov_base);
41         con->transmitting = true;
42     } else {
43         con->transmitting = false;
44     }
45 }
46 
cdcconsole_print(print_callback_t * cb,const char * str,size_t len)47 static void cdcconsole_print(print_callback_t *cb, const char *str, size_t len) {
48     cdcconsole_t *con = cb->context;
49 
50     cbuf_write(&con->tx_buf, str, len, false);
51 
52     if (con->online) {
53         spin_lock_saved_state_t state;
54         spin_lock_irqsave(&con->tx_lock, state);
55         if (!con->transmitting) {
56             cdcconsole_handle_tx(con);
57         }
58         spin_unlock_irqrestore(&con->tx_lock, state);
59     }
60 }
61 
cdcconsole_queue_read(cdcconsole_t * con)62 static void cdcconsole_queue_read(cdcconsole_t *con) {
63     cdcserial_read_async(&con->cdc_chan, &con->rx_transfer, cdcconsole_rx_cb,
64                          sizeof(con->rx_buf), con->rx_buf);
65 }
66 
cdcconsole_tx_cb(ep_t endpoint,usbc_transfer_t * t)67 static status_t cdcconsole_tx_cb(ep_t endpoint, usbc_transfer_t *t) {
68     cdcconsole_t *con = containerof(t, cdcconsole_t, tx_transfer);
69     spin_lock_saved_state_t state;
70     spin_lock_irqsave(&con->tx_lock, state);
71 
72     // Consume the peeked data.
73     cbuf_read(&con->tx_buf, NULL, t->bufpos, false);
74     cdcconsole_handle_tx(con);
75     spin_unlock_irqrestore(&con->tx_lock, state);
76     return NO_ERROR;
77 }
78 
cdcconsole_rx_cb(ep_t endpoint,usbc_transfer_t * t)79 static status_t cdcconsole_rx_cb(ep_t endpoint, usbc_transfer_t *t) {
80     cdcconsole_t *con = containerof(t, cdcconsole_t, rx_transfer);
81 
82     cbuf_write(&console_input_cbuf, t->buf, t->bufpos, false);
83     cdcconsole_queue_read(con);
84     return NO_ERROR;
85 }
86 
cdcconsole_online(cdcserial_channel_t * chan,bool online)87 static void cdcconsole_online(cdcserial_channel_t *chan, bool online) {
88     cdcconsole_t *con = containerof(chan, cdcconsole_t, cdc_chan);
89     con->online = online;
90     if (online) {
91         spin_lock_saved_state_t state;
92         spin_lock_irqsave(&con->tx_lock, state);
93         if (!con->transmitting) {
94             // Throw away previous data.
95             cbuf_reset(&con->tx_buf);
96         }
97         spin_unlock_irqrestore(&con->tx_lock, state);
98 
99         cdcconsole_queue_read(con);
100     }
101 }
102 
cdcconsole_init(cdcconsole_t * con,int data_ep_addr,int ctrl_ep_addr)103 void cdcconsole_init(cdcconsole_t *con, int data_ep_addr, int ctrl_ep_addr) {
104     memset(con, 0x0, sizeof(*con));
105 
106     cbuf_initialize(&con->tx_buf, CDCCONSOLE_TX_BUFFER_SIZE);
107     spin_lock_init(&con->tx_lock);
108 
109     con->cdc_chan.online_cb = cdcconsole_online;
110     cdcserial_create_channel(&con->cdc_chan, data_ep_addr, ctrl_ep_addr);
111 
112     con->print_cb.print = cdcconsole_print;
113     con->print_cb.context = con;
114     register_print_callback(&con->print_cb);
115 }
116